From ddb43af5a22b1457f9c0acd31686caee4968cbed Mon Sep 17 00:00:00 2001 From: Marcin Kostrzewa Date: Wed, 5 Aug 2020 22:16:44 +0200 Subject: [PATCH] Qualified names & uppercase name resolution (#1062) --- docs/CONTRIBUTING.md | 7 +- .../benchmarks/semantic/AtomBenchmarks.java | 6 + .../fixtures/semantic/AtomFixtures.scala | 10 + .../atom/QualifiedAccessorNode.java | 39 +++ .../org/enso/interpreter/runtime/Module.java | 2 +- .../callable/atom/AtomConstructor.java | 16 + .../runtime/scope/ModuleScope.java | 13 +- .../main/scala/org/enso/compiler/Passes.scala | 2 + .../org/enso/compiler/codegen/AstToIr.scala | 67 +++-- .../org/enso/compiler/codegen/AstView.scala | 28 +- .../enso/compiler/codegen/IrToTruffle.scala | 89 ++++-- .../compiler/context/FreshNameSupply.scala | 11 +- .../scala/org/enso/compiler/core/IR.scala | 73 ++++- .../org/enso/compiler/data/BindingsMap.scala | 113 ++++++- .../compiler/pass/analyse/AliasAnalysis.scala | 7 +- .../pass/analyse/BindingAnalysis.scala | 30 ++ .../compiler/pass/desugar/ComplexType.scala | 2 +- .../desugar/LambdaShorthandToLambda.scala | 11 +- .../pass/resolve/IgnoredBindings.scala | 6 +- .../pass/resolve/MethodDefinitions.scala | 31 +- .../enso/compiler/pass/resolve/Patterns.scala | 66 ++++- .../pass/resolve/SuspendedArguments.scala | 6 +- .../pass/resolve/UppercaseNames.scala | 280 ++++++++++++++++++ .../enso/compiler/phase/StubIrBuilder.scala | 19 +- .../org/enso/compiler/test/CompilerTest.scala | 13 +- .../compiler/test/codegen/AstToIrTest.scala | 6 +- .../pass/analyse/BindingAnalysisTest.scala | 10 +- .../pass/analyse/DataflowAnalysisTest.scala | 2 +- .../pass/analyse/GatherDiagnosticsTest.scala | 12 +- .../pass/desugar/OperatorToFunctionTest.scala | 9 +- .../optimise/ApplicationSaturationTest.scala | 22 +- .../pass/optimise/LambdaConsolidateTest.scala | 8 +- .../pass/resolve/UppercaseNamesTest.scala | 131 ++++++++ .../test/semantic/ConstructorsTest.scala | 2 +- .../test/semantic/PatternMatchTest.scala | 10 +- project/WithDebugCommand.scala | 19 +- test/Test/src/Main.enso | 2 + test/Test/src/Names/Definitions.enso | 6 + test/Test/src/Names_Spec.enso | 39 +++ 39 files changed, 1063 insertions(+), 162 deletions(-) create mode 100644 engine/runtime/src/main/java/org/enso/interpreter/node/expression/atom/QualifiedAccessorNode.java create mode 100644 engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/UppercaseNames.scala create mode 100644 engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/UppercaseNamesTest.scala create mode 100644 test/Test/src/Names/Definitions.enso create mode 100644 test/Test/src/Names_Spec.enso diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index c69b4d7a4f..35c22a6afa 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -398,7 +398,12 @@ need to follow these steps: and add them to `truffleRunOptions` in [`build.sbt`](build.sbt). Remove the portion of these options after `suspend=y`, including the comma. They are placeholders that we don't use. -6. Now, when you want to debug something, you can place a breakpoint as usual in +6. Alternatively, certain tasks, such as `run`, `benchOnly` and `testOnly` can + be used through the `withDebug` SBT command. For this to work, your remote + configuration must specify the host of `localhost` and the port `5005`. + The command syntax is `withDebug --debugger TASK_NAME -- TASK_PARAMETERS`, + e.g. `withDebug --debugger testOnly -- *AtomConstructors*`. +7. Now, when you want to debug something, you can place a breakpoint as usual in IntelliJ, and then execute your remote debugging configuration. Now, in the SBT shell, run a command to execute the code you want to debug (e.g. `testOnly *CurryingTest*`). This will open the standard debugger interface diff --git a/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/AtomBenchmarks.java b/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/AtomBenchmarks.java index 9ef80ef675..57a9a4ad70 100644 --- a/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/AtomBenchmarks.java +++ b/engine/runtime/src/bench/java/org/enso/interpreter/bench/benchmarks/semantic/AtomBenchmarks.java @@ -25,6 +25,12 @@ public class AtomBenchmarks { main.mainFunction().value().execute(main.mainConstructor(), fixtures.million()); } + @Benchmark + public void benchGenerateListQualified() { + DefaultInterpreterRunner.MainMethod main = fixtures.generateListQualified(); + main.mainFunction().value().execute(main.mainConstructor(), fixtures.million()); + } + private void benchOnList(DefaultInterpreterRunner.MainMethod main) { main.mainFunction().value().execute(main.mainConstructor(), fixtures.millionElementList()); } diff --git a/engine/runtime/src/bench/scala/org/enso/interpreter/bench/fixtures/semantic/AtomFixtures.scala b/engine/runtime/src/bench/scala/org/enso/interpreter/bench/fixtures/semantic/AtomFixtures.scala index 041ef3c952..ffd6ba7860 100644 --- a/engine/runtime/src/bench/scala/org/enso/interpreter/bench/fixtures/semantic/AtomFixtures.scala +++ b/engine/runtime/src/bench/scala/org/enso/interpreter/bench/fixtures/semantic/AtomFixtures.scala @@ -29,6 +29,16 @@ class AtomFixtures extends DefaultInterpreterRunner { """.stripMargin val generateList = getMain(generateListCode) + val generateListQualifiedCode = + """ + |main = length -> + | generator = acc -> i -> if i == 0 then acc else generator (Builtins.cons i acc) (i - 1) + | + | res = generator Builtins.nil length + | res + """.stripMargin + val generateListQualified = getMain(generateListQualifiedCode) + val reverseListCode = """ |main = list -> diff --git a/engine/runtime/src/main/java/org/enso/interpreter/node/expression/atom/QualifiedAccessorNode.java b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/atom/QualifiedAccessorNode.java new file mode 100644 index 0000000000..c5bfd7e729 --- /dev/null +++ b/engine/runtime/src/main/java/org/enso/interpreter/node/expression/atom/QualifiedAccessorNode.java @@ -0,0 +1,39 @@ +package org.enso.interpreter.node.expression.atom; + +import com.oracle.truffle.api.TruffleLanguage; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.nodes.NodeInfo; +import com.oracle.truffle.api.nodes.RootNode; +import org.enso.interpreter.runtime.callable.atom.Atom; +import org.enso.interpreter.runtime.callable.atom.AtomConstructor; +import org.enso.interpreter.runtime.callable.function.Function; +import org.enso.interpreter.runtime.state.Stateful; + +@NodeInfo( + shortName = "get_cons", + description = "A base for auto-generated module-level atom constructor getters.") +public class QualifiedAccessorNode extends RootNode { + private final AtomConstructor atomConstructor; + + /** + * Creates a new instance of this node. + * + * @param language the current language instance. + * @param atomConstructor the constructor to return. + */ + public QualifiedAccessorNode(TruffleLanguage language, AtomConstructor atomConstructor) { + super(language); + this.atomConstructor = atomConstructor; + } + + /** + * Executes the node, returning the predefined constructor. + * + * @param frame current execution frame + * @return the constant constructor + */ + public Stateful execute(VirtualFrame frame) { + Object state = Function.ArgumentsHelper.getState(frame.getArguments()); + return new Stateful(state, atomConstructor); + } +} diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/Module.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/Module.java index 18d0618f75..9f5c4dedb7 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/Module.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/Module.java @@ -323,7 +323,7 @@ public class Module implements TruffleObject { Types.extractArguments(args, AtomConstructor.class, String.class); AtomConstructor cons = arguments.getFirst(); String name = arguments.getSecond(); - return scope.getMethods().get(cons).get(name); + return scope.getMethods().get(cons).get(name.toLowerCase()); } private static AtomConstructor getConstructor(ModuleScope scope, Object[] args) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java index 2ba472fc22..556250d689 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/callable/atom/AtomConstructor.java @@ -13,6 +13,7 @@ import org.enso.interpreter.node.ExpressionNode; import org.enso.interpreter.node.callable.argument.ReadArgumentNode; import org.enso.interpreter.node.expression.atom.GetFieldNode; import org.enso.interpreter.node.expression.atom.InstantiateNode; +import org.enso.interpreter.node.expression.atom.QualifiedAccessorNode; import org.enso.interpreter.node.expression.builtin.InstantiateAtomNode; import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition; import org.enso.interpreter.runtime.callable.function.Function; @@ -82,11 +83,26 @@ public class AtomConstructor implements TruffleObject { } private void generateMethods(ArgumentDefinition[] args) { + generateQualifiedAccessor(); for (ArgumentDefinition arg : args) { definitionScope.registerMethod(this, arg.getName(), generateGetter(arg.getPosition())); } } + private void generateQualifiedAccessor() { + QualifiedAccessorNode node = new QualifiedAccessorNode(null, this); + RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(node); + Function function = + new Function( + callTarget, + null, + new FunctionSchema( + FunctionSchema.CallStrategy.ALWAYS_DIRECT, + new ArgumentDefinition(0, "this", ArgumentDefinition.ExecutionMode.EXECUTE))); + definitionScope.registerMethod( + definitionScope.getAssociatedType(), this.name.toLowerCase(), function); + } + private Function generateGetter(int position) { GetFieldNode node = new GetFieldNode(null, position); RootCallTarget callTarget = Truffle.getRuntime().createCallTarget(node); diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java index 5aa2a3428f..ac24080ea4 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java @@ -105,6 +105,7 @@ public class ModuleScope { * @param function the {@link Function} associated with this definition */ public void registerMethod(AtomConstructor atom, String method, Function function) { + method = method.toLowerCase(); Map methodMap = ensureMethodMapFor(atom); if (methodMap.containsKey(method)) { @@ -147,16 +148,17 @@ public class ModuleScope { */ @CompilerDirectives.TruffleBoundary public Function lookupMethodDefinition(AtomConstructor atom, String name) { - Function definedWithAtom = atom.getDefinitionScope().getMethodMapFor(atom).get(name); + String lowerName = name.toLowerCase(); + Function definedWithAtom = atom.getDefinitionScope().getMethodMapFor(atom).get(lowerName); if (definedWithAtom != null) { return definedWithAtom; } - Function definedHere = getMethodMapFor(atom).get(name); + Function definedHere = getMethodMapFor(atom).get(lowerName); if (definedHere != null) { return definedHere; } return imports.stream() - .map(scope -> scope.getMethodMapFor(atom).get(name)) + .map(scope -> scope.getMethodMapFor(atom).get(lowerName)) .filter(Objects::nonNull) .findFirst() .orElse(null); @@ -179,6 +181,11 @@ public class ModuleScope { return methods; } + /** @return the polyglot symbols imported into this scope. */ + public Map getPolyglotSymbols() { + return polyglotSymbols; + } + public void reset() { imports = new HashSet<>(); methods = new HashMap<>(); diff --git a/engine/runtime/src/main/scala/org/enso/compiler/Passes.scala b/engine/runtime/src/main/scala/org/enso/compiler/Passes.scala index 64b694448f..d47831f39f 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/Passes.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/Passes.scala @@ -42,6 +42,8 @@ class Passes(passes: Option[List[PassGroup]] = None) { TypeFunctions, TypeSignatures, AliasAnalysis, + UppercaseNames, + AliasAnalysis, LambdaConsolidate, AliasAnalysis, SuspendedArguments, diff --git a/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstToIr.scala b/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstToIr.scala index 40ae248171..dd40daa613 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstToIr.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstToIr.scala @@ -116,7 +116,7 @@ object AstToIr { case AstView.Atom(consName, args) => Module.Scope.Definition .Atom( - Name.Literal(consName.name, getIdentifiedLocation(consName)), + buildName(consName), args.map(translateArgumentDefinition(_)), getIdentifiedLocation(inputAst) ) @@ -131,7 +131,7 @@ object AstToIr { if (containsAtomDefOrInclude && !hasArgs) { Module.Scope.Definition.Type( - Name.Literal(typeName.name, getIdentifiedLocation(typeName)), + buildName(typeName), args.map(translateArgumentDefinition(_)), translatedBody, getIdentifiedLocation(inputAst) @@ -148,14 +148,9 @@ object AstToIr { val pathSegments = targetPath.collect { case AST.Ident.Cons.any(c) => c } - val pathNames = pathSegments.map(c => - IR.Name.Literal(c.name, getIdentifiedLocation(c)) - ) + val pathNames = pathSegments.map(buildName) - val methodSegments = pathNames :+ Name.Literal( - nameStr.name, - getIdentifiedLocation(nameStr) - ) + val methodSegments = pathNames :+ buildName(nameStr) val typeSegments = methodSegments.init @@ -168,10 +163,8 @@ object AstToIr { MethodReference.genLocation(methodSegments) ) } else { - val typeName = Name.Here(None) - val methodName = - Name.Literal(nameStr.name, getIdentifiedLocation(nameStr)) - + val typeName = Name.Here(None) + val methodName = buildName(nameStr) Name.MethodReference( typeName, methodName, @@ -187,7 +180,7 @@ object AstToIr { ) case AstView.FunctionSugar(name, args, body) => val typeName = Name.Here(None) - val methodName = Name.Literal(name.name, getIdentifiedLocation(name)) + val methodName = buildName(name) val methodReference = Name.MethodReference( typeName, @@ -205,10 +198,10 @@ object AstToIr { case AstView.TypeAscription(typed, sig) => typed match { case AST.Ident.any(ident) => - val typeName = Name.Here(None) - val methodName = Name.Literal(ident.name, getIdentifiedLocation(ident)) + val typeName = Name.Here(None) + val methodName = buildName(ident) val methodReference = Name.MethodReference( - typeName, + typeName, methodName, methodName.location ) @@ -322,7 +315,7 @@ object AstToIr { ) case _ => IR.Application.Prefix( - IR.Name.Literal("negate", None), + IR.Name.Literal("negate", isReferent = false, None), List( IR.CallArgument.Specified( None, @@ -344,7 +337,7 @@ object AstToIr { case AstView .SuspendedBlock(name, block @ AstView.Block(lines, lastLine)) => Expression.Binding( - Name.Literal(name.name, getIdentifiedLocation(name)), + buildName(name), Expression.Block( lines.map(translateExpression), translateExpression(lastLine), @@ -366,7 +359,7 @@ object AstToIr { // Note [Uniform Call Syntax Translation] Application.Prefix( - translateExpression(name), + translateIdent(name), (target :: validArguments).map(translateCallArgument), hasDefaultsSuspended = hasDefaultsSuspended, getIdentifiedLocation(inputAst) @@ -556,7 +549,7 @@ object AstToIr { case AstView.AssignedArgument(left, right) => CallArgument .Specified( - Some(Name.Literal(left.name, getIdentifiedLocation(left))), + Some(buildName(left)), translateExpression(right), getIdentifiedLocation(arg) ) @@ -631,7 +624,7 @@ object AstToIr { } else { Application.Operator.Binary( leftArg, - Name.Literal(fn.name, getIdentifiedLocation(fn)), + buildName(fn), rightArg, getIdentifiedLocation(callable) ) @@ -680,13 +673,13 @@ object AstToIr { } else { Application.Operator.Section.Left( leftArg, - Name.Literal(left.opr.name, getIdentifiedLocation(left.opr)), + buildName(left.opr), getIdentifiedLocation(left) ) } case AST.App.Section.Sides.any(sides) => Application.Operator.Section.Sides( - Name.Literal(sides.opr.name, getIdentifiedLocation(sides.opr)), + buildName(sides.opr), getIdentifiedLocation(sides) ) case AST.App.Section.Right.any(right) => @@ -696,7 +689,7 @@ object AstToIr { Error.Syntax(section, Error.Syntax.NamedArgInSection) } else { Application.Operator.Section.Right( - Name.Literal(right.opr.name, getIdentifiedLocation(right.opr)), + buildName(right.opr), translateCallArgument(right.arg), getIdentifiedLocation(right) ) @@ -718,10 +711,10 @@ object AstToIr { } else if (name == "here") { Name.Here(getIdentifiedLocation(identifier)) } else { - Name.Literal(name, getIdentifiedLocation(identifier)) + buildName(identifier) } - case AST.Ident.Cons(name) => - Name.Literal(name, getIdentifiedLocation(identifier)) + case AST.Ident.Cons(_) => + buildName(identifier) case AST.Ident.Blank(_) => Name.Blank(getIdentifiedLocation(identifier)) case AST.Ident.Opr.any(_) => @@ -802,9 +795,14 @@ object AstToIr { */ def translatePattern(pattern: AST): Pattern = { AstView.MaybeManyParensed.unapply(pattern).getOrElse(pattern) match { - case AstView.ConstructorPattern(cons, fields) => + case AstView.ConstructorPattern(conses, fields) => + val irConses = conses.map(translateIdent(_).asInstanceOf[IR.Name]) + val name = irConses match { + case List(n) => n + case _ => IR.Name.Qualified(irConses, None) + } Pattern.Constructor( - translateIdent(cons).asInstanceOf[IR.Name], + name, fields.map(translatePattern), getIdentifiedLocation(pattern) ) @@ -904,4 +902,13 @@ object AstToIr { throw new UnhandledEntity(comment, "processComment") } } + + private def isReferant(ident: AST.Ident): Boolean = + ident match { + case AST.Ident.Cons.any(_) => true + case _ => false + } + + private def buildName(ident: AST.Ident): IR.Name.Literal = + IR.Name.Literal(ident.name, isReferant(ident), getIdentifiedLocation(ident)) } diff --git a/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstView.scala b/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstView.scala index cbdf4263e1..d56a616bf0 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstView.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/codegen/AstView.scala @@ -443,6 +443,13 @@ object AstView { object MethodCall { + private def consToVar(ast: AST.Ident): AST.Ident = + ast match { + case AST.Ident.Cons(c) => + AST.Ident.Var(c).setLocation(ast.location).setID(ast.id) + case _ => ast + } + /** Matches on a method call. * * A method call has the form `. ` where `` is @@ -456,14 +463,14 @@ object AstView { def unapply(ast: AST): Option[(AST, AST.Ident, List[AST])] = ast match { case OperatorDot(target, Application(ConsOrVar(ident), args)) => - Some((target, ident, args)) + Some((target, consToVar(ident), args)) case AST.App.Section.Left( MethodCall(target, ident, List()), susp @ SuspendDefaultsOperator(_) ) => Some((target, ident, List(susp))) case OperatorDot(target, ConsOrVar(ident)) => - Some((target, ident, List())) + Some((target, consToVar(ident), List())) case _ => None } } @@ -792,6 +799,17 @@ object AstView { } } + object QualifiedName { + def unapply(ast: AST): Option[List[AST.Ident.Cons]] = + ast match { + case OperatorDot(l, AST.Ident.Cons.any(name)) => + unapply(l).map(_ :+ name) + case AST.Ident.Cons.any(name) => + Some(List(name)) + case _ => None + } + } + object ConstructorPattern { /** Matches on a constructor pattern. @@ -803,12 +821,12 @@ object AstView { * @param ast the structure to try and match on * @return the pattern */ - def unapply(ast: AST): Option[(AST.Ident, List[AST])] = { + def unapply(ast: AST): Option[(List[AST.Ident.Cons], List[AST])] = { MaybeManyParensed.unapply(ast).getOrElse(ast) match { - case AST.Ident.Cons.any(cons) => Some((cons, List())) + case QualifiedName(cons) => Some((cons, List())) case SpacedList(elems) if elems.nonEmpty => elems.head match { - case AST.Ident.Cons.any(refName) => + case QualifiedName(refName) => val allFieldsValid = elems.tail.forall { case Pattern(_) => true case _ => false diff --git a/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala index 15c42ac468..011426e674 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala @@ -15,7 +15,11 @@ import org.enso.compiler.pass.analyse.{ TailCall } import org.enso.compiler.pass.optimise.ApplicationSaturation -import org.enso.compiler.pass.resolve.{MethodDefinitions, Patterns} +import org.enso.compiler.pass.resolve.{ + MethodDefinitions, + Patterns, + UppercaseNames +} import org.enso.interpreter.node.callable.argument.ReadArgumentNode import org.enso.interpreter.node.callable.function.{ BlockNode, @@ -60,7 +64,6 @@ import org.enso.interpreter.{Constants, Language} import scala.collection.mutable import scala.collection.mutable.ArrayBuffer -import scala.jdk.OptionConverters._ /** This is an implementation of a codegeneration pass that lowers the Enso * [[IR]] into the truffle [[org.enso.compiler.core.Core.Node]] structures that @@ -205,12 +208,21 @@ class IrToTruffle( methodDef.methodReference.typePointer .getMetadata(MethodDefinitions) .map { - case BindingsMap.Resolution(BindingsMap.ResolvedModule(module)) => - module.getScope.getAssociatedType - case BindingsMap.Resolution( - BindingsMap.ResolvedConstructor(definitionModule, cons) - ) => - definitionModule.getScope.getConstructors.get(cons.name) + res => + res.target match { + case BindingsMap.ResolvedModule(module) => + module.getScope.getAssociatedType + case BindingsMap.ResolvedConstructor(definitionModule, cons) => + definitionModule.getScope.getConstructors.get(cons.name) + case BindingsMap.ResolvedPolyglotSymbol(_, _) => + throw new CompilerError( + "Impossible polyglot symbol, should be caught by MethodDefinitions pass." + ) + case _: BindingsMap.ResolvedMethod => + throw new CompilerError( + "Impossible here, should be caught by MethodDefinitions pass." + ) + } } consOpt.foreach { @@ -555,6 +567,22 @@ class IrToTruffle( ) ) => Right(mod.getScope.getConstructors.get(cons.name)) + case Some( + BindingsMap.Resolution( + BindingsMap.ResolvedPolyglotSymbol(_, _) + ) + ) => + throw new CompilerError( + "Impossible polyglot symbol here, should be caught by Patterns resolution pass." + ) + case Some( + BindingsMap.Resolution( + BindingsMap.ResolvedMethod(_, _) + ) + ) => + throw new CompilerError( + "Impossible method here, should be caught by Patterns resolution pass." + ) } } @@ -685,7 +713,7 @@ class IrToTruffle( */ def processName(name: IR.Name): RuntimeExpression = { val nameExpr = name match { - case IR.Name.Literal(nameStr, _, _, _) => + case IR.Name.Literal(nameStr, _, _, _, _) => val useInfo = name .unsafeGetMetadata( AliasAnalysis, @@ -693,17 +721,28 @@ class IrToTruffle( ) .unsafeAs[AliasAnalysis.Info.Occurrence] - val slot = scope.getFramePointer(useInfo.id) - val atomCons = moduleScope.getConstructor(nameStr).toScala - val polySymbol = moduleScope.lookupPolyglotSymbol(nameStr).toScala - if (nameStr == Constants.Names.CURRENT_MODULE) { - ConstructorNode.build(moduleScope.getAssociatedType) - } else if (slot.isDefined) { + val slot = scope.getFramePointer(useInfo.id) + val global = name.getMetadata(UppercaseNames) + if (slot.isDefined) { ReadLocalVariableNode.build(slot.get) - } else if (atomCons.isDefined) { - ConstructorNode.build(atomCons.get) - } else if (polySymbol.isDefined) { - ConstantObjectNode.build(polySymbol.get) + } else if (global.isDefined) { + val resolution = global.get.target + resolution match { + case BindingsMap.ResolvedConstructor(definitionModule, cons) => + ConstructorNode.build( + definitionModule.getScope.getConstructors.get(cons.name) + ) + case BindingsMap.ResolvedModule(module) => + ConstructorNode.build(module.getScope.getAssociatedType) + case BindingsMap.ResolvedPolyglotSymbol(module, symbol) => + ConstantObjectNode.build( + module.getScope.getPolyglotSymbols.get(symbol.name) + ) + case BindingsMap.ResolvedMethod(_, _) => + throw new CompilerError( + "Impossible here, should be desugared by UppercaseNames resolver" + ) + } } else { DynamicSymbolNode.build( UnresolvedSymbol.build(nameStr, moduleScope) @@ -713,7 +752,12 @@ class IrToTruffle( ConstructorNode.build(moduleScope.getAssociatedType) case IR.Name.This(location, passData, _) => processName( - IR.Name.Literal(Constants.Names.THIS_ARGUMENT, location, passData) + IR.Name.Literal( + Constants.Names.THIS_ARGUMENT, + isReferent = false, + location, + passData + ) ) case _: IR.Name.Blank => throw new CompilerError( @@ -727,7 +771,7 @@ class IrToTruffle( throw new CompilerError( "Qualified names should not be present at codegen time." ) - case _: IR.Error.Resolution => throw new RuntimeException("todo") + case err: IR.Error.Resolution => processError(err) } setLocation(nameExpr, name.location) @@ -768,7 +812,8 @@ class IrToTruffle( context.getBuiltins.error().compileError().newInstance(err.message) case err: Error.Unexpected.TypeSignature => context.getBuiltins.error().compileError().newInstance(err.message) - case _: Error.Resolution => throw new RuntimeException("bleee") + case err: Error.Resolution => + context.getBuiltins.error().compileError().newInstance(err.message) case _: Error.Pattern => throw new CompilerError( "Impossible here, should be handled in the pattern match." diff --git a/engine/runtime/src/main/scala/org/enso/compiler/context/FreshNameSupply.scala b/engine/runtime/src/main/scala/org/enso/compiler/context/FreshNameSupply.scala index 07ecaa43e3..91841a1a3c 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/context/FreshNameSupply.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/context/FreshNameSupply.scala @@ -13,17 +13,20 @@ class FreshNameSupply { * @param numId the numeric identifier to use in the name * @return a new name */ - private def mkName(numId: Long): IR.Name.Literal = - IR.Name.Literal(s"", None) + private def mkName(numId: Long, isReferent: Boolean): IR.Name.Literal = { + val refMarker = if (isReferent) "ref" else "" + IR.Name.Literal(s"", isReferent, None) + } /** Generates a name guaranteed not to exist in this program. * + * @param isReferent whether or not the name should be marked as referent. * @return a new name */ - def newName(): IR.Name.Literal = { + def newName(isReferent: Boolean = false): IR.Name.Literal = { val num = counter counter += 1 - mkName(num) + mkName(num, isReferent) } } diff --git a/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala b/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala index 0fbdd4bf54..5c5d24fefe 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala @@ -1439,25 +1439,21 @@ object IR { keepDiagnostics: Boolean = true ): Name - /** Checks whether a name is in referant form. + /** Checks whether a name is in referent form. * * Please see the syntax specification for more details on this form. * - * @return `true` if `this` is in referant form, otherwise `false` + * @return `true` if `this` is in referent form, otherwise `false` */ - def isReferant: Boolean = { - name.split("_").filterNot(_.isEmpty).forall(_.head.isUpper) - } + def isReferent: Boolean /** Checks whether a name is in variable form. * * Please see the syntax specification for more details on this form. * - * @return `true` if `this` is in referant form, otherwise `false` + * @return `true` if `this` is in referent form, otherwise `false` */ - def isVariable: Boolean = !isReferant - - // TODO [AA] toReferant and toVariable for converting forms + def isVariable: Boolean = !isReferent } object Name { @@ -1510,6 +1506,8 @@ object IR { res } + override def isReferent: Boolean = true + override def duplicate( keepLocations: Boolean = true, keepMetadata: Boolean = true, @@ -1621,6 +1619,8 @@ object IR { override def setLocation(location: Option[IdentifiedLocation]): Name = copy(location = location) + override def isReferent: Boolean = true + /** Creates a copy of `this`. * * @param parts the segments of the name @@ -1706,6 +1706,8 @@ object IR { res } + override def isReferent: Boolean = false + override def duplicate( keepLocations: Boolean = true, keepMetadata: Boolean = true, @@ -1744,12 +1746,14 @@ object IR { /** The representation of a literal name. * * @param name the literal text of the name + * @param isReferent is this a referent name * @param location the source location that the node corresponds to * @param passData the pass metadata associated with this node * @param diagnostics compiler diagnostics for this node */ sealed case class Literal( override val name: String, + override val isReferent: Boolean, override val location: Option[IdentifiedLocation], override val passData: MetadataStorage = MetadataStorage(), override val diagnostics: DiagnosticStorage = DiagnosticStorage() @@ -1759,6 +1763,7 @@ object IR { /** Creates a copy of `this`. * * @param name the literal text of the name + * @param isReferent is this a referent name * @param location the source location that the node corresponds to * @param passData the pass metadata associated with this node * @param diagnostics compiler diagnostics for this node @@ -1767,12 +1772,13 @@ object IR { */ def copy( name: String = name, + isReferent: Boolean = isReferent, location: Option[IdentifiedLocation] = location, passData: MetadataStorage = passData, diagnostics: DiagnosticStorage = diagnostics, id: Identifier = id ): Literal = { - val res = Literal(name, location, passData, diagnostics) + val res = Literal(name, isReferent, location, passData, diagnostics) res.id = id res } @@ -1800,6 +1806,7 @@ object IR { s""" |IR.Name.Literal( |name = $name, + |isReferent = $isReferent, |location = $location, |passData = ${this.showPassData}, |diagnostics = $diagnostics, @@ -1826,6 +1833,8 @@ object IR { override protected var id: Identifier = randomId override val name: String = "this" + override def isReferent: Boolean = false + /** Creates a copy of `this`. * * @param location the source location that the node corresponds to @@ -1894,6 +1903,8 @@ object IR { override protected var id: Identifier = randomId override val name: String = "here" + override def isReferent: Boolean = false + /** Creates a copy of `this`. * * @param location the source location that the node corresponds to @@ -4995,6 +5006,8 @@ object IR { with IR.Name { override val name: String = originalName.name + override def isReferent: Boolean = originalName.isReferent + override def mapExpressions(fn: Expression => Expression): Resolution = this @@ -5052,19 +5065,51 @@ object IR { object Resolution { + /** + * A representation of a symbol resolution error. + */ + sealed trait Reason { + def explain(originalName: IR.Name): String + } + + /** + * An error coming from an unexpected occurence of a polyglot symbol. + * + * @param context the description of a context in which the error + * happened. + */ + case class UnexpectedPolyglot(context: String) extends Reason { + override def explain(originalName: Name): String = + s"The name ${originalName.name} resolved to a polyglot symbol," + + s"but polyglot symbols are not allowed in $context." + } + + /** + * An error coming from an unexpected occurence of a static method. + * + * @param context the description of a context in which the error + * happened. + */ + case class UnexpectedMethod(context: String) extends Reason { + override def explain(originalName: Name): String = + s"The name ${originalName.name} resolved to a method," + + s"but methods are not allowed in $context." + } + /** * An error coming from name resolver. * * @param err the original error. */ - case class Reason(err: BindingsMap.ResolutionError) { + case class ResolverError(err: BindingsMap.ResolutionError) + extends Reason { /** * Provides a human-readable explanation of the error. * @param originalName the original unresolved name. * @return a human-readable message. */ - def explain(originalName: IR.Name): String = + override def explain(originalName: IR.Name): String = err match { case BindingsMap.ResolutionAmbiguous(candidates) => val firstLine = @@ -5077,6 +5122,10 @@ object IR { s" Type ${cons.name} defined in module ${definitionModule.getName};" case BindingsMap.ResolvedModule(module) => s" The module ${module.getName};" + case BindingsMap.ResolvedPolyglotSymbol(_, symbol) => + s" The imported polyglot symbol ${symbol.name};" + case BindingsMap.ResolvedMethod(module, symbol) => + s" The method ${symbol.name} defined in module ${module.getName}" } (firstLine :: lines).mkString("\n") case BindingsMap.ResolutionNotFound => diff --git a/engine/runtime/src/main/scala/org/enso/compiler/data/BindingsMap.scala b/engine/runtime/src/main/scala/org/enso/compiler/data/BindingsMap.scala index d9beeffc6e..c444f4e732 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/data/BindingsMap.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/data/BindingsMap.scala @@ -8,10 +8,14 @@ import org.enso.interpreter.runtime.Module * A utility structure for resolving symbols in a given module. * * @param types the types defined in the current module + * @param polyglotSymbols the polyglot symbols imported into the scope + * @param moduleMethods the methods defined with current module as `this` * @param currentModule the module holding these bindings */ case class BindingsMap( types: List[BindingsMap.Cons], + polyglotSymbols: List[BindingsMap.PolyglotSymbol], + moduleMethods: List[BindingsMap.ModuleMethod], currentModule: Module ) extends IRPass.Metadata { import BindingsMap._ @@ -33,11 +37,28 @@ case class BindingsMap( .map(ResolvedConstructor(currentModule, _)) } + private def findPolyglotCandidates( + name: String + ): List[ResolvedPolyglotSymbol] = { + polyglotSymbols + .filter(_.name == name) + .map(ResolvedPolyglotSymbol(currentModule, _)) + } + + private def findMethodCandidates(name: String): List[ResolvedName] = { + moduleMethods + .filter(_.name.toLowerCase == name.toLowerCase) + .map(ResolvedMethod(currentModule, _)) + } + private def findLocalCandidates(name: String): List[ResolvedName] = { if (currentModule.getName.item == name) { List(ResolvedModule(currentModule)) } else { - findConstructorCandidates(name) + val conses = findConstructorCandidates(name) + val polyglot = findPolyglotCandidates(name) + val methods = findMethodCandidates(name) + conses ++ polyglot ++ methods } } @@ -57,7 +78,7 @@ case class BindingsMap( "Wrong pass ordering. Running resolution on an unparsed module" ) } - .flatMap(_.findConstructorCandidates(name)) + .flatMap(_.findExportedSymbolsFor(name)) } private def handleAmbiguity( @@ -70,6 +91,13 @@ case class BindingsMap( } } + private def getBindingsFrom(module: Module): BindingsMap = { + module.getIr.unsafeGetMetadata( + BindingAnalysis, + "imported module has no binding map info" + ) + } + /** * Resolves a name in the context of current module. * @@ -90,6 +118,52 @@ case class BindingsMap( } handleAmbiguity(findExportedCandidatesInImports(name)) } + + /** + * Resolves a qualified name to a symbol in the context of this module. + * + * @param name the name to resolve + * @return a resolution for `name` + */ + def resolveQualifiedName( + name: List[String] + ): Either[ResolutionError, ResolvedName] = + name match { + case List() => Left(ResolutionNotFound) + case List(item) => resolveUppercaseName(item) + case List(module, cons) => + resolveUppercaseName(module).flatMap { + case ResolvedModule(mod) => + getBindingsFrom(mod).resolveExportedName(cons) + case _ => Left(ResolutionNotFound) + } + case _ => + // TODO[MK] Implement when exports possible. Currently this has + // no viable interpretation. + Left(ResolutionNotFound) + } + + private def findExportedSymbolsFor(name: String): List[ResolvedName] = { + val matchingConses = types + .filter(_.name.toLowerCase == name.toLowerCase) + .map(ResolvedConstructor(currentModule, _)) + val matchingMethods = moduleMethods + .filter(_.name.toLowerCase == name.toLowerCase) + .map(ResolvedMethod(currentModule, _)) + matchingConses ++ matchingMethods + } + + /** + * Resolves a name exported by this module. + * + * @param name the name to resolve + * @return the resolution for `name` + */ + def resolveExportedName( + name: String + ): Either[ResolutionError, ResolvedName] = { + handleAmbiguity(findExportedSymbolsFor(name)) + } } object BindingsMap { @@ -102,6 +176,20 @@ object BindingsMap { */ case class Cons(name: String, arity: Int) + /** + * A representation of an imported polyglot symbol. + * + * @param name the name of the symbol. + */ + case class PolyglotSymbol(name: String) + + /** + * A representation of a method defined on the current module. + * + * @param name the name of the method. + */ + case class ModuleMethod(name: String) + /** * A result of successful name resolution. */ @@ -123,6 +211,22 @@ object BindingsMap { */ case class ResolvedModule(module: Module) extends ResolvedName + /** + * A representation of a name being resolved to a method call. + * @param module the module defining the method. + * @param method the method representation. + */ + case class ResolvedMethod(module: Module, method: ModuleMethod) + extends ResolvedName + + /** + * A representation of a name being resolved to a polyglot symbol. + * + * @param symbol the imported symbol name. + */ + case class ResolvedPolyglotSymbol(module: Module, symbol: PolyglotSymbol) + extends ResolvedName + /** * A representation of an error during name resolution. */ @@ -141,9 +245,8 @@ object BindingsMap { */ case object ResolutionNotFound extends ResolutionError - /** A metadata-friendly storage for resolutions */ - case class Resolution(target: ResolvedName) - extends IRPass.Metadata { + /** A metadata-friendly storage for resolutions */ + case class Resolution(target: ResolvedName) extends IRPass.Metadata { /** The name of the metadata as a string. */ override val metadataName: String = "Resolution" diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala index a97d7bf687..0fbafdf2c4 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/AliasAnalysis.scala @@ -554,7 +554,7 @@ case object AliasAnalysis extends IRPass { ): IR.Pattern = { pattern match { case named @ Pattern.Name(name, _, _, _) => - if (name.isReferant) { + if (name.isReferent) { throw new CompilerError( "Nested patterns should be desugared by the point of alias " + "analysis." @@ -666,8 +666,9 @@ case object AliasAnalysis extends IRPass { */ def copy: Graph = { val graph = new Graph - graph.links = links - graph.rootScope = rootScope.copy + graph.links = links + graph.rootScope = rootScope.copy + graph.nextIdCounter = nextIdCounter graph } diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/BindingAnalysis.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/BindingAnalysis.scala index 138bbb149e..cdbed29179 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/BindingAnalysis.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/analyse/BindingAnalysis.scala @@ -2,6 +2,7 @@ package org.enso.compiler.pass.analyse import org.enso.compiler.context.{InlineContext, ModuleContext} import org.enso.compiler.core.IR +import org.enso.compiler.core.IR.Module.Scope.Import.Polyglot import org.enso.compiler.core.ir.MetadataStorage.ToPair import org.enso.compiler.data.BindingsMap import org.enso.compiler.pass.IRPass @@ -48,9 +49,38 @@ case object BindingAnalysis extends IRPass { case cons: IR.Module.Scope.Definition.Atom => BindingsMap.Cons(cons.name.name, cons.arguments.length) } + val importedPolyglot = ir.imports.collect { + case poly: IR.Module.Scope.Import.Polyglot => + val sym = poly.entity match { + case Polyglot.Java(_, className) => className + } + BindingsMap.PolyglotSymbol(sym) + } + val moduleMethods = ir.bindings + .collect { + case method: IR.Module.Scope.Definition.Method.Explicit => + val ref = method.methodReference + ref.typePointer match { + case IR.Name.Qualified(List(), _, _, _) => Some(ref.methodName.name) + case IR.Name.Qualified(List(n), _, _, _) => + if (n.name == moduleContext.module.getName.item) + Some(ref.methodName.name) + else None + case IR.Name.Here(_, _, _) => Some(ref.methodName.name) + case IR.Name.Literal(n, _, _, _, _) => + if (n == moduleContext.module.getName.item) + Some(ref.methodName.name) + else None + case _ => None + } + } + .flatten + .map(BindingsMap.ModuleMethod) ir.updateMetadata( this -->> BindingsMap( definedConstructors, + importedPolyglot, + moduleMethods, moduleContext.module ) ) diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/ComplexType.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/ComplexType.scala index 6aa6fa28f6..99c037dcde 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/ComplexType.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/ComplexType.scala @@ -133,7 +133,7 @@ case object ComplexType extends IRPass { val sig = lastSignature match { case Some(IR.Type.Ascription(typed, _, _, _, _)) => typed match { - case IR.Name.Literal(nameStr, _, _, _) => + case IR.Name.Literal(nameStr, _, _, _, _) => if (name.name == nameStr) { lastSignature } else { diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/LambdaShorthandToLambda.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/LambdaShorthandToLambda.scala index e7262ffeaa..422e95bcdf 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/LambdaShorthandToLambda.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/LambdaShorthandToLambda.scala @@ -131,7 +131,7 @@ case object LambdaShorthandToLambda extends IRPass { IR.Function.Lambda( List( IR.DefinitionArgument.Specified( - IR.Name.Literal(newName.name, None), + IR.Name.Literal(newName.name, isReferent = false, None), None, suspended = false, None @@ -207,7 +207,8 @@ case object LambdaShorthandToLambda extends IRPass { IR.Function.Lambda( List( IR.DefinitionArgument.Specified( - IR.Name.Literal(updatedName.get, fn.location), + IR.Name + .Literal(updatedName.get, isReferent = false, fn.location), None, suspended = false, None @@ -320,7 +321,11 @@ case object LambdaShorthandToLambda extends IRPass { case IR.CallArgument.Specified(_, value, _, _, passData, diagnostics) => // Note [Safe Casting to IR.Name.Literal] val defArgName = - IR.Name.Literal(value.asInstanceOf[IR.Name.Literal].name, None) + IR.Name.Literal( + value.asInstanceOf[IR.Name.Literal].name, + isReferent = false, + None + ) Some( IR.DefinitionArgument.Specified( diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/IgnoredBindings.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/IgnoredBindings.scala index 103760131f..ad99abe6d4 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/IgnoredBindings.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/IgnoredBindings.scala @@ -249,9 +249,9 @@ case object IgnoredBindings extends IRPass { */ def isIgnore(ir: IR.Name): Boolean = { ir match { - case _: IR.Name.Blank => true - case IR.Name.Literal(name, _, _, _) => name == "_" - case _ => false + case _: IR.Name.Blank => true + case IR.Name.Literal(name, _, _, _, _) => name == "_" + case _ => false } } diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/MethodDefinitions.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/MethodDefinitions.scala index e98671bbaa..55d58c017e 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/MethodDefinitions.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/MethodDefinitions.scala @@ -55,12 +55,33 @@ case object MethodDefinitions extends IRPass { BindingsMap.ResolvedModule(availableSymbolsMap.currentModule) ) ) - case tp @ IR.Name.Qualified(List(item), _, _, _) => - availableSymbolsMap.resolveUppercaseName(item.name) match { + case tp @ IR.Name.Qualified(names, _, _, _) => + val items = names.map(_.name) + availableSymbolsMap.resolveQualifiedName(items) match { case Left(err) => - IR.Error.Resolution(tp, IR.Error.Resolution.Reason(err)) - case Right(candidate) => - tp.updateMetadata(this -->> BindingsMap.Resolution(candidate)) + IR.Error.Resolution(tp, IR.Error.Resolution.ResolverError(err)) + case Right(value: BindingsMap.ResolvedConstructor) => + tp.updateMetadata( + this -->> BindingsMap.Resolution(value) + ) + case Right(value: BindingsMap.ResolvedModule) => + tp.updateMetadata( + this -->> BindingsMap.Resolution(value) + ) + case Right(_: BindingsMap.ResolvedPolyglotSymbol) => + IR.Error.Resolution( + tp, + IR.Error.Resolution.UnexpectedPolyglot( + "a method definition target" + ) + ) + case Right(_: BindingsMap.ResolvedMethod) => + IR.Error.Resolution( + tp, + IR.Error.Resolution.UnexpectedMethod( + "a method definition target" + ) + ) } case tp: IR.Error.Resolution => tp case _ => diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/Patterns.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/Patterns.scala index e72300a407..56da23aa9e 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/Patterns.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/Patterns.scala @@ -4,6 +4,7 @@ import org.enso.compiler.context.{InlineContext, ModuleContext} import org.enso.compiler.core.IR import org.enso.compiler.core.ir.MetadataStorage.ToPair import org.enso.compiler.data.BindingsMap +import org.enso.compiler.exception.CompilerError import org.enso.compiler.pass.IRPass import org.enso.compiler.pass.analyse.{AliasAnalysis, BindingAnalysis} import org.enso.compiler.pass.desugar.{GenerateMethodBodies, NestedPatternMatch} @@ -69,27 +70,60 @@ object Patterns extends IRPass { val newBranches = caseExpr.branches.map { branch => val resolvedPattern = branch.pattern match { case consPat: IR.Pattern.Constructor => - val resolvedName = consPat.constructor match { + val consName = consPat.constructor + val resolution = consName match { + case qual: IR.Name.Qualified => + val parts = qual.parts.map(_.name) + Some(bindings.resolveQualifiedName(parts)) case lit: IR.Name.Literal => - val resolution = bindings.resolveUppercaseName(lit.name) - resolution match { - case Left(err) => - IR.Error.Resolution( - consPat.constructor, - IR.Error.Resolution.Reason(err) - ) - case Right(value) => - lit.updateMetadata( - this -->> BindingsMap.Resolution(value) - ) - } - case other => other + Some(bindings.resolveUppercaseName(lit.name)) + case _ => None } - val resolution = resolvedName.getMetadata(this) - val expectedArity = resolution.map { res => + val resolvedName = resolution + .map { + case Left(err) => + IR.Error.Resolution( + consPat.constructor, + IR.Error.Resolution.ResolverError(err) + ) + case Right(value: BindingsMap.ResolvedConstructor) => + consName.updateMetadata( + this -->> BindingsMap.Resolution(value) + ) + case Right(value: BindingsMap.ResolvedModule) => + consName.updateMetadata( + this -->> BindingsMap.Resolution(value) + ) + case Right(_: BindingsMap.ResolvedPolyglotSymbol) => + IR.Error.Resolution( + consName, + IR.Error.Resolution.UnexpectedPolyglot( + "a pattern match" + ) + ) + case Right(_: BindingsMap.ResolvedMethod) => + IR.Error.Resolution( + consName, + IR.Error.Resolution.UnexpectedMethod( + "a pattern match" + ) + ) + } + .getOrElse(consName) + + val actualResolution = resolvedName.getMetadata(this) + val expectedArity = actualResolution.map { res => res.target match { case BindingsMap.ResolvedConstructor(_, cons) => cons.arity case BindingsMap.ResolvedModule(_) => 0 + case BindingsMap.ResolvedPolyglotSymbol(_, _) => + throw new CompilerError( + "Impossible, should be transformed into an error before." + ) + case BindingsMap.ResolvedMethod(_, _) => + throw new CompilerError( + "Impossible, should be transformed into an error before." + ) } } expectedArity match { diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/SuspendedArguments.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/SuspendedArguments.scala index 07d89b2461..5b6cc74523 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/SuspendedArguments.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/SuspendedArguments.scala @@ -189,7 +189,7 @@ case object SuspendedArguments extends IRPass { signature match { case IR.Application.Operator.Binary( l, - IR.Name.Literal("->", _, _, _), + IR.Name.Literal("->", _, _, _, _), r, _, _, @@ -210,8 +210,8 @@ case object SuspendedArguments extends IRPass { */ def representsSuspended(value: IR.Expression): Boolean = { value match { - case IR.Name.Literal("Suspended", _, _, _) => true - case _ => false + case IR.Name.Literal("Suspended", _, _, _, _) => true + case _ => false } } diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/UppercaseNames.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/UppercaseNames.scala new file mode 100644 index 0000000000..ae68cc72c0 --- /dev/null +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/UppercaseNames.scala @@ -0,0 +1,280 @@ +package org.enso.compiler.pass.resolve + +import org.enso.compiler.context.{FreshNameSupply, InlineContext, ModuleContext} +import org.enso.compiler.core.IR +import org.enso.compiler.core.ir.MetadataStorage.ToPair +import org.enso.compiler.data.BindingsMap +import org.enso.compiler.data.BindingsMap.{ + Resolution, + ResolvedConstructor, + ResolvedMethod +} +import org.enso.compiler.exception.CompilerError +import org.enso.compiler.pass.IRPass +import org.enso.compiler.pass.analyse.{AliasAnalysis, BindingAnalysis} +import org.enso.interpreter.Constants + +/** + * Resolves and desugars referent name occurences in non-pattern contexts. + * + * 1. Attaches resolution metadata to encountered constructors, modules, + * and polygot symbols. + * 2. Desugars encountered method references into proper applications. + * 3. Resolves qualified calls to constructors, i.e. a call of the form + * `KnownModule.consName a b c` is transformed into `KnownCons a b c`, + * if `consName` refers to a constructor and `KnownModule` was successfully + * resolved to a module. + */ +case object UppercaseNames extends IRPass { + + /** The type of the metadata object that the pass writes to the IR. */ + override type Metadata = BindingsMap.Resolution + + /** The type of configuration for the pass. */ + override type Config = IRPass.Configuration.Default + + /** The passes that this pass depends _directly_ on to run. */ + override val precursorPasses: Seq[IRPass] = + Seq(AliasAnalysis, BindingAnalysis) + + /** The passes that are invalidated by running this pass. */ + override val invalidatedPasses: Seq[IRPass] = Seq(AliasAnalysis) + + /** Executes the pass on the provided `ir`, and returns a possibly transformed + * or annotated version of `ir`. + * + * @param ir the Enso IR to process + * @param moduleContext a context object that contains the information needed + * to process a module + * @return `ir`, possibly having made transformations or annotations to that + * IR. + */ + override def runModule( + ir: IR.Module, + moduleContext: ModuleContext + ): IR.Module = { + val scopeMap = ir.unsafeGetMetadata( + BindingAnalysis, + "No binding analysis on the module" + ) + val freshNameSupply = moduleContext.freshNameSupply.getOrElse( + throw new CompilerError( + "No fresh name supply passed to UppercaseNames resolver." + ) + ) + ir.mapExpressions(processExpression(_, scopeMap, freshNameSupply)) + } + + /** Executes the pass on the provided `ir`, and returns a possibly transformed + * or annotated version of `ir` in an inline context. + * + * @param ir the Enso IR to process + * @param inlineContext a context object that contains the information needed + * for inline evaluation + * @return `ir`, possibly having made transformations or annotations to that + * IR. + */ + override def runExpression( + ir: IR.Expression, + inlineContext: InlineContext + ): IR.Expression = { + val scopeMap = inlineContext.module.getIr.unsafeGetMetadata( + BindingAnalysis, + "No binding analysis on the module" + ) + val freshNameSupply = inlineContext.freshNameSupply.getOrElse( + throw new CompilerError( + "No fresh name supply passed to UppercaseNames resolver." + ) + ) + processExpression(ir, scopeMap, freshNameSupply) + } + + private def processExpression( + ir: IR.Expression, + bindings: BindingsMap, + freshNameSupply: FreshNameSupply, + isInsideApplication: Boolean = false + ): IR.Expression = + ir.transformExpressions { + case lit: IR.Name.Literal => + if (lit.isReferent && !isLocalVar(lit)) { + val resolution = bindings.resolveUppercaseName(lit.name) + resolution match { + case Left(error) => + IR.Error.Resolution( + lit, + IR.Error.Resolution.ResolverError(error) + ) + case Right(r @ BindingsMap.ResolvedMethod(mod, method)) => + if (isInsideApplication) { + lit.updateMetadata(this -->> BindingsMap.Resolution(r)) + + } else { + val self = freshNameSupply + .newName(isReferent = true) + .updateMetadata( + this -->> BindingsMap.Resolution( + BindingsMap.ResolvedModule(mod) + ) + ) + val fun = lit.copy(name = method.name) + val app = IR.Application.Prefix( + fun, + List(IR.CallArgument.Specified(None, self, None)), + hasDefaultsSuspended = false, + None + ) + app + } + case Right(value) => + lit.updateMetadata(this -->> BindingsMap.Resolution(value)) + } + + } else { lit } + case app: IR.Application.Prefix => + app.function match { + case n: IR.Name.Literal => + if (n.isReferent) + resolveReferantApplication(app, bindings, freshNameSupply) + else resolveLocalApplication(app, bindings, freshNameSupply) + case _ => + app.mapExpressions(processExpression(_, bindings, freshNameSupply)) + + } + + } + + private def resolveReferantApplication( + app: IR.Application.Prefix, + bindingsMap: BindingsMap, + freshNameSupply: FreshNameSupply + ): IR.Expression = { + val processedFun = processExpression( + app.function, + bindingsMap, + freshNameSupply, + isInsideApplication = true + ) + val processedArgs = app.arguments.map( + _.mapExpressions(processExpression(_, bindingsMap, freshNameSupply)) + ) + processedFun.getMetadata(this) match { + case Some(Resolution(ResolvedMethod(mod, method))) => + val self = freshNameSupply + .newName(isReferent = true) + .updateMetadata( + this -->> BindingsMap.Resolution( + BindingsMap.ResolvedModule(mod) + ) + ) + val selfArg = IR.CallArgument.Specified(None, self, None) + processedFun.passData.remove(this) + val renamed = rename(processedFun, method.name) + app.copy(function = renamed, arguments = selfArg :: processedArgs) + case _ => app.copy(function = processedFun, arguments = processedArgs) + } + } + + private def rename(name: IR.Expression, newName: String): IR.Expression = + name match { + case lit: IR.Name.Literal => lit.copy(name = newName) + case _ => name + } + + private def resolveLocalApplication( + app: IR.Application.Prefix, + bindings: BindingsMap, + freshNameSupply: FreshNameSupply + ): IR.Expression = { + val processedFun = + processExpression(app.function, bindings, freshNameSupply) + val processedArgs = + app.arguments.map( + _.mapExpressions(processExpression(_, bindings, freshNameSupply)) + ) + val newApp: Option[IR.Expression] = for { + thisArgPos <- findThisPosition(processedArgs) + thisArg = processedArgs(thisArgPos) + thisArgResolution <- thisArg.value.getMetadata(this) + funAsVar <- asGlobalVar(processedFun) + cons <- resolveToCons(thisArgResolution, funAsVar) + newFun = + buildSymbolFor(cons, freshNameSupply).setLocation(funAsVar.location) + newArgs = processedArgs.patch(thisArgPos, Nil, 1) + } yield buildConsApplication(app, cons.cons, newFun, newArgs) + newApp.getOrElse( + app.copy(function = processedFun, arguments = processedArgs) + ) + } + + private def buildConsApplication( + originalApp: IR.Application.Prefix, + calledCons: BindingsMap.Cons, + newFun: IR.Expression, + newArgs: List[IR.CallArgument] + ): IR.Expression = { + if ( + newArgs.isEmpty && (!originalApp.hasDefaultsSuspended || calledCons.arity == 0) + ) { + newFun + } else { + originalApp.copy(function = newFun, arguments = newArgs) + } + } + + private def buildSymbolFor( + cons: BindingsMap.ResolvedConstructor, + freshNameSupply: FreshNameSupply + ): IR.Expression = { + freshNameSupply + .newName(isReferent = true) + .updateMetadata(this -->> BindingsMap.Resolution(cons)) + } + + private def resolveToCons( + thisResolution: BindingsMap.Resolution, + consName: IR.Name.Literal + ): Option[BindingsMap.ResolvedConstructor] = + thisResolution.target match { + case BindingsMap.ResolvedModule(module) => + val resolution = module.getIr + .unsafeGetMetadata( + BindingAnalysis, + "Imported module without bindings analysis results" + ) + .resolveExportedName(consName.name) + resolution match { + case Right(cons @ ResolvedConstructor(_, _)) => Some(cons) + case _ => None + } + case _ => None + } + + private def findThisPosition(args: List[IR.CallArgument]): Option[Int] = { + val ix = args.indexWhere(arg => + arg.name.exists( + _.name == Constants.Names.THIS_ARGUMENT + ) || arg.name.isEmpty + ) + if (ix == -1) None else Some(ix) + } + + private def asGlobalVar(ir: IR): Option[IR.Name.Literal] = + ir match { + case name: IR.Name.Literal => + if (isLocalVar(name) || name.isReferent) None else Some(name) + case _ => None + } + + private def isLocalVar(name: IR.Name.Literal): Boolean = { + val aliasInfo = name + .unsafeGetMetadata( + AliasAnalysis, + "no alias analysis info on a name" + ) + .unsafeAs[AliasAnalysis.Info.Occurrence] + val defLink = aliasInfo.graph.defLinkFor(aliasInfo.id) + defLink.isDefined + } +} diff --git a/engine/runtime/src/main/scala/org/enso/compiler/phase/StubIrBuilder.scala b/engine/runtime/src/main/scala/org/enso/compiler/phase/StubIrBuilder.scala index 0588e2d11e..a04c0015f2 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/phase/StubIrBuilder.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/phase/StubIrBuilder.scala @@ -21,14 +21,29 @@ object StubIrBuilder { * @return the built stub IR. */ def build(module: Module): IR.Module = { - val ir = IR.Module(List(), List(), None) + val ir = IR.Module(List(), List(), None) + val scope = module.getScope + val conses = scope.getConstructors.asScala + val consNames = conses.keys.map(_.toLowerCase()).toSet val definedConstructors: List[BindingsMap.Cons] = - module.getScope.getConstructors.asScala.toList.map { + conses.toList.map { case (name, cons) => BindingsMap.Cons(name, cons.getArity) } + val moduleMethods = Option(scope.getMethods.get(scope.getAssociatedType)) + .map(methods => + methods.asScala.keys + .filter(!consNames.contains(_)) + .map(name => BindingsMap.ModuleMethod(name)) + .toList + ) + .getOrElse(List()) + val polyglot = scope.getPolyglotSymbols.asScala.keys.toList + .map(BindingsMap.PolyglotSymbol) val meta = BindingsMap( definedConstructors, + polyglot, + moduleMethods, module ) ir.updateMetadata(BindingAnalysis -->> meta) diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/CompilerTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/CompilerTest.scala index 1e4f3e85eb..49615c085f 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/CompilerTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/CompilerTest.scala @@ -149,7 +149,7 @@ trait CompilerRunner { * @return an IR name representing the name `str` */ def nameFromString(str: String): IR.Name.Literal = { - IR.Name.Literal(str, None) + IR.Name.Literal(str, isReferent = false, None) } // === IR Testing Utils ===================================================== @@ -167,8 +167,11 @@ trait CompilerRunner { def asMethod: IR.Module.Scope.Definition.Method = { IR.Module.Scope.Definition.Method.Explicit( IR.Name.MethodReference( - IR.Name.Qualified(List(IR.Name.Literal("TestType", None)), None), - IR.Name.Literal("testMethod", None), + IR.Name.Qualified( + List(IR.Name.Literal("TestType", isReferent = true, None)), + None + ), + IR.Name.Literal("testMethod", isReferent = false, None), None ), ir, @@ -182,11 +185,11 @@ trait CompilerRunner { */ def asAtomDefaultArg: IR.Module.Scope.Definition.Atom = { IR.Module.Scope.Definition.Atom( - IR.Name.Literal("TestAtom", None), + IR.Name.Literal("TestAtom", isReferent = true, None), List( IR.DefinitionArgument .Specified( - IR.Name.Literal("arg", None), + IR.Name.Literal("arg", isReferent = false, None), Some(ir), suspended = false, None diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/codegen/AstToIrTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/codegen/AstToIrTest.scala index e10befa49b..2c586b3e99 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/codegen/AstToIrTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/codegen/AstToIrTest.scala @@ -320,7 +320,11 @@ class AstToIrTest extends CompilerTest with Inside { ir shouldBe an[IR.Application.Prefix] val fn = ir.asInstanceOf[IR.Application.Prefix] - fn.function shouldEqual IR.Name.Literal("negate", None) + fn.function shouldEqual IR.Name.Literal( + "negate", + isReferent = false, + None + ) val fooArg = fn.arguments.head.asInstanceOf[IR.CallArgument.Specified] fooArg.value shouldBe an[IR.Name.Literal] diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/BindingAnalysisTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/BindingAnalysisTest.scala index 8510dc190e..5ae01b9e2a 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/BindingAnalysisTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/BindingAnalysisTest.scala @@ -4,7 +4,7 @@ import org.enso.compiler.Passes import org.enso.compiler.context.{FreshNameSupply, ModuleContext} import org.enso.compiler.core.IR import org.enso.compiler.data.BindingsMap -import org.enso.compiler.data.BindingsMap.Cons +import org.enso.compiler.data.BindingsMap.{Cons, ModuleMethod, PolyglotSymbol} import org.enso.compiler.pass.analyse.BindingAnalysis import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager} import org.enso.compiler.test.CompilerTest @@ -50,18 +50,24 @@ class BindingAnalysisTest extends CompilerTest { val ir = """ + |polyglot java import foo.bar.baz.MyClass + | |type Foo a b c |type Bar |type Baz x y | |Baz.foo = 123 |Bar.baz = Baz 1 2 . foo + | + |foo = 123 |""".stripMargin.preprocessModule.analyse - "discover all atoms in a module" in { + "discover all atoms, methods, and polyglot symbols in a module" in { ir.getMetadata(BindingAnalysis) shouldEqual Some( BindingsMap( List(Cons("Foo", 3), Cons("Bar", 0), Cons("Baz", 2)), + List(PolyglotSymbol("MyClass")), + List(ModuleMethod("foo")), ctx.module ) ) diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/DataflowAnalysisTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/DataflowAnalysisTest.scala index 44bd84b4c0..7e51bbe6f4 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/DataflowAnalysisTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/DataflowAnalysisTest.scala @@ -303,7 +303,7 @@ class DataflowAnalysisTest extends CompilerTest { val printlnFn = printlnExpr.function.asInstanceOf[IR.Name.Literal] val printlnArgIO = printlnExpr.arguments.head.asInstanceOf[IR.CallArgument.Specified] - val printlnArgIOExpr = printlnArgIO.value.asInstanceOf[IR.Name.Literal] + val printlnArgIOExpr = printlnArgIO.value.asInstanceOf[IR.Error.Resolution] val printlnArgB = printlnExpr.arguments(1).asInstanceOf[IR.CallArgument.Specified] val printlnArgBExpr = printlnArgB.value.asInstanceOf[IR.Name.Literal] diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/GatherDiagnosticsTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/GatherDiagnosticsTest.scala index 4f180e7a11..fdf0b68d39 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/GatherDiagnosticsTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/analyse/GatherDiagnosticsTest.scala @@ -16,7 +16,7 @@ class GatherDiagnosticsTest extends CompilerTest { AST.Invalid.Unrecognized("@@"), IR.Error.Syntax.UnrecognizedToken ) - val plusOp = IR.Name.Literal("+", None) + val plusOp = IR.Name.Literal("+", isReferent = false, None) val plusApp = IR.Application.Prefix( plusOp, List( @@ -29,7 +29,7 @@ class GatherDiagnosticsTest extends CompilerTest { List( IR.DefinitionArgument .Specified( - IR.Name.Literal("bar", None), + IR.Name.Literal("bar", isReferent = false, None), None, suspended = false, None @@ -59,10 +59,10 @@ class GatherDiagnosticsTest extends CompilerTest { IR.Error.Syntax.UnexpectedExpression ) - val typeName = IR.Name.Literal("Foo", None) - val method1Name = IR.Name.Literal("bar", None) - val method2Name = IR.Name.Literal("baz", None) - val fooName = IR.Name.Literal("foo", None) + val typeName = IR.Name.Literal("Foo", isReferent = false, None) + val method1Name = IR.Name.Literal("bar", isReferent = false, None) + val method2Name = IR.Name.Literal("baz", isReferent = false, None) + val fooName = IR.Name.Literal("foo", isReferent = false, None) val method1Ref = IR.Name.MethodReference( diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/desugar/OperatorToFunctionTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/desugar/OperatorToFunctionTest.scala index 7d773a79ed..0933fab3bb 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/desugar/OperatorToFunctionTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/desugar/OperatorToFunctionTest.scala @@ -45,14 +45,14 @@ class OperatorToFunctionTest extends CompilerTest { // === The Tests ============================================================ "Operators" should { - val opName = IR.Name.Literal("=:=", None) + val opName = IR.Name.Literal("=:=", isReferent = false, None) val left = IR.Empty(None) val right = IR.Empty(None) val rightArg = IR.CallArgument.Specified(None, IR.Empty(None), None) val (operator, operatorFn) = genOprAndFn(opName, left, right) - val oprArg = IR.CallArgument.Specified(None, operator, None) + val oprArg = IR.CallArgument.Specified(None, operator, None) val oprFnArg = IR.CallArgument.Specified(None, operatorFn, None) "be translated to functions" in { @@ -76,7 +76,10 @@ class OperatorToFunctionTest extends CompilerTest { None ) - OperatorToFunction.runExpression(recursiveIR, ctx) shouldEqual recursiveIRResult + OperatorToFunction.runExpression( + recursiveIR, + ctx + ) shouldEqual recursiveIRResult } } } diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/optimise/ApplicationSaturationTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/optimise/ApplicationSaturationTest.scala index e4147af196..469f183e77 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/optimise/ApplicationSaturationTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/optimise/ApplicationSaturationTest.scala @@ -35,7 +35,7 @@ class ApplicationSaturationTest extends CompilerTest { val name = if (positional) { None } else { - Some(IR.Name.Literal("a", None)) + Some(IR.Name.Literal("a", isReferent = false, None)) } List.fill(n)(IR.CallArgument.Specified(name, IR.Empty(None), None)) @@ -83,7 +83,7 @@ class ApplicationSaturationTest extends CompilerTest { "Known applications" should { val plusFn = IR.Application .Prefix( - IR.Name.Literal("+", None), + IR.Name.Literal("+", isReferent = false, None), genNArgs(2), hasDefaultsSuspended = false, None @@ -93,7 +93,7 @@ class ApplicationSaturationTest extends CompilerTest { val bazFn = IR.Application .Prefix( - IR.Name.Literal("baz", None), + IR.Name.Literal("baz", isReferent = false, None), genNArgs(2), hasDefaultsSuspended = false, None @@ -103,7 +103,7 @@ class ApplicationSaturationTest extends CompilerTest { val fooFn = IR.Application .Prefix( - IR.Name.Literal("foo", None), + IR.Name.Literal("foo", isReferent = false, None), genNArgs(5), hasDefaultsSuspended = false, None @@ -113,8 +113,8 @@ class ApplicationSaturationTest extends CompilerTest { val fooFnByName = IR.Application .Prefix( - IR.Name.Literal("foo", None), - genNArgs(4, positional = false), + IR.Name.Literal("foo", isReferent = false, None), + genNArgs(4, positional = false), hasDefaultsSuspended = false, None ) @@ -159,7 +159,7 @@ class ApplicationSaturationTest extends CompilerTest { "Unknown applications" should { val unknownFn = IR.Application .Prefix( - IR.Name.Literal("unknown", None), + IR.Name.Literal("unknown", isReferent = false, None), genNArgs(10), hasDefaultsSuspended = false, None @@ -180,7 +180,7 @@ class ApplicationSaturationTest extends CompilerTest { val empty = IR.Empty(None) val knownPlus = IR.Application .Prefix( - IR.Name.Literal("+", None), + IR.Name.Literal("+", isReferent = false, None), genNArgs(2), hasDefaultsSuspended = false, None @@ -190,7 +190,7 @@ class ApplicationSaturationTest extends CompilerTest { val undersaturatedPlus = IR.Application .Prefix( - IR.Name.Literal("+", None), + IR.Name.Literal("+", isReferent = false, None), genNArgs(1), hasDefaultsSuspended = false, None @@ -200,7 +200,7 @@ class ApplicationSaturationTest extends CompilerTest { val oversaturatedPlus = IR.Application .Prefix( - IR.Name.Literal("+", None), + IR.Name.Literal("+", isReferent = false, None), genNArgs(3), hasDefaultsSuspended = false, None @@ -222,7 +222,7 @@ class ApplicationSaturationTest extends CompilerTest { def outerPlus(argExpr: IR.Expression): IR.Application.Prefix = { IR.Application .Prefix( - IR.Name.Literal("+", None), + IR.Name.Literal("+", isReferent = false, None), List( IR.CallArgument.Specified(None, argExpr, None), IR.CallArgument.Specified(None, empty, None) diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/optimise/LambdaConsolidateTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/optimise/LambdaConsolidateTest.scala index 5d7e12e5b9..f06f30239c 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/optimise/LambdaConsolidateTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/optimise/LambdaConsolidateTest.scala @@ -206,13 +206,13 @@ class LambdaConsolidateTest extends CompilerTest { List( IR.DefinitionArgument .Specified( - IR.Name.Literal("a", None), + IR.Name.Literal("a", isReferent = false, None), None, suspended = false, None ), IR.DefinitionArgument.Specified( - IR.Name.Literal("b", None), + IR.Name.Literal("b", isReferent = false, None), None, suspended = false, None @@ -221,13 +221,13 @@ class LambdaConsolidateTest extends CompilerTest { IR.Function.Lambda( List( IR.DefinitionArgument.Specified( - IR.Name.Literal("c", None), + IR.Name.Literal("c", isReferent = false, None), None, suspended = false, None ) ), - IR.Name.Literal("c", None), + IR.Name.Literal("c", isReferent = false, None), None ), None diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/UppercaseNamesTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/UppercaseNamesTest.scala new file mode 100644 index 0000000000..f3d5f0ea88 --- /dev/null +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/pass/resolve/UppercaseNamesTest.scala @@ -0,0 +1,131 @@ +package org.enso.compiler.test.pass.resolve + +import org.enso.compiler.Passes +import org.enso.compiler.context.{FreshNameSupply, ModuleContext} +import org.enso.compiler.core.IR +import org.enso.compiler.data.BindingsMap.{ + Cons, + Resolution, + ResolvedConstructor, + ResolvedModule +} +import org.enso.compiler.pass.resolve.UppercaseNames +import org.enso.compiler.pass.{PassConfiguration, PassGroup, PassManager} +import org.enso.compiler.test.CompilerTest + +class UppercaseNamesTest extends CompilerTest { + + // === Test Setup =========================================================== + + def mkModuleContext: ModuleContext = + buildModuleContext( + freshNameSupply = Some(new FreshNameSupply) + ) + + val passes = new Passes + + val precursorPasses: PassGroup = + passes.getPrecursors(UppercaseNames).get + + val passConfiguration: PassConfiguration = PassConfiguration() + + implicit val passManager: PassManager = + new PassManager(List(precursorPasses), passConfiguration) + + /** Adds an extension method to analyse an Enso module. + * + * @param ir the ir to analyse + */ + implicit class AnalyseModule(ir: IR.Module) { + + /** Performs tail call analysis on [[ir]]. + * + * @param context the module context in which analysis takes place + * @return [[ir]], with tail call analysis metadata attached + */ + def analyse(implicit context: ModuleContext) = { + UppercaseNames.runModule(ir, context) + } + } + + // === The Tests ============================================================ + + "Method definition resolution" should { + implicit val ctx: ModuleContext = mkModuleContext + + val code = """ + |main = + | x1 = My_Cons 1 2 3 + | x2 = Constant + | x3 = Add_One 1 + | x4 = Test_Module.My_Cons 1 2 3 + | x5 = Does_Not_Exist 32 + | 0 + | + |type My_Cons a b c + | + |constant = 2 + | + |add_one x = x + 1 + | + |""".stripMargin + val preIr = code.preprocessModule + ctx.module.unsafeSetIr(preIr) + + val ir = preIr.analyse + + val bodyExprs = ir + .bindings(0) + .asInstanceOf[IR.Module.Scope.Definition.Method.Explicit] + .body + .asInstanceOf[IR.Function.Lambda] + .body + .asInstanceOf[IR.Expression.Block] + .expressions + .map(expr => expr.asInstanceOf[IR.Expression.Binding].expression) + + "resolve visible constructors" in { + bodyExprs(0) + .asInstanceOf[IR.Application.Prefix] + .function + .getMetadata(UppercaseNames) shouldEqual Some( + Resolution(ResolvedConstructor(ctx.module, Cons("My_Cons", 3))) + ) + } + + "resolve uppercase method names to applications" in { + val expr = bodyExprs(1) + expr shouldBe an[IR.Application.Prefix] + val app = expr.asInstanceOf[IR.Application.Prefix] + app.function.asInstanceOf[IR.Name.Literal].name shouldEqual "constant" + app.arguments.length shouldEqual 1 + app.arguments(0).value.getMetadata(UppercaseNames) shouldEqual Some( + Resolution(ResolvedModule(ctx.module)) + ) + } + + "resolve uppercase method names in applications by adding the self argument" in { + val expr = bodyExprs(2) + expr shouldBe an[IR.Application.Prefix] + val app = expr.asInstanceOf[IR.Application.Prefix] + app.function.asInstanceOf[IR.Name.Literal].name shouldEqual "add_one" + app.arguments.length shouldEqual 2 + app.arguments(0).value.getMetadata(UppercaseNames) shouldEqual Some( + Resolution(ResolvedModule(ctx.module)) + ) + } + + "resolve qualified uses of constructors into a simplified form when possible" in { + val app = bodyExprs(3).asInstanceOf[IR.Application.Prefix] + app.arguments.length shouldBe 3 + app.function.getMetadata(UppercaseNames) shouldEqual Some( + Resolution(ResolvedConstructor(ctx.module, Cons("My_Cons", 3))) + ) + } + + "indicate resolution failures" in { + val app = bodyExprs(4).asInstanceOf[IR.Application.Prefix] + app.function shouldBe an[IR.Error.Resolution] + } + } +} diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/ConstructorsTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/ConstructorsTest.scala index ecabee9509..16fca6486a 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/ConstructorsTest.scala +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/ConstructorsTest.scala @@ -17,7 +17,7 @@ class ConstructorsTest extends InterpreterTest { val patternMatchingCode = """ |main = - | x = Cons 1 Nil + | x = Builtins.Cons 1 Nil | case x of | Cons h t -> h | Nil -> 0 diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/PatternMatchTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/PatternMatchTest.scala index 1f07b83df1..b25e3f9fa1 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/PatternMatchTest.scala +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/PatternMatchTest.scala @@ -19,10 +19,10 @@ class PatternMatchTest extends InterpreterTest { """ |main = | f = case _ of - | Cons a _ -> a - | Nil -> -10 + | Builtins.Cons a _ -> a + | Builtins.Nil -> -10 | - | (10.Cons Nil . f) - Nil.f + | (Builtins.Cons 10 Builtins.Nil . f) - Nil.f |""".stripMargin eval(code) shouldEqual 20 @@ -38,7 +38,7 @@ class PatternMatchTest extends InterpreterTest { | MyAtom a -> a | _ -> -100 | - | (50.MyAtom . f) + Nil.f + | (MyAtom 50 . f) + Nil.f |""".stripMargin eval(code) shouldEqual -50 @@ -54,7 +54,7 @@ class PatternMatchTest extends InterpreterTest { | MyAtom a -> a | a -> a + 5 | - | (50.MyAtom . f) + 30.f + | (MyAtom 50 . f) + 30.f |""".stripMargin eval(code) shouldEqual 85 diff --git a/project/WithDebugCommand.scala b/project/WithDebugCommand.scala index b60b6ae2a4..34aadda172 100644 --- a/project/WithDebugCommand.scala +++ b/project/WithDebugCommand.scala @@ -43,22 +43,26 @@ object WithDebugCommand { val printAssemblyOption = "--printAssembly" + val debuggerOption = "--debugger" + val argSeparator = "--" val commandName = "withDebug" val benchOnlyCommandName = "benchOnly" val runCommandName = "run" + val testOnlyCommandName = "testOnly" /** The main logic for parsing and transforming the debug flags into JVM level flags */ - def withDebug: Command = Command.args(commandName, "") { - (state, args) => + def withDebug: Command = + Command.args(commandName, "") { (state, args) => val (debugFlags, prefixedRunArgs) = args.span(_ != argSeparator) val runArgs = " " + prefixedRunArgs.drop(1).mkString(" ") val taskKey = if (debugFlags.contains(benchOnlyCommandName)) BenchTasks.benchOnly else if (debugFlags.contains(runCommandName)) Compile / Keys.run + else if (debugFlags.contains(testOnlyCommandName)) Test / Keys.testOnly else throw new IllegalArgumentException("Invalid command name.") val dumpGraphsOpts = @@ -72,11 +76,18 @@ object WithDebugCommand { if (debugFlags.contains(printAssemblyOption)) trufflePrintAssemblyOptions else Seq() + val debuggerOpts = + if (debugFlags.contains(debuggerOption)) + Seq( + "-agentlib:jdwp=transport=dt_socket,server=n,address=localhost:5005,suspend=y" + ) + else Seq() val javaOpts: Seq[String] = Seq( truffleNoBackgroundCompilationOptions, dumpGraphsOpts, showCompilationsOpts, - printAssemblyOpts + printAssemblyOpts, + debuggerOpts ).flatten val extracted = Project.extract(state) @@ -88,5 +99,5 @@ object WithDebugCommand { .extract(withJavaOpts) .runInputTask(taskKey, runArgs, withJavaOpts) state - } + } } diff --git a/test/Test/src/Main.enso b/test/Test/src/Main.enso index 1767d17f82..6611be4774 100644 --- a/test/Test/src/Main.enso +++ b/test/Test/src/Main.enso @@ -2,8 +2,10 @@ import Base.Test import Test.List_Spec import Test.Number_Spec import Test.Import_Loop_Spec +import Test.Names_Spec main = Suite.runMain <| List_Spec.spec Number_Spec.spec Import_Loop_Spec.spec + Names_Spec.spec diff --git a/test/Test/src/Names/Definitions.enso b/test/Test/src/Names/Definitions.enso new file mode 100644 index 0000000000..4411decd80 --- /dev/null +++ b/test/Test/src/Names/Definitions.enso @@ -0,0 +1,6 @@ +type Foo a b c + +Foo.sum = case this of + Foo a b c -> a + b + c + +another_constant = 10 diff --git a/test/Test/src/Names_Spec.enso b/test/Test/src/Names_Spec.enso new file mode 100644 index 0000000000..5ab5259db5 --- /dev/null +++ b/test/Test/src/Names_Spec.enso @@ -0,0 +1,39 @@ +import Test.Names.Definitions +import Base.Test + +Definitions.Foo.my_method = case this of + Definitions.Foo x y z -> x * y * z + +get_foo module = module.Foo + +constant = 1 + +add_one (x = 0) = x + 1 + +spec = + describe "Qualified Names" <| + it "should allow to call constructors in a qualified manner" <| + Definitions.Foo 1 2 3 . sum . should_equal 6 + Definitions . Foo 1 2 3 . sum . should_equal 6 + it "should allow pattern matching in a qualified manner" <| + v = Foo 1 2 3 + res = case v of + Definitions.Foo a b c -> a + b + c + res.should_equal 6 + it "should allow defining methods on qualified names" <| + v = Definitions.Foo 2 3 5 + v.my_method.should_equal 30 + it "should allow using constructors from value-bound modules" <| + v = here.get_foo Definitions 1 2 3 + v.sum.should_equal 6 + describe "Uppercase Methods" <| + it "should allow calling methods without a target, through uppercase resolution" <| + v = Constant + v.should_equal 1 + it "should allow calling methods that use defaulted arguments" <| + Add_One.should_equal 1 + Add_One 100 . should_equal 101 + it "should allow calling methods imported from another module" <| + ## TODO + This should only work with `all` import. + Another_Constant.should_equal 10