Carp/test/interface.carp
Scott Olsen d420635762
feat: overwrite existing interface implementations (#1094)
* feat: overwrite existing interface implementations

This commit alters the behavior of interfaces so that implementations
with the same type signature will overwrite previous implementations
with that signature--before this was a runtime error.

Previously, if a user defined two distinctly named implementations of an
interface that shared a type, Carp would panic and error at runtime if
the interface was called and resolved to the type, since it couldn't
decide which implementation to use from the type alone. After this
commit, we instead issue a warning and overwrite existing
implementations of the same type, so that defining:

```
(defn foo [] 0)
(implements zero foo)
```

will replace `Int.zero` in the `zero` interface's implementation path
list and won't result in a runtime error--instead `foo` will be called
when `zero` is called in a context in which it returns an int:

```
[WARNING] An implementation of the interface zero with type (Fn [] Int)
already exists: Int.zero. It will be replaced by the implementation:
foo.
This may break a bunch of upstream code!
```

test/interface.carp also has a concrete illustration of this case.

* chore: address hlint suggestions

* fix: don't print overridden interface implementations in info

This commit updates our handling of interface overrides to remove
interfaces from the implements meta of a function that was overridden by
a new implementation.

Similarly, this refactors primitiveInfo to prevent printing binders that
do not actually implement an interface.

* refactor: incorporate @TimDeve's error message suggestion
2020-12-23 22:24:52 +01:00

63 lines
1.7 KiB
Plaintext

;; Test Interfaces
(load "Test.carp")
(use Test)
(definterface foo (Fn [a] Int))
;; A module implements an interface using implements.
;; Implementations don't need to share names with interfaces.
(defmodule A
(defn bar [x] x)
(implements foo A.bar))
;; Implementations may be declared before definitions
;; like `doc`, the name is relative to the module environment
(defmodule B
(implements foo baz)
(defn baz [y] (if y 5 0)))
;; Interfaces may be implemented retroactively
;; global functions can also implement interfaces.
(sig gojira (Fn [&String] String))
(defn gojira [s] @s)
(implements monster gojira)
(definterface monster (Fn [a] String))
;; An interface name can be used as a default implementation
(defn monster [scary?] (if scary? @"RAWR" @"meow"))
(implements monster monster)
;; If multiple implementations of the same concrete type are provided,
;; one overwrites the other.
(defn laugh-monster [times] (String.repeat times "LOL"))
(implements monster laugh-monster)
(defn pikachu [times] (String.repeat times "pika"))
(implements monster pikachu)
(deftest test
(assert-equal test
&2
&(foo 2) ;; A.foo
"Implements works as expected.")
(assert-equal test
&5
&(foo true) ;; B.foo
"Implementations can be declared before definitions.")
(assert-equal test
"SKRYEEE"
&(monster "SKRYEEE")
"Interfaces can be implemented retroactively.")
(assert-equal test
"meow"
&(monster false)
"Implementations may be global, and an implementation with the same name may
be used as a default.")
(assert-equal test
"pikapikapika"
&(monster 3)
"Implementations may be overwritten, when multiple implementations of the same type
are provided.")
)