Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prefer module methods over Any instance ones #12048

Merged
merged 4 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@
previously allowed to use spaces in the argument definition without
parentheses. [This is now a syntax error.][11856]
- [Native libraries of projects can be added to `polyglot/lib` directory][11874]
- [Redo stack is no longer lost when interacting with text literals][11908].
JaroslavTulach marked this conversation as resolved.
Show resolved Hide resolved
- [Prefer module methods over `Any` instance ones][12048]
- Symetric, transitive and reflexive [equality for intersection types][11897]
- [IR definitions are generated by an annotation processor][11770]

[11777]: https://github.com/enso-org/enso/pull/11777
[11600]: https://github.com/enso-org/enso/pull/11600
[11856]: https://github.com/enso-org/enso/pull/11856
[11874]: https://github.com/enso-org/enso/pull/11874
[11908]: https://github.com/enso-org/enso/pull/11908
[12048]: https://github.com/enso-org/enso/pull/12048
[11897]: https://github.com/enso-org/enso/pull/11897
[11770]: https://github.com/enso-org/enso/pull/11770

Expand Down
5 changes: 2 additions & 3 deletions docs/semantics/dispatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,5 @@ as _methods_.

## Dynamic Dispatch

> The actionables for this section are:
>
> - Specify the semantics of dynamic dispatch in Enso.
There is a [decicated page](../types/dynamic-dispatch.md) discussing _dynamic
dispatch and its semantics_.
55 changes: 55 additions & 0 deletions docs/types/dynamic-dispatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@ implementation to invoke.

- [Specificity](#specificity)
- [Multiple Dispatch](#multiple-dispatch)
- [Resolving Clashes on `Any`](#resolving-clashes-on-any)

<!-- /MarkdownTOC -->

Another page related to [dispatch](../semantics/dispatch.md) exists.

## Specificity

In order to determine which of the potential dispatch candidates is the correct
Expand Down Expand Up @@ -107,3 +110,55 @@ Multiple dispatch is also used on `from` conversions, because in expression
>
> Here, because `Person` conforms to the `HasName` interface, the second `greet`
> implementation is chosen because the constraints make it more specific.

## Resolving Clashes on `Any`

Special attention must be paid to `Any` and its methods and extension methods.
`Any` is a super type of all objects in Enso. As such the methods available on
`Any` are also available on every object - including special objects like those
representing `type` and holding its _static methods_ (discussed at
[types page](types.md) as well).

There is a `to_text` _instance method_ defined on `Any` - what does it mean when
one calls `Integer.to_text`? Should it by treated as:

```ruby
Any.to_text Integer # yields Integer text
```

or should be a static reference to `Integer.to_text` method without providing
the argument? In case of _regular types_ like `Integer` the following code:

```ruby
main = Integer.to_text
```

is considered as invocation of _instance method_ `to_text` on object `Integer`
and yields `Integer` text.

The situation is different when a _module static_ method together with `Any`
_extension method_ is defined:

```ruby
# following function makes sure `simplify` can be called on any object
Any.simplify self = "Not a Test module, but"+self.to_text
# following "overrides" the method on Test module
simplify = "Test module"
```

With such a setup following code invokes `Any.simplify` extension method

```ruby
main = "Hi".simplify
```

and yields `Not a Test module, but Hi` text. On contrary the following code
yields `Test module` value:

```ruby
main = Test.simplify
```

When invoking a method on _module object_ its _module static methods_ take
precedence over _instance methods_ defined on `Any`. Thus a module serves
primarily as a _container for module (static) methods_.
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.enso.interpreter.test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.io.ByteArrayOutputStream;
import org.enso.common.LanguageInfo;
import org.enso.common.MethodNames;
import org.enso.test.utils.ContextUtils;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

public class AnyOrStaticTest {
private static final ByteArrayOutputStream out = new ByteArrayOutputStream();
private Context ctx;

@Before
public void prepareCtx() {
ctx = ContextUtils.createDefaultContext(out);
out.reset();
}

@After
public void disposeCtx() {
ctx.close();
}

@Test
public void methodOnModuleAny() throws Exception {
var code =
"""
from Standard.Base import Any

check_is v t = "check of "+v.to_text+" and "+t.to_text
Any.check_is self t = "got to Any for "+self.to_text+" and "+t.to_text

dispatch a b = Checker.check_is a b
""";
var src = Source.newBuilder(LanguageInfo.ID, code, "Checker.enso").build();

var module = ctx.eval(src);

var dispatch = module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "dispatch");
var where = dispatch.execute("FirstArg", "SecondArg");
assertTrue("String returned " + where, where.isString());
assertEquals("check of FirstArg and SecondArg", where.asString());
}

@Test
public void methodOnTypeAndAny() throws Exception {
var code =
"""
from Standard.Base import Any

type Type_With_Check
check_is v t = "check of "+v.to_text+" and "+t.to_text

Any.check_is self t = "got to Any for "+self.to_text+" and "+t.to_text

dispatch a = Type_With_Check.check_is a
""";
var src = Source.newBuilder(LanguageInfo.ID, code, "Typer.enso").build();

var module = ctx.eval(src);

var dispatch = module.invokeMember(MethodNames.Module.EVAL_EXPRESSION, "dispatch");
var where = dispatch.execute("FirstArg");
assertTrue("String returned " + where, where.isString());
assertEquals("got to Any for Type_With_Check and FirstArg", where.asString());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.profiles.BranchProfile;
import com.oracle.truffle.api.profiles.CountingConditionProfile;
import com.oracle.truffle.api.source.SourceSection;
Expand Down Expand Up @@ -169,16 +168,18 @@ Function resolveFunction(
return null;
}

RootNode where = function.getCallTarget().getRootNode();
// If both Any and the type where `function` is declared, define `symbol`
// and the method is invoked statically, i.e. type of self is the eigentype,
// then we want to disambiguate method resolution by always resolved to the one in Any.
EnsoContext ctx = EnsoContext.get(this);
if (where instanceof MethodRootNode node && typeCanOverride(node, ctx)) {
Type any = ctx.getBuiltins().any();
Function anyFun = symbol.getScope().lookupMethodDefinition(any, symbol.getName());
if (anyFun != null) {
function = anyFun;
if (selfTpe.getDefinitionScope().getAssociatedType() != selfTpe) {
var where = function.getCallTarget().getRootNode();
// If both Any and the type where `function` is declared, define `symbol`
// and the method is invoked statically, i.e. type of self is the eigentype,
// then we want to disambiguate method resolution by always resolved to the one in Any.
var ctx = EnsoContext.get(this);
if (where instanceof MethodRootNode node && typeCanOverride(node, ctx)) {
Type any = ctx.getBuiltins().any();
Function anyFun = symbol.getScope().lookupMethodDefinition(any, symbol.getName());
if (anyFun != null) {
function = anyFun;
}
}
}
return function;
Expand Down
Loading