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

Invoke instance methods for Any overrides #6441

Merged
merged 5 commits into from
Apr 28, 2023
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
2 changes: 2 additions & 0 deletions distribution/lib/Standard/Base/0.0.0-dev/src/Data/List.enso
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,8 @@ type List

An error representing that the list is empty.
type Empty_Error
Error

## PRIVATE

Pretty prints the empty error.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ from project.Test import Test
Any.should_fail_with : Any -> Integer -> Test_Result
Any.should_fail_with self matcher frames_to_skip=0 =
loc = Meta.get_source_location 1+frames_to_skip
matcher_text = case matcher.to_text of
text : Text -> text
_ -> Meta.meta matcher . to_text
matcher_text = matcher . to_text
Test.fail ("Expected an error " + matcher_text + " but no error occurred, instead got: " + self.to_text + " (at " + loc + ").")

## Expect a function to fail with the provided dataflow error.
Expand All @@ -48,9 +46,7 @@ Error.should_fail_with self matcher frames_to_skip=0 =
caught = self.catch
if caught == matcher || caught.is_a matcher then Nothing else
loc = Meta.get_source_location 2+frames_to_skip
matcher_text = case matcher.to_text of
text : Text -> text
_ -> Meta.meta matcher . to_text
matcher_text = matcher . to_text
Test.fail ("Expected error "+matcher_text+", but error " + caught.to_text + " has been returned (at " + loc + ").")

## Asserts that `self` value is equal to the expected value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2167,15 +2167,15 @@ class RuntimeVisualizationsTest
val moduleName = "Enso_Test.Test.Main"
val metadata = new Metadata

val idMain = metadata.addItem(106, 28)
val idMain = metadata.addItem(106, 34)

val code =
"""import Standard.Base.Data.List
|import Standard.Visualization
|import Standard.Base.Error.Error
|
|main =
| Error.throw List.Empty_Error
| Error.throw List.Empty_Error.Error
|""".stripMargin.linesIterator.mkString("\n")
val contents = metadata.appendToCode(code)
val mainFile = context.writeMain(contents)
Expand Down Expand Up @@ -2258,7 +2258,7 @@ class RuntimeVisualizationsTest
data
}
val stringified = new String(data)
stringified shouldEqual """{"kind":"Dataflow","message":"The List is empty. (at <enso> Main.main(Enso_Test.Test.Main:6:5-32)"}"""
stringified shouldEqual """{"kind":"Dataflow","message":"The List is empty. (at <enso> Main.main(Enso_Test.Test.Main:6:5-38)"}"""
}

it should "run visualisation default preprocessor" in {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
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.ConditionProfile;
import com.oracle.truffle.api.source.SourceSection;
import org.enso.interpreter.node.BaseNode;
import org.enso.interpreter.node.MethodRootNode;
import org.enso.interpreter.node.callable.dispatch.InvokeFunctionNode;
import org.enso.interpreter.node.callable.resolver.HostMethodCallNode;
import org.enso.interpreter.node.callable.resolver.MethodResolverNode;
Expand Down Expand Up @@ -102,19 +104,46 @@ public void setTailStatus(TailStatus tailStatus) {
public abstract Object execute(
VirtualFrame frame, State state, UnresolvedSymbol symbol, Object self, Object[] arguments);

@Specialization(guards = {"dispatch.hasType(self)", "!dispatch.hasSpecialDispatch(self)"})
@Specialization(guards = {"typesLibrary.hasType(self)", "!typesLibrary.hasSpecialDispatch(self)"})
Object doFunctionalDispatch(
VirtualFrame frame,
State state,
UnresolvedSymbol symbol,
Object self,
Object[] arguments,
@CachedLibrary(limit = "10") TypesLibrary dispatch,
@CachedLibrary(limit = "10") TypesLibrary typesLibrary,
@Cached MethodResolverNode methodResolverNode) {
Function function = methodResolverNode.expectNonNull(self, dispatch.getType(self), symbol);

Type selfTpe = typesLibrary.getType(self);
Function function = methodResolverNode.expectNonNull(self, selfTpe, symbol);

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.
if (where instanceof MethodRootNode node && typeCanOverride(node, EnsoContext.get(this))) {
Function anyFun = symbol.getScope().lookupMethodDefinition(EnsoContext.get(this).getBuiltins().any(), symbol.getName());
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, we could resolve symbol on Any first and then on selfTpe. That unfortunately leads to more complicated checks (one problematic example is for example is_a that is defined on Any, Error, Meta and who knows what else) so I didn't pursue it further.

if (anyFun != null) {
function = anyFun;
}
}
return invokeFunctionNode.execute(function, frame, state, arguments);
}

private boolean typeCanOverride(MethodRootNode node, EnsoContext ctx) {
Type methodOwnerType = node.getType();
Builtins builtins = ctx.getBuiltins();
Type any = builtins.any();
Type warning = builtins.warning();
Type panic = builtins.panic();
return methodOwnerType.isEigenType()

&& builtins.nothing() != methodOwnerType
&& any.getEigentype() != methodOwnerType
&& panic.getEigentype() != methodOwnerType
&& warning.getEigentype() != methodOwnerType;
}

@Specialization
Object doDataflowError(
VirtualFrame frame,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ Function resolveCached(
}

@Specialization(replaces = "resolveCached")
@CompilerDirectives.TruffleBoundary
Function resolveUncached(Type self, UnresolvedSymbol symbol) {
return symbol.resolveFor(self);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,10 @@ public Type getEigentype() {
return eigentype;
}

public boolean isEigenType() {
return eigentype == this;
}

public void registerConstructor(AtomConstructor constructor) {
constructors.put(constructor.getName(), constructor);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,8 +237,6 @@ public void testPrintToTextStaticMethod() throws Exception {
IO.println a
""";

checkPrint(code, "My_Object.type.to_text[test:6-16]");
// We would want the following result:
//checkPrint(code, "My_Object");
checkPrint(code, "My_Object");
hubertp marked this conversation as resolved.
Show resolved Hide resolved
}
}
27 changes: 23 additions & 4 deletions test/Tests/src/Semantic/Any_Spec.enso
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
from Standard.Base import all

from Standard.Test import Test
from Standard.Test import Test, Test_Suite
import Standard.Test.Extensions

type My_Type
Value a
from project.Semantic.Definitions.Any_Types import all

spec =
Test.group "Any.map_nothing" <|
Expand Down Expand Up @@ -39,3 +37,24 @@ spec =
(1 != 2) . should_be_true
(1 != 1) . should_be_false

Test.group "Any's methods" <|
Test.specify "should not be overridable when called statically" <|
My_Type.Value 33 . x . should_equal "Any:(My_Type.Value 33)"
With_X.Value 44 . x . should_equal "With_X:(With_X.Value 44)"
With_Y.Value 44 . x . should_equal "With_Y:With_Y(44)"
Any.x . to_text . should_equal "Any.type.x[Any_Types.enso:6-26]"
My_Type.x . should_equal "Any:My_Type"
With_X.x . to_text . should_equal "Any:With_X"
With_X.y . to_text . should_equal "With_X.type.y[Any_Types.enso:12-32]"
With_Y.x . to_text . should_equal "Any:With_Y"
With_Y.y . to_text . should_equal "With_Y.type.y[Any_Types.enso:18-38]"
With_X.to_text . to_text . should_equal "With_X"
With_Y.to_text . to_text . should_equal "With_Y"
hubertp marked this conversation as resolved.
Show resolved Hide resolved
Any.x self=With_X . should_equal "Any:With_X"
Any.x self=With_Y . should_equal "Any:With_Y"
Any.x (My_Type.Value 22) . should_equal "Any:(My_Type.Value 22)"
Any.x (With_X.Value 22) . should_equal "Any:(With_X.Value 22)"
Any.x (With_Y.Value 22) . should_equal "Any:With_Y(22)"
Date.to_display_text . should_equal "Date"

main = Test_Suite.run_main spec
20 changes: 20 additions & 0 deletions test/Tests/src/Semantic/Definitions/Any_Types.enso
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from Standard.Base import all

type My_Type
Value a

Any.x self = "Any:" + self.to_text

type With_X
Value b

x self = "With_X:" + self.to_text
y self = "With_X:" + self.to_text

type With_Y
Value b

x self = "With_Y:" + self.to_text
y self = "With_Y:" + self.to_text

to_text self = "With_Y("+self.b.to_text+")"
1 change: 1 addition & 0 deletions test/Tests/src/Semantic/Meta_Spec.enso
Original file line number Diff line number Diff line change
Expand Up @@ -370,4 +370,5 @@ spec =
(.is_nothing) . is_a Function . should_equal True
Meta.type_of (_.is_nothing) . should_equal Function
Meta.type_of (.is_nothing) . should_equal Function

main = Test_Suite.run_main spec
20 changes: 10 additions & 10 deletions test/Tests/src/Semantic/Names_Spec.enso
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from Standard.Base import all

from project.Semantic.Names.Definitions import another_method, another_constant, method_with_local_vars, Bar_Data, Bar
import project.Semantic.Names.Definitions
from project.Semantic.Definitions.Names import another_method, another_constant, method_with_local_vars, Bar_Data, Bar
import project.Semantic.Definitions.Names

from Standard.Test import Test
import Standard.Test.Extensions

Definitions.Foo.my_method self = case self of
Definitions.Foo.Value x y z -> x * y * z
Names.Foo.my_method self = case self of
Names.Foo.Value x y z -> x * y * z

get_foo module = module.Foo

Expand All @@ -18,14 +18,14 @@ add_one (x = 0) = x + 1
spec =
Test.group "Qualified Names" <|
Test.specify "should allow to call constructors in a qualified manner" <|
Definitions.Foo.Value 1 2 3 . sum . should_equal 6
Names.Foo.Value 1 2 3 . sum . should_equal 6
Test.specify "should allow pattern matching in a qualified manner" <|
v = Definitions.Foo.Value 1 2 3
v = Names.Foo.Value 1 2 3
res = case v of
Definitions.Foo.Value a b c -> a + b + c
Names.Foo.Value a b c -> a + b + c
res.should_equal 6
Test.specify "should allow defining methods on qualified names" <|
v = Definitions.Foo.Value 2 3 5
v = Names.Foo.Value 2 3 5
v.my_method.should_equal 30
Test.group "Lowercase Methods" <|
Test.specify "should allow calling methods without a target" <|
Expand All @@ -38,8 +38,8 @@ spec =
another_method 10 . should_equal 10
another_constant . should_equal 10
Test.specify "should allow calling methods with fully qualified module name" <|
(Definitions.another_method 10).should_equal 10
v = Definitions.another_method
(Names.another_method 10).should_equal 10
v = Names.another_method
v 10 . should_equal 10
Test.specify "should be resolved correctly in the presence of variables with the same name" <|
method_with_local_vars 1 . should_equal 13
Expand Down