Skip to content

Commit b3dd778

Browse files
authored
Static, but instance, but static (#3764)
Adds the ability to write `Foo.method (Mk_Foo 123)` as a synonym of `(Mk_Foo 123).method` because Rust.
1 parent 7e0ab89 commit b3dd778

File tree

7 files changed

+76
-17
lines changed

7 files changed

+76
-17
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,7 @@
378378
- [Invalidate module's IR cache if imported module changed][3703]
379379
- [Don't rename imported Main module that only imports names][3710]
380380
- [Notify node status to the IDE][3729]
381+
- [Make instance methods callable like statics][3764]
381382
- [Distinguish static and instance methods][3740]
382383
- [By-type pattern matching][3742]
383384
- [Fix performance of method calls on polyglot arrays][3781]
@@ -431,6 +432,7 @@
431432
[3710]: https://github.com/enso-org/enso/pull/3710
432433
[3729]: https://github.com/enso-org/enso/pull/3729
433434
[3740]: https://github.com/enso-org/enso/pull/3740
435+
[3764]: https://github.com/enso-org/enso/pull/3764
434436
[3742]: https://github.com/enso-org/enso/pull/3742
435437
[3781]: https://github.com/enso-org/enso/pull/3781
436438

engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1474,7 +1474,11 @@ class IrToTruffle(
14741474

14751475
val argName = arg.getName
14761476

1477-
if (seenArgNames contains argName) {
1477+
if (
1478+
argName != Constants.Names.SELF_ARGUMENT && seenArgNames.contains(
1479+
argName
1480+
)
1481+
) {
14781482
throw new IllegalStateException(
14791483
s"A duplicate argument name, $argName, was found during codegen."
14801484
)

engine/runtime/src/main/scala/org/enso/compiler/context/SuggestionBuilder.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,14 @@ final class SuggestionBuilder[A: IndexedSource](val source: A) {
7474

7575
go(tree ++= tpSuggestions.map(Tree.Node(_, Vector())), scope)
7676

77-
case IR.Module.Scope.Definition.Method
77+
case m @ IR.Module.Scope.Definition.Method
7878
.Explicit(
7979
IR.Name.MethodReference(typePtr, methodName, _, _, _),
8080
IR.Function.Lambda(args, body, _, _, _, _),
8181
_,
8282
_,
8383
_
84-
) =>
84+
) if !m.isStaticWrapperForInstanceMethod =>
8585
val typeSignature = ir.getMetadata(TypeSignatures)
8686
val selfTypeOpt = typePtr match {
8787
case Some(typePtr) =>

engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1455,6 +1455,21 @@ object IR {
14551455
true // if it's not a function, it has no arguments, therefore no `self`
14561456
}
14571457

1458+
def isStaticWrapperForInstanceMethod: Boolean = body match {
1459+
case function: Function.Lambda =>
1460+
function.arguments.map(_.name) match {
1461+
case IR.Name.Self(_, true, _, _) :: IR.Name.Self(
1462+
_,
1463+
false,
1464+
_,
1465+
_
1466+
) :: _ =>
1467+
true
1468+
case _ => false
1469+
}
1470+
case _ => false
1471+
}
1472+
14581473
}
14591474

14601475
/** The definition of a method for a given constructor using sugared

engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/MethodDefinitions.scala

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import org.enso.compiler.context.{InlineContext, ModuleContext}
44
import org.enso.compiler.core.IR
55
import org.enso.compiler.core.ir.MetadataStorage.ToPair
66
import org.enso.compiler.data.BindingsMap
7+
import org.enso.compiler.data.BindingsMap.{Resolution, ResolvedType}
78
import org.enso.compiler.exception.CompilerError
89
import org.enso.compiler.pass.IRPass
910
import org.enso.compiler.pass.analyse.BindingAnalysis
@@ -84,7 +85,38 @@ case object MethodDefinitions extends IRPass {
8485
case other => other
8586
}
8687

87-
ir.copy(bindings = newDefs)
88+
val withStaticAliases = newDefs.flatMap {
89+
case method: IR.Module.Scope.Definition.Method.Explicit
90+
if !method.isStatic =>
91+
method.methodReference.typePointer.flatMap(
92+
_.getMetadata(this)
93+
) match {
94+
case Some(Resolution(ResolvedType(_, tp))) if tp.members.nonEmpty =>
95+
val dup = method.duplicate()
96+
val static = dup.copy(body =
97+
IR.Function.Lambda(
98+
List(
99+
IR.DefinitionArgument
100+
.Specified(
101+
IR.Name.Self(None, true),
102+
None,
103+
None,
104+
false,
105+
None
106+
)
107+
),
108+
dup.body,
109+
None
110+
)
111+
)
112+
List(method, static)
113+
case _ => List(method)
114+
}
115+
116+
case other => List(other)
117+
}
118+
119+
ir.copy(bindings = withStaticAliases)
88120
}
89121

90122
private def resolveType(

engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/OverloadsResolution.scala

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ case object OverloadsResolution extends IRPass {
4747
ir: IR.Module,
4848
@unused moduleContext: ModuleContext
4949
): IR.Module = {
50-
var seenTypes: Set[String] = Set()
51-
var seenMethods: Map[Option[String], Set[String]] = Map()
50+
var seenTypes: Set[String] = Set()
51+
var seenMethods: Map[Option[String], Set[(String, Boolean)]] = Map()
5252

5353
val types = ir.bindings.collect {
5454
case tp: IR.Module.Scope.Definition.Type => tp
@@ -72,7 +72,7 @@ case object OverloadsResolution extends IRPass {
7272
val newMethods: List[IR.Module.Scope.Definition] = methods.map(method => {
7373
if (
7474
seenMethods(method.typeName.map(_.name))
75-
.contains(method.methodName.name)
75+
.contains((method.methodName.name, method.isStatic))
7676
) {
7777
IR.Error.Redefined
7878
.Method(method.typeName, method.methodName, method.location)
@@ -85,10 +85,10 @@ case object OverloadsResolution extends IRPass {
8585
method.location
8686
)
8787
case _ =>
88-
val currentMethods = seenMethods(method.typeName.map(_.name))
88+
val currentMethods: Set[(String, Boolean)] =
89+
seenMethods(method.typeName.map(_.name))
8990
seenMethods = seenMethods + (method.typeName.map(_.name) ->
90-
(currentMethods + method.methodName.name))
91-
91+
(currentMethods + ((method.methodName.name, method.isStatic))))
9292
method
9393
}
9494
}

engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/MethodsTest.scala

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -165,19 +165,25 @@ class MethodsTest extends InterpreterTest {
165165
eval(code).toString shouldEqual "(Mk_Foo 123)"
166166
}
167167

168-
"not be callable on types when non-static" in {
168+
"be callable on types when non-static, with additional self arg" in {
169169
val code =
170-
"""
170+
"""from Standard.Base.IO import all
171+
|
171172
|type Foo
172173
| Mk_Foo a
173174
|
174-
| inc self = Mk_Foo self.a
175+
| inc self = Foo.Mk_Foo self.a+1
175176
|
176-
|main = Foo.inc
177+
|main =
178+
| IO.println (Foo.inc (Foo.Mk_Foo 12))
179+
| IO.println (Foo.Mk_Foo 13).inc
180+
| IO.println (.inc self=Foo self=(Foo.Mk_Foo 14))
181+
| IO.println (Foo.inc self=(Foo.Mk_Foo 15))
177182
|""".stripMargin
178-
the[InterpreterException] thrownBy eval(
179-
code
180-
) should have message "Method `inc` of Foo could not be found."
183+
eval(code)
184+
consumeOut.shouldEqual(
185+
List("(Mk_Foo 13)", "(Mk_Foo 14)", "(Mk_Foo 15)", "(Mk_Foo 16)")
186+
)
181187
}
182188

183189
"not be callable on instances when static" in {

0 commit comments

Comments
 (0)