Qualified names & uppercase name resolution (#1062)

This commit is contained in:
Marcin Kostrzewa 2020-08-05 22:16:44 +02:00 committed by GitHub
parent c64298cb8e
commit ddb43af5a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 1063 additions and 162 deletions

View File

@ -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

View File

@ -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());
}

View File

@ -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 ->

View File

@ -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);
}
}

View File

@ -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)

View File

@ -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);

View File

@ -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<String, Function> 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<String, Object> getPolyglotSymbols() {
return polyglotSymbols;
}
public void reset() {
imports = new HashSet<>();
methods = new HashMap<>();

View File

@ -42,6 +42,8 @@ class Passes(passes: Option[List[PassGroup]] = None) {
TypeFunctions,
TypeSignatures,
AliasAnalysis,
UppercaseNames,
AliasAnalysis,
LambdaConsolidate,
AliasAnalysis,
SuspendedArguments,

View File

@ -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))
}

View File

@ -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 `<obj>.<fn-name> <args...>` where `<obj>` 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

View File

@ -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."

View File

@ -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"<internal-${numId}>", None)
private def mkName(numId: Long, isReferent: Boolean): IR.Name.Literal = {
val refMarker = if (isReferent) "ref" else ""
IR.Name.Literal(s"<internal-$refMarker-${numId}>", 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)
}
}

View File

@ -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 =>

View File

@ -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"

View File

@ -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
}

View File

@ -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
)
)

View File

@ -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 {

View File

@ -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(

View File

@ -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
}
}

View File

@ -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 _ =>

View File

@ -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 {

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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)

View File

@ -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

View File

@ -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]

View File

@ -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
)
)

View File

@ -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]

View File

@ -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(

View File

@ -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
}
}
}

View File

@ -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)

View File

@ -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

View File

@ -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]
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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, "<arguments>") {
(state, args) =>
def withDebug: Command =
Command.args(commandName, "<arguments>") { (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
}
}
}

View File

@ -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

View File

@ -0,0 +1,6 @@
type Foo a b c
Foo.sum = case this of
Foo a b c -> a + b + c
another_constant = 10

View File

@ -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