Extension methods can be exported by name (#10274)

Ultimately, we want to forbid the `from ... export all` syntax. This PR starts by providing a way to explicitly export extension and conversion methods by name.

Stdlib code will be modified in upcoming PR.

# Important Notes
A single name can refer to multiple extension or conversion methods. Exports are not qualified. For example,
```
type My_Type
type Other_Type
My_Type.ext_method x = x
Other_Type.ext_method x = x
```
```
from project.Mod export ext_method
```
will export both `My_Type.ext_method` and `Other_Type.ext_method`.
This commit is contained in:
Pavel Marek 2024-06-25 14:08:22 +02:00 committed by GitHub
parent 0c2630122d
commit 9010cf93be
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 1554 additions and 319 deletions

View File

@ -333,6 +333,7 @@ lazy val enso = (project in file("."))
`connected-lock-manager`, `connected-lock-manager`,
`connected-lock-manager-server`, `connected-lock-manager-server`,
testkit, testkit,
`test-utils`,
`common-polyglot-core-utils`, `common-polyglot-core-utils`,
`std-base`, `std-base`,
`std-database`, `std-database`,

View File

@ -100,15 +100,17 @@ binds the function name. This means that:
## Methods ## Methods
Enso makes a distinction between functions and methods. In Enso, a method is a Enso makes a distinction between functions and methods. In Enso, a method is a
function where the first argument (known as the `this` argument) is associated function where the first argument (known as the `self` argument) is associated
with a given atom. Methods are dispatched dynamically based on the type of the with a given atom. Methods are dispatched dynamically based on the type of the
`this` argument, while functions are not. `self` argument, while functions are not.
Methods can be defined in Enso in two ways: Methods can be defined in Enso in two ways:
1. **In the Body of a Type:** A function defined in the body of a `type` 1. **In the Body of a Type:** A function defined in the body of a `type`
definition is automatically converted to a method on all the atoms defined in definition is automatically converted to a method on all the atoms defined in
the body of that type definition. the body of that type definition. If the function has `self` parameter, it is
called an _instance method_. If the function does not have `self` parameter,
it is called a _static method_.
```ruby ```ruby
type Maybe a type Maybe a
@ -120,9 +122,9 @@ type Maybe a
Maybe.Just _ -> True Maybe.Just _ -> True
``` ```
2. **As an Extension Method:** A function defined _explicitly_ on an atom counts 2. **As an Extension Method:** A function defined _explicitly_ on a type counts
as an extension method on that atom. It can be defined on a typeset to apply as an extension method on that type. An _extension_ method can be _static_ or
to all the atoms within that typeset. _instance_, depending on whether the `self` argument is present or not.
```ruby ```ruby
Number.floor self = case self of Number.floor self = case self of
@ -130,11 +132,11 @@ Number.floor self = case self of
... ...
``` ```
3. **As a Function with an Explicit `this` Argument:** A function defined with 3. **As a Function with an Explicit `self` Argument:** A function defined with
the type of the `this` argument specified to be a type. the type of the `self` argument specified to be a type.
```ruby ```ruby
floor (this : Number) = case this of floor (self : Number) = case self of
Integer -> ... Integer -> ...
``` ```
@ -151,7 +153,8 @@ diagnostics, we distinguish between how functions and methods are called.
- To call a function `f` on arguments `a` and `b`, we write `f a b`. - To call a function `f` on arguments `a` and `b`, we write `f a b`.
- To call a method `f` defined on a type `A` (value `a`, here) on argument `b`, - To call a method `f` defined on a type `A` (value `a`, here) on argument `b`,
we write `a.f b`. we write `a.f b`. This instance method can also be called via a _static_
method with `A.f a b`, which is equivalent to `a.f b`.
## Code Blocks ## Code Blocks

View File

@ -29,7 +29,7 @@ import org.enso.compiler.core.ir.module.scope.definition.Method;
import org.enso.compiler.core.ir.module.scope.imports.Polyglot; import org.enso.compiler.core.ir.module.scope.imports.Polyglot;
import org.enso.compiler.data.BindingsMap; import org.enso.compiler.data.BindingsMap;
import org.enso.compiler.data.BindingsMap.ResolvedConstructor; import org.enso.compiler.data.BindingsMap.ResolvedConstructor;
import org.enso.compiler.data.BindingsMap.ResolvedMethod; import org.enso.compiler.data.BindingsMap.ResolvedModuleMethod;
import org.enso.compiler.data.BindingsMap.ResolvedPolyglotField; import org.enso.compiler.data.BindingsMap.ResolvedPolyglotField;
import org.enso.compiler.data.BindingsMap.ResolvedPolyglotSymbol; import org.enso.compiler.data.BindingsMap.ResolvedPolyglotSymbol;
import org.enso.compiler.data.BindingsMap.ResolvedType; import org.enso.compiler.data.BindingsMap.ResolvedType;
@ -513,9 +513,9 @@ public class IRDumper {
bldr.addLabelLine( bldr.addLabelLine(
"target: ResolvedConstructor(" + resolvedConstructor.cons().name() + ")"); "target: ResolvedConstructor(" + resolvedConstructor.cons().name() + ")");
} }
case ResolvedMethod resolvedMethod -> { case ResolvedModuleMethod resolvedModuleMethod -> {
bldr.addLabelLine( bldr.addLabelLine(
"target: ResolvedMethod(" + resolvedMethod.method().name() + ")"); "target: ResolvedMethod(" + resolvedModuleMethod.method().name() + ")");
} }
case ResolvedPolyglotField resolvedPolyglotField -> { case ResolvedPolyglotField resolvedPolyglotField -> {
bldr.addLabelLine( bldr.addLabelLine(
@ -563,7 +563,7 @@ public class IRDumper {
switch (entity) { switch (entity) {
case BindingsMap.Type tp -> bldr.addLabelLine(" - Type(" + tp.name() + ")"); case BindingsMap.Type tp -> bldr.addLabelLine(" - Type(" + tp.name() + ")");
case BindingsMap.ModuleMethod method -> bldr.addLabelLine( case BindingsMap.ModuleMethod method -> bldr.addLabelLine(
" - Method(" + method.name() + ")"); " - ModuleMethod(" + method.name() + ")");
case BindingsMap.PolyglotSymbol polySym -> bldr.addLabelLine( case BindingsMap.PolyglotSymbol polySym -> bldr.addLabelLine(
" - PolyglotSymbol(" + polySym.name() + ")"); " - PolyglotSymbol(" + polySym.name() + ")");
default -> throw unimpl(entity); default -> throw unimpl(entity);

View File

@ -125,16 +125,18 @@ public class PrivateSymbolsAnalysis implements IRPass {
private Pattern processCasePattern(Pattern pattern, BindingsMap bindingsMap) { private Pattern processCasePattern(Pattern pattern, BindingsMap bindingsMap) {
if (pattern instanceof Pattern.Constructor cons) { if (pattern instanceof Pattern.Constructor cons) {
var consName = cons.constructor(); var consName = cons.constructor();
var resolvedCons = tryResolveName(consName, bindingsMap); var resolvedNames = tryResolveName(consName, bindingsMap);
if (resolvedCons != null && isProjectPrivate(resolvedCons)) { for (var resolvedName : resolvedNames) {
var curProjName = getProjName(bindingsMap.currentModule().getName()); if (isProjectPrivate(resolvedName)) {
var resolvedProjName = getProjName(resolvedCons.module().getName()); var curProjName = getProjName(bindingsMap.currentModule().getName());
if (!curProjName.equals(resolvedProjName)) { var resolvedProjName = getProjName(resolvedName.module().getName());
var reason = if (!curProjName.equals(resolvedProjName)) {
new org.enso.compiler.core.ir.expression.errors.Pattern.PrivateConstructor( var reason =
consName.name(), curProjName, resolvedProjName); new org.enso.compiler.core.ir.expression.errors.Pattern.PrivateConstructor(
return new org.enso.compiler.core.ir.expression.errors.Pattern( consName.name(), curProjName, resolvedProjName);
cons, reason, cons.passData(), cons.diagnostics()); return new org.enso.compiler.core.ir.expression.errors.Pattern(
cons, reason, cons.passData(), cons.diagnostics());
}
} }
} }
} }
@ -142,16 +144,18 @@ public class PrivateSymbolsAnalysis implements IRPass {
} }
private Expression processName(Name name, BindingsMap bindingsMap) { private Expression processName(Name name, BindingsMap bindingsMap) {
var resolvedName = tryResolveName(name, bindingsMap); var resolvedNames = tryResolveName(name, bindingsMap);
if (resolvedName != null && isProjectPrivate(resolvedName)) { for (var resolvedName : resolvedNames) {
var curProjName = getProjName(bindingsMap.currentModule().getName()); if (isProjectPrivate(resolvedName)) {
var resolvedProjName = getProjName(resolvedName.module().getName()); var curProjName = getProjName(bindingsMap.currentModule().getName());
if (!curProjName.equals(resolvedProjName)) { var resolvedProjName = getProjName(resolvedName.module().getName());
var reason = if (!curProjName.equals(resolvedProjName)) {
new org.enso.compiler.core.ir.expression.errors.Resolution.PrivateEntity( var reason =
curProjName, resolvedProjName); new org.enso.compiler.core.ir.expression.errors.Resolution.PrivateEntity(
return new org.enso.compiler.core.ir.expression.errors.Resolution( curProjName, resolvedProjName);
name, reason, name.passData(), name.diagnostics()); return new org.enso.compiler.core.ir.expression.errors.Resolution(
name, reason, name.passData(), name.diagnostics());
}
} }
} }
return name.mapExpressions(e -> processExpression(e, bindingsMap)); return name.mapExpressions(e -> processExpression(e, bindingsMap));
@ -180,26 +184,28 @@ public class PrivateSymbolsAnalysis implements IRPass {
} }
} }
private ResolvedName tryResolveName(Name name, BindingsMap bindingsMap) { private List<ResolvedName> tryResolveName(Name name, BindingsMap bindingsMap) {
return switch (name) { return switch (name) {
case Name.Literal lit -> { case Name.Literal lit -> {
var resolved = bindingsMap.resolveName(lit.name()); var resolved = bindingsMap.resolveName(lit.name());
if (resolved.isRight()) { if (resolved.isRight()) {
yield (ResolvedName) resolved.getOrElse(() -> null); var resolvedNames = resolved.toOption().get();
yield CollectionConverters.asJava(resolvedNames);
} else { } else {
yield null; yield List.of();
} }
} }
case Name.Qualified qual -> { case Name.Qualified qual -> {
var nameParts = qual.parts().map(Name::name); var nameParts = qual.parts().map(Name::name);
var resolved = bindingsMap.resolveQualifiedName(nameParts); var resolved = bindingsMap.resolveQualifiedName(nameParts);
if (resolved.isRight()) { if (resolved.isRight()) {
yield (ResolvedName) resolved.getOrElse(() -> null); var resolvedNames = resolved.toOption().get();
yield CollectionConverters.asJava(resolvedNames);
} else { } else {
yield null; yield List.of();
} }
} }
default -> null; default -> List.of();
}; };
} }

View File

@ -18,6 +18,7 @@ import org.enso.pkg.QualifiedName
import java.io.ObjectOutputStream import java.io.ObjectOutputStream
import scala.annotation.unused import scala.annotation.unused
import scala.collection.mutable.ArrayBuffer
/** A utility structure for resolving symbols in a given module. /** A utility structure for resolving symbols in a given module.
* *
@ -174,15 +175,18 @@ case class BindingsMap(
}).map(_._1) }).map(_._1)
} }
/** Resolves a name in the context of current module. /** Resolves the symbol with the given name in the context of this import target.
* Note that it is valid to have multiple resolved names for a single symbol name,
* for example, if the symbol is a name of an extension method and there are multiple
* extension methods with the same name defined on multiple types.
* *
* @param name the name to resolve. * @param name (Unqualified) name of the symbol to resolve
* @return a resolution for `name` or an error, if the name could not be * @return A list of all resolutions for the given name or an error if no resolution
* resolved. * was found
*/ */
def resolveName( def resolveName(
name: String name: String
): Either[ResolutionError, ResolvedName] = { ): Either[ResolutionError, List[ResolvedName]] = {
val local = findLocalCandidates(name) val local = findLocalCandidates(name)
if (local.nonEmpty) { if (local.nonEmpty) {
return BindingsMap.handleAmbiguity(local) return BindingsMap.handleAmbiguity(local)
@ -200,14 +204,14 @@ case class BindingsMap(
scope: ResolvedName, scope: ResolvedName,
submoduleNames: List[String], submoduleNames: List[String],
finalItem: String finalItem: String
): Either[ResolutionError, ResolvedName] = scope match { ): Either[ResolutionError, List[ResolvedName]] = scope match {
case scoped: ImportTarget => case scoped: ImportTarget =>
var currentScope = scoped var currentScope = scoped
for (modName <- submoduleNames) { for (modName <- submoduleNames) {
val resolution = currentScope.resolveExportedSymbol(modName) val resolutions = currentScope.resolveExportedSymbol(modName)
resolution match { resolutions match {
case Left(err) => return Left(err) case Left(err) => return Left(err)
case Right(t: ImportTarget) => case Right(List(t: ImportTarget)) =>
currentScope = t currentScope = t
case _ => return Left(ResolutionNotFound) case _ => return Left(ResolutionNotFound)
} }
@ -215,7 +219,7 @@ case class BindingsMap(
currentScope.resolveExportedSymbol(finalItem) currentScope.resolveExportedSymbol(finalItem)
case s @ ResolvedPolyglotSymbol(_, _) => case s @ ResolvedPolyglotSymbol(_, _) =>
val found = s.findExportedSymbolFor(finalItem) val found = s.findExportedSymbolFor(finalItem)
Right(found) Right(List(found))
case _ => Left(ResolutionNotFound) case _ => Left(ResolutionNotFound)
} }
@ -223,37 +227,64 @@ case class BindingsMap(
* *
* @param name the name to resolve * @param name the name to resolve
* @return a resolution for `name` * @return a resolution for `name`
* @see [[resolveName]]
*/ */
def resolveQualifiedName( def resolveQualifiedName(
name: List[String] name: List[String]
): Either[ResolutionError, ResolvedName] = ): Either[ResolutionError, List[ResolvedName]] =
name match { name match {
case List() => Left(ResolutionNotFound) case List() => Left(ResolutionNotFound)
case List(item) => resolveName(item) case List(item) => resolveName(item)
case firstModuleName :: rest => case firstModuleName :: rest =>
resolveName(firstModuleName).flatMap { firstModule => val firstResolvedNamesOpt = resolveName(firstModuleName)
// This special handling is needed, because when resolving a local module name, we do not necessarily only look at _exported_ symbols, but overall locally defined symbols. firstResolvedNamesOpt match {
val isQualifiedLocalImport = case err @ Left(_) => err
firstModule == ResolvedModule(currentModule) case Right(firstResolvedNames) =>
if (isQualifiedLocalImport) { // This special handling is needed, because when resolving a local module name, we do not necessarily only look at _exported_ symbols, but overall locally defined symbols.
resolveLocalName(rest) val isQualifiedLocalImport =
} else { firstResolvedNames == List(ResolvedModule(currentModule))
val consName = rest.last if (isQualifiedLocalImport) {
val modNames = rest.init resolveLocalName(rest)
resolveQualifiedNameIn(firstModule, modNames, consName) } else {
} val consName = rest.last
val modNames = rest.init
val allResolvedNames: ArrayBuffer[ResolvedName] =
ArrayBuffer.empty
firstResolvedNames.foreach { firstResolvedName =>
val res =
resolveQualifiedNameIn(firstResolvedName, modNames, consName)
res match {
case Left(resolutionErr) => return Left(resolutionErr)
case Right(resolved) =>
allResolvedNames ++= resolved
}
}
Right(allResolvedNames.toList)
}
} }
} }
private def resolveLocalName( private def resolveLocalName(
name: List[String] name: List[String]
): Either[ResolutionError, ResolvedName] = name match { ): Either[ResolutionError, List[ResolvedName]] = name match {
case List() => Left(ResolutionNotFound) case List() => Left(ResolutionNotFound)
case List(singleItem) => case List(singleItem) =>
handleAmbiguity(findLocalCandidates(singleItem)) handleAmbiguity(findLocalCandidates(singleItem))
case firstName :: rest => case firstName :: rest =>
handleAmbiguity(findLocalCandidates(firstName)) handleAmbiguity(findLocalCandidates(firstName))
.flatMap(resolveQualifiedNameIn(_, rest.init, rest.last)) .flatMap(resolvedNames => {
val allResolvedNames: ArrayBuffer[ResolvedName] = ArrayBuffer.empty
resolvedNames.foreach { resolvedName =>
val res = resolveQualifiedNameIn(resolvedName, rest.init, rest.last)
res match {
case Left(resolutionErr) => return Left(resolutionErr)
case Right(resolved) =>
allResolvedNames ++= resolved
}
}
Right(allResolvedNames.toList)
})
} }
private def findExportedSymbolsFor( private def findExportedSymbolsFor(
@ -269,7 +300,7 @@ case class BindingsMap(
*/ */
def resolveExportedName( def resolveExportedName(
name: String name: String
): Either[ResolutionError, ResolvedName] = { ): Either[ResolutionError, List[ResolvedName]] = {
handleAmbiguity(findExportedSymbolsFor(name)) handleAmbiguity(findExportedSymbolsFor(name))
} }
@ -323,11 +354,25 @@ object BindingsMap {
private def handleAmbiguity( private def handleAmbiguity(
candidates: List[ResolvedName] candidates: List[ResolvedName]
): Either[ResolutionError, ResolvedName] = { ): Either[ResolutionError, List[ResolvedName]] = {
candidates.distinct match { candidates.distinct match {
case List() => Left(ResolutionNotFound) case List() => Left(ResolutionNotFound)
case List(it) => Right(it) case List(it) => Right(List(it))
case items => Left(ResolutionAmbiguous(items)) case items =>
val areAllResolvedMethods =
items.forall(_.isInstanceOf[ResolvedMethod])
if (areAllResolvedMethods) {
items
.map(_.asInstanceOf[ResolvedMethod])
.groupBy(_.methodName)
.values
.toList match {
case List(single) => Right(single)
case _ => Left(ResolutionAmbiguous(items))
}
} else {
Left(ResolutionAmbiguous(items))
}
} }
} }
@ -673,10 +718,20 @@ object BindingsMap {
override def toAbstract: ImportTarget override def toAbstract: ImportTarget
override def toConcrete(moduleMap: ModuleMap): Option[ImportTarget] override def toConcrete(moduleMap: ModuleMap): Option[ImportTarget]
def findExportedSymbolsFor(name: String): List[ResolvedName] def findExportedSymbolsFor(name: String): List[ResolvedName]
/** Resolves the symbol with the given name in the context of this import target.
* Note that it is valid to have multiple resolved names for a single symbol name,
* for example, if the symbol is a name of an extension method and there are multiple
* extension methods with the same name defined on multiple types.
*
* @param name (Unqualified) name of the symbol to resolve
* @see [[BindingsMap.resolveName()]]
*/
def resolveExportedSymbol( def resolveExportedSymbol(
name: String name: String
): Either[ResolutionError, ResolvedName] = ): Either[ResolutionError, List[ResolvedName]] =
BindingsMap.handleAmbiguity(findExportedSymbolsFor(name)) BindingsMap.handleAmbiguity(findExportedSymbolsFor(name))
def exportedSymbols: Map[String, List[ResolvedName]] def exportedSymbols: Map[String, List[ResolvedName]]
} }
@ -720,14 +775,21 @@ object BindingsMap {
sealed trait DefinedEntity { sealed trait DefinedEntity {
def name: String def name: String
def resolvedIn(module: ModuleReference): ResolvedName = this match { def resolvedIn(module: ModuleReference): ResolvedName = this match {
case t: Type => ResolvedType(module, t) case t: Type => ResolvedType(module, t)
case m: ModuleMethod => ResolvedMethod(module, m) case staticMethod: StaticMethod =>
ResolvedStaticMethod(module, staticMethod)
case conversionMethod: ConversionMethod =>
ResolvedConversionMethod(module, conversionMethod)
case m: ModuleMethod => ResolvedModuleMethod(module, m)
case p: PolyglotSymbol => ResolvedPolyglotSymbol(module, p) case p: PolyglotSymbol => ResolvedPolyglotSymbol(module, p)
} }
def resolvedIn(module: Module): ResolvedName = resolvedIn( def resolvedIn(module: Module): ResolvedName = resolvedIn(
ModuleReference.Concrete(module) ModuleReference.Concrete(module)
) )
// Determines if this entity can be exported during export resolution pass // Determines if this entity can be exported during export resolution pass
def canExport: Boolean def canExport: Boolean
} }
@ -811,12 +873,43 @@ object BindingsMap {
override def canExport: Boolean = false override def canExport: Boolean = false
} }
/** A representation of a method defined on the current module. sealed trait Method extends DefinedEntity {
override def canExport: Boolean = true
}
/** A representation of a method defined on the module, that is, a method
* that is not defined on any type, but directly on a module.
* *
* @param name the name of the method. * @param name the name of the method.
*/ */
case class ModuleMethod(override val name: String) extends DefinedEntity { case class ModuleMethod(override val name: String) extends Method {}
override def canExport: Boolean = true
/** Static or extension method. Note that from the perspective of the runtime, there is no difference
* between a static or an extension method. In the following snippet, both methods are considered
* a duplicate:
* ```
* type My_Type
* method = 42
* My_Type.method = 42
* ```
*/
case class StaticMethod(
methodName: String,
tpName: String
) extends Method {
override def name: String = methodName
}
case class ConversionMethod(
methodName: String,
sourceTpName: String,
targetTpName: String
) extends Method {
override def name: String = methodName
override def toString: String =
targetTpName + ".from (other:" + sourceTpName + ")"
} }
/** A name resolved to a sum type. /** A name resolved to a sum type.
@ -938,23 +1031,27 @@ object BindingsMap {
.exportedSymbols .exportedSymbols
} }
/** A representation of a name being resolved to a method call. sealed trait ResolvedMethod extends ResolvedName {
def methodName: String
}
/** A representation of a resolved method defined directly on module.
* *
* @param module the module defining the method. * @param module the module defining the method.
* @param method the method representation. * @param method the method representation.
*/ */
case class ResolvedMethod(module: ModuleReference, method: ModuleMethod) case class ResolvedModuleMethod(module: ModuleReference, method: ModuleMethod)
extends ResolvedName { extends ResolvedMethod {
/** @inheritdoc */ /** @inheritdoc */
override def toAbstract: ResolvedMethod = { override def toAbstract: ResolvedModuleMethod = {
this.copy(module = module.toAbstract) this.copy(module = module.toAbstract)
} }
/** @inheritdoc */ /** @inheritdoc */
override def toConcrete( override def toConcrete(
moduleMap: ModuleMap moduleMap: ModuleMap
): Option[ResolvedMethod] = { ): Option[ResolvedModuleMethod] = {
module.toConcrete(moduleMap).map(module => this.copy(module = module)) module.toConcrete(moduleMap).map(module => this.copy(module = module))
} }
@ -979,6 +1076,58 @@ object BindingsMap {
override def qualifiedName: QualifiedName = override def qualifiedName: QualifiedName =
module.getName.createChild(method.name) module.getName.createChild(method.name)
override def methodName: String = method.name
}
/** Method resolved on a type - either static method or extension method.
*/
case class ResolvedStaticMethod(
module: ModuleReference,
staticMethod: StaticMethod
) extends ResolvedMethod {
override def toAbstract: ResolvedStaticMethod = {
this.copy(module = module.toAbstract)
}
override def toConcrete(
moduleMap: ModuleMap
): Option[ResolvedStaticMethod] = {
module.toConcrete(moduleMap).map { module =>
this.copy(module = module)
}
}
override def qualifiedName: QualifiedName =
module.getName
.createChild(staticMethod.tpName)
.createChild(staticMethod.methodName)
override def methodName: String = staticMethod.methodName
}
case class ResolvedConversionMethod(
module: ModuleReference,
conversionMethod: ConversionMethod
) extends ResolvedMethod {
override def toAbstract: ResolvedConversionMethod = {
this.copy(module = module.toAbstract)
}
override def toConcrete(
moduleMap: ModuleMap
): Option[ResolvedConversionMethod] = {
module.toConcrete(moduleMap).map { module =>
this.copy(module = module)
}
}
override def qualifiedName: QualifiedName =
module.getName
.createChild(conversionMethod.targetTpName)
.createChild(conversionMethod.methodName)
override def methodName: String = conversionMethod.methodName
} }
/** A representation of a name being resolved to a polyglot symbol. /** A representation of a name being resolved to a polyglot symbol.
@ -1046,8 +1195,12 @@ object BindingsMap {
s" The imported polyglot symbol ${symbol.name};" s" The imported polyglot symbol ${symbol.name};"
case BindingsMap.ResolvedPolyglotField(_, name) => case BindingsMap.ResolvedPolyglotField(_, name) =>
s" The imported polyglot field ${name};" s" The imported polyglot field ${name};"
case BindingsMap.ResolvedMethod(module, symbol) => case BindingsMap.ResolvedModuleMethod(module, symbol) =>
s" The method ${symbol.name} defined in module ${module.getName}" s" The method ${symbol.name} defined in module ${module.getName}"
case BindingsMap.ResolvedStaticMethod(module, staticMethod) =>
s" The static method ${staticMethod.methodName} defined in module ${module.getName} for type ${staticMethod.tpName}"
case BindingsMap.ResolvedConversionMethod(module, conversionMethod) =>
s" The conversion method ${conversionMethod.targetTpName}.${conversionMethod.methodName} defined in module ${module.getName}"
case BindingsMap.ResolvedType(module, typ) => case BindingsMap.ResolvedType(module, typ) =>
s" Type ${typ.name} defined in module ${module.getName}" s" Type ${typ.name} defined in module ${module.getName}"
} }

View File

@ -8,6 +8,7 @@ import org.enso.compiler.core.ir.module.scope.Import
import org.enso.compiler.core.ir.module.scope.imports import org.enso.compiler.core.ir.module.scope.imports
import org.enso.compiler.data.BindingsMap import org.enso.compiler.data.BindingsMap
import org.enso.compiler.core.CompilerError import org.enso.compiler.core.CompilerError
import org.enso.compiler.data.BindingsMap.ResolvedName
import org.enso.compiler.pass.IRPass import org.enso.compiler.pass.IRPass
import scala.collection.mutable import scala.collection.mutable
@ -106,33 +107,34 @@ case object AmbiguousImportsAnalysis extends IRPass {
case Some(importTarget) => case Some(importTarget) =>
val encounteredErrors: ListBuffer[errors.ImportExport] = val encounteredErrors: ListBuffer[errors.ImportExport] =
ListBuffer() ListBuffer()
val imp = onlyNames.foreach { symbol =>
onlyNames.foldLeft(moduleImport: Import) { case (imp, symbol) => val symbolName = symbol.name
val symbolName = symbol.name importTarget.resolveExportedSymbol(symbolName) match {
importTarget.resolveExportedSymbol(symbolName) match { case Right(resolvedNames) =>
case Right(resolvedName) => resolvedNames.foreach { resolvedName =>
val symbolPath = resolvedName.qualifiedName.toString val symbolPath = resolvedName.qualifiedName.toString
tryAddEncounteredSymbol( tryAddEncounteredSymbol(
encounteredSymbols, encounteredSymbols,
imp, moduleImport,
symbolName, symbolName,
symbolPath symbolPath,
Some(resolvedName)
) match { ) match {
case Left(error) => case Left(error) =>
encounteredErrors += error encounteredErrors += error
imp case Right(_) => ()
case Right(imp) => imp
} }
case Left(resolutionError) => }
throw new CompilerError( case Left(resolutionError) =>
s"Unreachable: (should have been resolved in previous passes) $resolutionError" throw new CompilerError(
) s"Unreachable: (should have been resolved in previous passes) $resolutionError"
} )
} }
}
if (encounteredErrors.nonEmpty) { if (encounteredErrors.nonEmpty) {
Left(encounteredErrors.toList) Left(encounteredErrors.toList)
} else { } else {
Right(imp) Right(moduleImport)
} }
case None => case None =>
@ -164,32 +166,32 @@ case object AmbiguousImportsAnalysis extends IRPass {
} }
val encounteredErrors: ListBuffer[errors.ImportExport] = val encounteredErrors: ListBuffer[errors.ImportExport] =
ListBuffer() ListBuffer()
val imp = symbolsToIterate.foreach { symbolName =>
symbolsToIterate.foldLeft(moduleImport: Import) { importTarget.resolveExportedSymbol(symbolName) match {
case (imp, symbolName) => case Left(resolutionError) =>
importTarget.resolveExportedSymbol(symbolName) match { throw new CompilerError(
case Left(resolutionError) => s"Unreachable: (should have been resolved in previous passes) $resolutionError"
throw new CompilerError( )
s"Unreachable: (should have been resolved in previous passes) $resolutionError" case Right(List(resolvedName)) =>
) tryAddEncounteredSymbol(
case Right(resolvedName) => encounteredSymbols,
tryAddEncounteredSymbol( moduleImport,
encounteredSymbols, symbolName,
imp, resolvedName.qualifiedName.toString,
symbolName, Some(resolvedName)
resolvedName.qualifiedName.toString ) match {
) match { case Left(error) =>
case Left(error) => encounteredErrors += error
encounteredErrors += error case Right(_) => ()
imp
case Right(imp) => imp
}
} }
// If the symbolName is resolved to multiple objects, we ignore it.
case Right(_) => ()
} }
}
if (encounteredErrors.nonEmpty) { if (encounteredErrors.nonEmpty) {
Left(encounteredErrors.toList) Left(encounteredErrors.toList)
} else { } else {
Right(imp) Right(moduleImport)
} }
case None => case None =>
@ -213,7 +215,8 @@ case object AmbiguousImportsAnalysis extends IRPass {
encounteredSymbols, encounteredSymbols,
moduleImport, moduleImport,
rename.name, rename.name,
symbolPath symbolPath,
None
) match { ) match {
case Left(error) => Left(List(error)) case Left(error) => Left(List(error))
case Right(imp) => Right(imp) case Right(imp) => Right(imp)
@ -235,7 +238,8 @@ case object AmbiguousImportsAnalysis extends IRPass {
encounteredSymbols, encounteredSymbols,
moduleImport, moduleImport,
importPath.parts.last.name, importPath.parts.last.name,
importPath.name importPath.name,
None
) match { ) match {
case Left(err) => Left(List(err)) case Left(err) => Left(List(err))
case Right(imp) => Right(imp) case Right(imp) => Right(imp)
@ -252,7 +256,8 @@ case object AmbiguousImportsAnalysis extends IRPass {
encounteredSymbols, encounteredSymbols,
polyImport, polyImport,
symbolName, symbolName,
symbolPath symbolPath,
None
) match { ) match {
case Left(err) => Left(List(err)) case Left(err) => Left(List(err))
case Right(imp) => Right(imp) case Right(imp) => Right(imp)
@ -288,7 +293,8 @@ case object AmbiguousImportsAnalysis extends IRPass {
encounteredSymbols: EncounteredSymbols, encounteredSymbols: EncounteredSymbols,
currentImport: Import, currentImport: Import,
symbolName: String, symbolName: String,
symbolPath: String symbolPath: String,
resolvedName: Option[ResolvedName]
): Either[errors.ImportExport, Import] = { ): Either[errors.ImportExport, Import] = {
if (encounteredSymbols.containsSymbol(symbolName)) { if (encounteredSymbols.containsSymbol(symbolName)) {
val encounteredFullName = val encounteredFullName =
@ -304,18 +310,31 @@ case object AmbiguousImportsAnalysis extends IRPass {
) )
Right(currentImport.addDiagnostic(warn)) Right(currentImport.addDiagnostic(warn))
} else { } else {
Left( // The symbol was encountered before and the physical path is different.
createErrorForAmbiguousImport( val ambiguousImpErr = createErrorForAmbiguousImport(
originalImport, originalImport,
encounteredFullName, encounteredFullName,
currentImport, currentImport,
symbolName, symbolName,
symbolPath symbolPath
)
) )
encounteredSymbols.getResolvedNameForSymbol(symbolName) match {
case Some(resolvedMethod: BindingsMap.ResolvedMethod)
if resolvedMethod.methodName == symbolName =>
// This is a valid ambiguous case - in previously encountered import, the symbol was resolved
// to either an extension, static, or conversion method.
Right(currentImport)
case _ =>
Left(ambiguousImpErr)
}
} }
} else { } else {
encounteredSymbols.addSymbol(currentImport, symbolName, symbolPath) encounteredSymbols.addSymbol(
currentImport,
symbolName,
symbolPath,
resolvedName
)
Right(currentImport) Right(currentImport)
} }
} }
@ -350,14 +369,26 @@ case object AmbiguousImportsAnalysis extends IRPass {
) )
} }
/** @param symbolPath Fully qualified name of the symbol, i.e., its physical path.
* @param resolvedName The optinal resolved name of the symbol.
* @param originalImport The import IR from which the symbol was originally imported.
* i.e. the first encountered import IR that imports the symbol.
*/
private case class SymbolTarget(
symbolPath: String,
resolvedName: Option[ResolvedName],
originalImport: Import
)
/** For every encountered symbol name, we keep track of the original import from which it was imported, /** For every encountered symbol name, we keep track of the original import from which it was imported,
* along with the entity path. The entity path is vital to decide whether an imported symbol is duplicated * along with the entity path. The entity path is vital to decide whether an imported symbol is duplicated
* or ambiguous. * or ambiguous.
* Note that there are some exceptions that are allowed to be ambiguous, like extension methods.
*/ */
private class EncounteredSymbols( private class EncounteredSymbols(
private val encounteredSymbols: mutable.Map[ private val encounteredSymbols: mutable.Map[
String, String,
(Import, String) SymbolTarget
] = mutable.HashMap.empty ] = mutable.HashMap.empty
) { ) {
@ -370,9 +401,13 @@ case object AmbiguousImportsAnalysis extends IRPass {
def addSymbol( def addSymbol(
imp: Import, imp: Import,
symbol: String, symbol: String,
symbolPath: String symbolPath: String,
resolvedName: Option[ResolvedName]
): Unit = { ): Unit = {
encounteredSymbols.put(symbol, (imp, symbolPath)) encounteredSymbols.put(
symbol,
SymbolTarget(symbolPath, resolvedName, imp)
)
} }
/** Returns the entity path for the symbol. /** Returns the entity path for the symbol.
@ -381,8 +416,19 @@ case object AmbiguousImportsAnalysis extends IRPass {
symbol: String symbol: String
): String = { ): String = {
encounteredSymbols.get(symbol) match { encounteredSymbols.get(symbol) match {
case Some((_, fullName)) => case Some(symbolTarget) =>
fullName symbolTarget.symbolPath
case None =>
throw new IllegalStateException("unreachable")
}
}
def getResolvedNameForSymbol(
symbol: String
): Option[ResolvedName] = {
encounteredSymbols.get(symbol) match {
case Some(symbolTarget) =>
symbolTarget.resolvedName
case None => case None =>
throw new IllegalStateException("unreachable") throw new IllegalStateException("unreachable")
} }
@ -393,7 +439,7 @@ case object AmbiguousImportsAnalysis extends IRPass {
def getOriginalImportForSymbol( def getOriginalImportForSymbol(
symbol: String symbol: String
): Option[Import] = { ): Option[Import] = {
encounteredSymbols.get(symbol).map(_._1) encounteredSymbols.get(symbol).map(_.originalImport)
} }
} }
} }

View File

@ -12,7 +12,7 @@ import org.enso.compiler.core.ir.{
Name Name
} }
import org.enso.compiler.core.ir.module.scope.definition import org.enso.compiler.core.ir.module.scope.definition
import org.enso.compiler.data.BindingsMap.{Resolution, ResolvedMethod} import org.enso.compiler.data.BindingsMap.{Resolution, ResolvedModuleMethod}
import org.enso.compiler.core.CompilerError import org.enso.compiler.core.CompilerError
import org.enso.compiler.core.ir.expression.{Application, Operator} import org.enso.compiler.core.ir.expression.{Application, Operator}
import org.enso.compiler.pass.IRPass import org.enso.compiler.pass.IRPass
@ -486,7 +486,7 @@ object AutomaticParallelism extends IRPass {
// the called function. It is then sequenced with statuses of the // the called function. It is then sequenced with statuses of the
// arguments. // arguments.
app.function.getMetadata(MethodCalls) match { app.function.getMetadata(MethodCalls) match {
case Some(Resolution(method: ResolvedMethod)) => case Some(Resolution(method: ResolvedModuleMethod)) =>
val methodIr = method.unsafeGetIr("Invalid method call resolution.") val methodIr = method.unsafeGetIr("Invalid method call resolution.")
val isParallelize = methodIr val isParallelize = methodIr
.getMetadata(ModuleAnnotations) .getMetadata(ModuleAnnotations)

View File

@ -8,6 +8,11 @@ import org.enso.compiler.core.ir.module.scope.definition
import org.enso.compiler.core.ir.module.scope.imports import org.enso.compiler.core.ir.module.scope.imports
import org.enso.compiler.core.ir.MetadataStorage.MetadataPair import org.enso.compiler.core.ir.MetadataStorage.MetadataPair
import org.enso.compiler.data.BindingsMap import org.enso.compiler.data.BindingsMap
import org.enso.compiler.data.BindingsMap.{
ConversionMethod,
ModuleMethod,
StaticMethod
}
import org.enso.compiler.pass.IRPass import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.desugar.{ import org.enso.compiler.pass.desugar.{
ComplexType, ComplexType,
@ -60,33 +65,61 @@ case object BindingAnalysis extends IRPass {
val importedPolyglot = ir.imports.collect { case poly: imports.Polyglot => val importedPolyglot = ir.imports.collect { case poly: imports.Polyglot =>
BindingsMap.PolyglotSymbol(poly.getVisibleName) BindingsMap.PolyglotSymbol(poly.getVisibleName)
} }
val moduleMethods = ir.bindings val staticMethods: List[BindingsMap.Method] = ir.bindings.collect {
.collect { case method: definition.Method.Explicit => case method: definition.Method.Explicit =>
val ref = method.methodReference val ref = method.methodReference
ref.typePointer match { ref.typePointer match {
case Some(Name.Qualified(List(), _, _, _)) => case Some(Name.Qualified(List(), _, _, _)) =>
Some(ref.methodName.name) Some(ModuleMethod(ref.methodName.name))
case Some(Name.Qualified(List(n), _, _, _)) => case Some(Name.Qualified(List(n), _, _, _)) =>
val shadowed = definedSumTypes.exists(_.name == n.name) val shadowed = definedSumTypes.exists(_.name == n.name)
if (!shadowed && n.name == moduleContext.getName().item) if (!shadowed && n.name == moduleContext.getName().item)
Some(ref.methodName.name) Some(ModuleMethod(ref.methodName.name))
else None else {
Some(
StaticMethod(ref.methodName.name, n.name)
)
}
case Some(literal: Name.Literal) => case Some(literal: Name.Literal) =>
val shadowed = definedSumTypes.exists(_.name == literal.name) val shadowed = definedSumTypes.exists(_.name == literal.name)
if (!shadowed && literal.name == moduleContext.getName().item) if (!shadowed && literal.name == moduleContext.getName().item)
Some(ref.methodName.name) Some(ModuleMethod(ref.methodName.name))
else None else {
case None => Some(ref.methodName.name) Some(
StaticMethod(ref.methodName.name, literal.name)
)
}
case None => Some(ModuleMethod(ref.methodName.name))
case _ => None case _ => None
} }
} case conversion: definition.Method.Conversion =>
.flatten val targetTpNameOpt = conversion.typeName match {
.map(BindingsMap.ModuleMethod) case Some(targetTpName) =>
Some(targetTpName.name)
case None => None
}
val sourceTpNameOpt = conversion.sourceTypeName match {
case name: Name =>
Some(name.name)
case _ => None
}
(sourceTpNameOpt, targetTpNameOpt) match {
case (Some(sourceTpName), Some(targetTpName)) =>
Some(
ConversionMethod(
conversion.methodName.name,
sourceTpName,
targetTpName
)
)
case _ => None
}
}.flatten
ir.updateMetadata( ir.updateMetadata(
new MetadataPair( new MetadataPair(
this, this,
BindingsMap( BindingsMap(
definedSumTypes ++ importedPolyglot ++ moduleMethods, definedSumTypes ++ importedPolyglot ++ staticMethods,
moduleContext.moduleReference() moduleContext.moduleReference()
) )
) )

View File

@ -199,16 +199,22 @@ case object FullyQualifiedNames extends IRPass {
case tp: Definition.Type => case tp: Definition.Type =>
tp.copy(members = tp.copy(members =
tp.members.map( tp.members.map(
_.mapExpressions( _.mapExpressions(expr => {
val selfTypeResolution =
bindings.resolveName(tp.name.name) match {
case Right(List(resolvedName)) =>
Some(Resolution(resolvedName))
case _ => None
}
processExpression( processExpression(
_, expr,
bindings, bindings,
tp.params.map(_.name), tp.params.map(_.name),
freshNameSupply, freshNameSupply,
bindings.resolveName(tp.name.name).toOption.map(Resolution), selfTypeResolution,
pkgRepo pkgRepo
) )
) })
) )
) )

View File

@ -19,8 +19,8 @@ import org.enso.compiler.data.BindingsMap
import org.enso.compiler.data.BindingsMap.{ import org.enso.compiler.data.BindingsMap.{
Resolution, Resolution,
ResolutionNotFound, ResolutionNotFound,
ResolvedMethod, ResolvedModule,
ResolvedModule ResolvedModuleMethod
} }
import org.enso.compiler.core.CompilerError import org.enso.compiler.core.CompilerError
import org.enso.compiler.core.ConstantsNames import org.enso.compiler.core.ConstantsNames
@ -122,15 +122,21 @@ case object GlobalNames extends IRPass {
case tp: Definition.Type => case tp: Definition.Type =>
tp.copy(members = tp.copy(members =
tp.members.map( tp.members.map(
_.mapExpressions( _.mapExpressions { expr =>
val selfTypeResolution =
bindings.resolveName(tp.name.name) match {
case Right(List(resolvedName)) =>
Some(Resolution(resolvedName))
case _ => None
}
processExpression( processExpression(
_, expr,
bindings, bindings,
tp.params, tp.params,
freshNameSupply, freshNameSupply,
bindings.resolveName(tp.name.name).toOption.map(Resolution) selfTypeResolution
) )
) }
) )
) )
@ -181,10 +187,17 @@ case object GlobalNames extends IRPass {
lit, lit,
errors.Resolution.ResolverError(error) errors.Resolution.ResolverError(error)
) )
case Right(r @ BindingsMap.ResolvedMethod(mod, method)) => case Right(values)
if values.exists(_.isInstanceOf[ResolvedModuleMethod]) =>
val resolvedModuleMethod = values.collectFirst {
case r: ResolvedModuleMethod => r
}.get
if (isInsideApplication) { if (isInsideApplication) {
lit.updateMetadata( lit.updateMetadata(
new MetadataPair(this, BindingsMap.Resolution(r)) new MetadataPair(
this,
BindingsMap.Resolution(resolvedModuleMethod)
)
) )
} else { } else {
val self = freshNameSupply val self = freshNameSupply
@ -193,14 +206,15 @@ case object GlobalNames extends IRPass {
new MetadataPair( new MetadataPair(
this, this,
BindingsMap.Resolution( BindingsMap.Resolution(
BindingsMap.ResolvedModule(mod) BindingsMap
.ResolvedModule(resolvedModuleMethod.module)
) )
) )
) )
// The synthetic applications gets the location so that instrumentation // The synthetic applications gets the location so that instrumentation
// identifies the node correctly // identifies the node correctly
val fun = lit.copy( val fun = lit.copy(
name = method.name, name = resolvedModuleMethod.method.name,
location = None location = None
) )
val app = Application.Prefix( val app = Application.Prefix(
@ -222,9 +236,11 @@ case object GlobalNames extends IRPass {
fun.passData.remove(ExpressionAnnotations) fun.passData.remove(ExpressionAnnotations)
app app
} }
case Right(value) => case Right(values) =>
lit.updateMetadata( values.foldLeft(lit)((lit, value) =>
new MetadataPair(this, BindingsMap.Resolution(value)) lit.updateMetadata(
new MetadataPair(this, BindingsMap.Resolution(value))
)
) )
} }
@ -297,7 +313,7 @@ case object GlobalNames extends IRPass {
) )
) )
processedFun.getMetadata(this) match { processedFun.getMetadata(this) match {
case Some(Resolution(ResolvedMethod(mod, _))) if !isLocalVar(fun) => case Some(Resolution(ResolvedModuleMethod(mod, _))) if !isLocalVar(fun) =>
val self = freshNameSupply val self = freshNameSupply
.newName() .newName()
.updateMetadata( .updateMetadata(
@ -408,8 +424,8 @@ case object GlobalNames extends IRPass {
) )
.resolveExportedName(consName.name) .resolveExportedName(consName.name)
resolution match { resolution match {
case Right(res) => Some(res) case Right(List(res)) => Some(res)
case _ => None case _ => None
} }
case _ => None case _ => None
} }

View File

@ -7,7 +7,11 @@ import org.enso.compiler.core.ir.Name
import org.enso.compiler.core.ir.MetadataStorage.MetadataPair import org.enso.compiler.core.ir.MetadataStorage.MetadataPair
import org.enso.compiler.core.ir.expression.Application import org.enso.compiler.core.ir.expression.Application
import org.enso.compiler.data.BindingsMap import org.enso.compiler.data.BindingsMap
import org.enso.compiler.data.BindingsMap.{Resolution, ResolvedModule} import org.enso.compiler.data.BindingsMap.{
Resolution,
ResolvedModule,
ResolvedModuleMethod
}
import org.enso.compiler.pass.IRPass import org.enso.compiler.pass.IRPass
import org.enso.compiler.pass.analyse.BindingAnalysis import org.enso.compiler.pass.analyse.BindingAnalysis
@ -63,9 +67,9 @@ object MethodCalls extends IRPass {
app.function match { app.function match {
case name: Name if name.isMethod => case name: Name if name.isMethod =>
app.arguments match { app.arguments match {
case first :: _ => case selfArgument :: _ =>
val targetBindings = val targetBindings =
first.value.getMetadata(GlobalNames) match { selfArgument.value.getMetadata(GlobalNames) match {
case Some(Resolution(ResolvedModule(module))) => case Some(Resolution(ResolvedModule(module))) =>
val moduleIr = module.unsafeAsModule().getIr val moduleIr = module.unsafeAsModule().getIr
Option Option
@ -77,13 +81,23 @@ object MethodCalls extends IRPass {
} }
targetBindings match { targetBindings match {
case Some(bindings) => case Some(bindings) =>
val resolution = val resolutionsOpt =
bindings.exportedSymbols.get(name.name) bindings.exportedSymbols.get(name.name)
resolution match { val resolvedModuleMethodOpt = resolutionsOpt match {
case Some(List(resolution)) => case Some(resolutions) =>
resolutions.collectFirst { case x: ResolvedModuleMethod =>
x
}
case None => None
}
resolvedModuleMethodOpt match {
case Some(resolvedModuleMethod) =>
val newName = val newName =
name.updateMetadata( name.updateMetadata(
new MetadataPair(this, Resolution(resolution)) new MetadataPair(
this,
Resolution(resolvedModuleMethod)
)
) )
val newArgs = val newArgs =
app.arguments.map( app.arguments.map(

View File

@ -190,43 +190,60 @@ case object MethodDefinitions extends IRPass {
typePointer, typePointer,
errors.Resolution.ResolverError(err) errors.Resolution.ResolverError(err)
) )
case Right(_: BindingsMap.ResolvedConstructor) => case Right(resolvedItems) =>
errors.Resolution( assert(resolvedItems.size == 1, "Expected a single resolution")
typePointer, resolvedItems.head match {
errors.Resolution.UnexpectedConstructor( case _: BindingsMap.ResolvedConstructor =>
"a method definition target" errors.Resolution(
) typePointer,
) errors.Resolution.UnexpectedConstructor(
case Right(value: BindingsMap.ResolvedModule) => "a method definition target"
typePointer.updateMetadata( )
new MetadataPair(this, BindingsMap.Resolution(value)) )
) case value: BindingsMap.ResolvedModule =>
case Right(value: BindingsMap.ResolvedType) => typePointer.updateMetadata(
typePointer.updateMetadata( new MetadataPair(this, BindingsMap.Resolution(value))
new MetadataPair(this, BindingsMap.Resolution(value)) )
) case value: BindingsMap.ResolvedType =>
case Right(_: BindingsMap.ResolvedPolyglotSymbol) => typePointer.updateMetadata(
errors.Resolution( new MetadataPair(this, BindingsMap.Resolution(value))
typePointer, )
errors.Resolution.UnexpectedPolyglot( case _: BindingsMap.ResolvedPolyglotSymbol =>
"a method definition target" errors.Resolution(
) typePointer,
) errors.Resolution.UnexpectedPolyglot(
case Right(_: BindingsMap.ResolvedPolyglotField) => "a method definition target"
errors.Resolution( )
typePointer, )
errors.Resolution.UnexpectedPolyglot( case _: BindingsMap.ResolvedPolyglotField =>
"a method definition target" errors.Resolution(
) typePointer,
) errors.Resolution.UnexpectedPolyglot(
case Right(_: BindingsMap.ResolvedMethod) => "a method definition target"
errors.Resolution( )
typePointer, )
errors.Resolution.UnexpectedMethod( case _: BindingsMap.ResolvedModuleMethod =>
"a method definition target" errors.Resolution(
) typePointer,
) errors.Resolution.UnexpectedMethod(
"a method definition target"
)
)
case _: BindingsMap.ResolvedStaticMethod =>
errors.Resolution(
typePointer,
errors.Resolution.UnexpectedMethod(
"a static method definition target"
)
)
case _: BindingsMap.ResolvedConversionMethod =>
errors.Resolution(
typePointer,
errors.Resolution.UnexpectedMethod(
"a conversion method definition target"
)
)
}
} }
case tp: errors.Resolution => tp case tp: errors.Resolution => tp
case _ => case _ =>

View File

@ -78,6 +78,51 @@ object Patterns extends IRPass {
} }
} }
/** Just delegates to the same-named method from [[BindingsMap]]
* and expects a single resolution.
*/
private def resolveSingleQualifiedName(
bindingsMap: BindingsMap,
parts: List[String]
): Either[BindingsMap.ResolutionError, BindingsMap.ResolvedName] = {
bindingsMap.resolveQualifiedName(parts) match {
case Left(err) => Left(err)
case Right(resolvedNames) =>
assert(resolvedNames.size == 1, "Expected a single resolution")
Right(resolvedNames.head)
}
}
/** @inheritdoc [[resolveSingleQualifiedName]]
*/
private def resolveSingleQualifiedNameIn(
bindingsMap: BindingsMap,
scope: BindingsMap.ResolvedName,
submoduleNames: List[String],
finalItem: String
): Either[BindingsMap.ResolutionError, BindingsMap.ResolvedName] = {
bindingsMap.resolveQualifiedNameIn(scope, submoduleNames, finalItem) match {
case Left(err) => Left(err)
case Right(resolvedNames) =>
assert(resolvedNames.size == 1, "Expected a single resolution")
Right(resolvedNames.head)
}
}
/** @inheritdoc [[resolveSingleQualifiedName]]
*/
private def resolveSingleName(
bindingsMap: BindingsMap,
name: String
): Either[BindingsMap.ResolutionError, BindingsMap.ResolvedName] = {
bindingsMap.resolveName(name) match {
case Left(err) => Left(err)
case Right(resolvedNames) =>
assert(resolvedNames.size == 1, "Expected a single resolution")
Right(resolvedNames.head)
}
}
private def doExpression( private def doExpression(
expr: Expression, expr: Expression,
bindings: BindingsMap, bindings: BindingsMap,
@ -93,7 +138,8 @@ object Patterns extends IRPass {
qual.parts match { qual.parts match {
case (_: Name.SelfType) :: (others :+ item) => case (_: Name.SelfType) :: (others :+ item) =>
selfTypeResolution.map( selfTypeResolution.map(
bindings.resolveQualifiedNameIn( resolveSingleQualifiedNameIn(
bindings,
_, _,
others.map(_.name), others.map(_.name),
item.name item.name
@ -102,11 +148,11 @@ object Patterns extends IRPass {
case _ => case _ =>
val parts = qual.parts.map(_.name) val parts = qual.parts.map(_.name)
Some( Some(
bindings.resolveQualifiedName(parts) resolveSingleQualifiedName(bindings, parts)
) )
} }
case lit: Name.Literal => case lit: Name.Literal =>
Some(bindings.resolveName(lit.name)) Some(resolveSingleName(bindings, lit.name))
case _: Name.SelfType => case _: Name.SelfType =>
selfTypeResolution.map(Right(_)) selfTypeResolution.map(Right(_))
case _ => None case _ => None
@ -140,14 +186,34 @@ object Patterns extends IRPass {
new MetadataPair(this, BindingsMap.Resolution(value)) new MetadataPair(this, BindingsMap.Resolution(value))
) )
case Right(_: BindingsMap.ResolvedMethod) => case Right(_: BindingsMap.ResolvedModuleMethod) =>
val r = errors.Resolution( val r = errors.Resolution(
consName, consName,
errors.Resolution.UnexpectedMethod( errors.Resolution.UnexpectedMethod(
"a pattern match" "method inside pattern match"
) )
) )
r.setLocation(consName.location) r.setLocation(consName.location)
case Right(_: BindingsMap.ResolvedStaticMethod) =>
val r = errors.Resolution(
consName,
errors.Resolution.UnexpectedMethod(
"static method inside pattern match"
)
)
r.setLocation(consName.location)
case Right(_: BindingsMap.ResolvedConversionMethod) =>
val r = errors.Resolution(
consName,
errors.Resolution.UnexpectedMethod(
"conversion method inside pattern match"
)
)
r.setLocation(consName.location)
case Right(_) =>
throw new CompilerError(
"Impossible, should be transformed into an error before."
)
} }
.getOrElse(consName) .getOrElse(consName)
@ -158,7 +224,15 @@ object Patterns extends IRPass {
case BindingsMap.ResolvedModule(_) => 0 case BindingsMap.ResolvedModule(_) => 0
case BindingsMap.ResolvedPolyglotSymbol(_, _) => 0 case BindingsMap.ResolvedPolyglotSymbol(_, _) => 0
case BindingsMap.ResolvedPolyglotField(_, _) => 0 case BindingsMap.ResolvedPolyglotField(_, _) => 0
case BindingsMap.ResolvedMethod(_, _) => case BindingsMap.ResolvedModuleMethod(_, _) =>
throw new CompilerError(
"Impossible, should be transformed into an error before."
)
case BindingsMap.ResolvedStaticMethod(_, _) =>
throw new CompilerError(
"Impossible, should be transformed into an error before."
)
case BindingsMap.ResolvedConversionMethod(_, _) =>
throw new CompilerError( throw new CompilerError(
"Impossible, should be transformed into an error before." "Impossible, should be transformed into an error before."
) )
@ -186,10 +260,10 @@ object Patterns extends IRPass {
case qual: Name.Qualified => case qual: Name.Qualified =>
val parts = qual.parts.map(_.name) val parts = qual.parts.map(_.name)
Some( Some(
bindings.resolveQualifiedName(parts) resolveSingleQualifiedName(bindings, parts)
) )
case lit: Name.Literal => case lit: Name.Literal =>
Some(bindings.resolveName(lit.name)) Some(resolveSingleName(bindings, lit.name))
case _: Name.SelfType => case _: Name.SelfType =>
selfTypeResolution.map(Right(_)) selfTypeResolution.map(Right(_))
case _ => None case _ => None
@ -223,15 +297,31 @@ object Patterns extends IRPass {
tpeName, tpeName,
errors.Resolution.UnexpectedPolyglot(s"type pattern case") errors.Resolution.UnexpectedPolyglot(s"type pattern case")
)*/ )*/
case Right(_: BindingsMap.ResolvedMethod) => case Right(_: BindingsMap.ResolvedModuleMethod) =>
errors.Resolution( errors.Resolution(
tpeName, tpeName,
errors.Resolution.UnexpectedMethod(s"type pattern case") errors.Resolution
.UnexpectedMethod(s"method type pattern case")
)
case Right(_: BindingsMap.ResolvedStaticMethod) =>
errors.Resolution(
tpeName,
errors.Resolution.UnexpectedMethod(
s"static method inside type pattern case"
)
)
case Right(_: BindingsMap.ResolvedConversionMethod) =>
errors.Resolution(
tpeName,
errors.Resolution.UnexpectedMethod(
s"conversion method inside type pattern case"
)
) )
case Right(_: BindingsMap.ResolvedModule) => case Right(_: BindingsMap.ResolvedModule) =>
errors.Resolution( errors.Resolution(
tpeName, tpeName,
errors.Resolution.UnexpectedModule(s"type pattern case") errors.Resolution
.UnexpectedModule(s"module inside type pattern case")
) )
} }
.getOrElse(tpeName) .getOrElse(tpeName)

View File

@ -182,8 +182,10 @@ case object TypeNames extends IRPass {
bindingsMap.resolveQualifiedName(n.parts.map(_.name)) bindingsMap.resolveQualifiedName(n.parts.map(_.name))
) )
case selfRef: Name.SelfType => case selfRef: Name.SelfType =>
val resolvedSelfType = selfTypeInfo.selfType.toRight { val resolvedSelfType = selfTypeInfo.selfType match {
BindingsMap.SelfTypeOutsideOfTypeDefinition case None => Left(BindingsMap.SelfTypeOutsideOfTypeDefinition)
case Some(selfType) =>
Right(List(selfType))
} }
processResolvedName(selfRef, resolvedSelfType) processResolvedName(selfRef, resolvedSelfType)
case s: `type`.Set => case s: `type`.Set =>
@ -192,10 +194,17 @@ case object TypeNames extends IRPass {
private def processResolvedName( private def processResolvedName(
name: Name, name: Name,
resolvedName: Either[BindingsMap.ResolutionError, BindingsMap.ResolvedName] resolvedNamesOpt: Either[BindingsMap.ResolutionError, List[
BindingsMap.ResolvedName
]]
): Name = ): Name =
resolvedName resolvedNamesOpt
.map(res => name.updateMetadata(new MetadataPair(this, Resolution(res)))) .map(resolvedNames => {
resolvedNames.foreach { resolvedName =>
name.updateMetadata(new MetadataPair(this, Resolution(resolvedName)))
}
name
})
.fold( .fold(
error => error =>
errors.Resolution(name, errors.Resolution.ResolverError(error)), errors.Resolution(name, errors.Resolution.ResolverError(error)),

View File

@ -65,7 +65,7 @@ trait IRUtils {
.flatMap { symbol => .flatMap { symbol =>
symbol.getMetadata(MethodCalls).flatMap { resolution => symbol.getMetadata(MethodCalls).flatMap { resolution =>
resolution.target match { resolution.target match {
case BindingsMap.ResolvedMethod(module, _) case BindingsMap.ResolvedModuleMethod(module, _)
if module.getName == moduleName => if module.getName == moduleName =>
Some(symbol) Some(symbol)
case _ => case _ =>

View File

@ -0,0 +1,47 @@
package org.enso.compiler.passes;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import org.enso.compiler.core.ir.Module;
import org.enso.compiler.core.ir.Name;
import org.enso.compiler.core.ir.expression.Application;
import org.enso.compiler.data.BindingsMap;
import org.enso.compiler.pass.resolve.MethodCalls$;
import org.enso.test.utils.ContextUtils;
import org.junit.Test;
public class MethodCallsTest {
@Test
public void resolveSimpleModuleMethod() {
var code =
"""
module_method x = x
main =
Test.module_method 42
""";
var ctx = ContextUtils.createDefaultContext();
var ir = ContextUtils.compileModule(ctx, code, "Test");
var methodCall = findMethodCall(ir, "module_method");
var meta = methodCall.function().passData().get(MethodCalls$.MODULE$);
assertThat(meta.isDefined(), is(true));
var metaTarget = ((BindingsMap.Resolution) meta.get()).target();
assertThat(metaTarget, is(instanceOf(BindingsMap.ResolvedModuleMethod.class)));
}
private Application.Prefix findMethodCall(Module ir, String methodName) {
var res =
ir.preorder()
.find(
childIr -> {
if (childIr instanceof Application.Prefix app
&& app.function() instanceof Name.Literal lit) {
return lit.name().equals(methodName);
}
return false;
});
assertThat(res.isDefined(), is(true));
return (Application.Prefix) res.get();
}
}

View File

@ -36,6 +36,11 @@ public class ExtensionMethodResolutionTest {
containsString("Method overloads are not supported"), containsString("Method overloads are not supported"),
containsString("defined multiple times")); containsString("defined multiple times"));
private static final Matcher<String> ambiguousResolutionErrorMessageMatcher =
allOf(
containsString("resolved ambiguously to"),
containsString("The symbol was first resolved to"));
@Test @Test
public void twoExtensionMethodsWithSameNameInOneModuleShouldFail() throws IOException { public void twoExtensionMethodsWithSameNameInOneModuleShouldFail() throws IOException {
var src = """ var src = """
@ -330,7 +335,10 @@ public class ExtensionMethodResolutionTest {
topScope.compile(true); topScope.compile(true);
fail("Expected compilation error: " + out); fail("Expected compilation error: " + out);
} catch (PolyglotException e) { } catch (PolyglotException e) {
assertThat(e.isSyntaxError(), is(true)); assertThat(
"Exception should be a syntax error, but instead is " + e.getMessage(),
e.isSyntaxError(),
is(true));
assertThat(out.toString(), errorMessageMatcher); assertThat(out.toString(), errorMessageMatcher);
} }
} }

View File

@ -0,0 +1,153 @@
package org.enso.interpreter.test.exports;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasKey;
import static org.hamcrest.Matchers.is;
import java.io.IOException;
import java.util.Set;
import org.enso.compiler.data.BindingsMap.DefinedEntity;
import org.enso.pkg.QualifiedName;
import org.enso.polyglot.PolyglotContext;
import org.enso.polyglot.RuntimeOptions;
import org.enso.test.utils.ContextUtils;
import org.enso.test.utils.ModuleUtils;
import org.enso.test.utils.ProjectUtils;
import org.enso.test.utils.SourceModule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
public class ExportExtensionMethodTest {
@Rule public TemporaryFolder tempFolder = new TemporaryFolder();
@Test
public void extensionMethodCanBeExportedByName() throws IOException {
var tMod =
new SourceModule(
QualifiedName.fromString("T_Module"),
"""
type My_Type
Value x
My_Type.extension_method self = self.x
""");
var aMod =
new SourceModule(
QualifiedName.fromString("A_Module"),
"""
from project.T_Module export My_Type, extension_method
""");
var mainMod =
new SourceModule(
QualifiedName.fromString("Main"),
"""
from project.A_Module import all
main =
obj = My_Type.Value 42
obj.extension_method
""");
var projDir = tempFolder.newFolder().toPath();
ProjectUtils.createProject("Proj", Set.of(tMod, aMod, mainMod), projDir);
ProjectUtils.testProjectRun(
projDir,
res -> {
assertThat(res.isNumber(), is(true));
assertThat(res.asInt(), is(42));
});
}
@Test
public void multipleExtensionMethodsCanBeExportedByName() throws IOException {
var tMod =
new SourceModule(
QualifiedName.fromString("T_Module"),
"""
type My_Type
Value x
type My_Other_Type
Value y
My_Type.extension_method self = self.x
My_Other_Type.extension_method self = self.y
""");
var aMod =
new SourceModule(
QualifiedName.fromString("A_Module"),
"""
from project.T_Module export My_Type, My_Other_Type, extension_method
""");
var mainMod =
new SourceModule(
QualifiedName.fromString("Main"),
"""
from project.A_Module import all
main =
t = My_Type.Value 42
ot = My_Other_Type.Value 42
t.extension_method == ot.extension_method
""");
var projDir = tempFolder.newFolder().toPath();
ProjectUtils.createProject("Proj", Set.of(tMod, aMod, mainMod), projDir);
ProjectUtils.testProjectRun(
projDir,
res -> {
assertThat(res.isBoolean(), is(true));
assertThat(res.asBoolean(), is(true));
});
}
@Test
public void extensionMethodIsInBindingMap() throws IOException {
var tMod =
new SourceModule(
QualifiedName.fromString("T_Module"),
"""
type My_Type
Value x
My_Type.extension_method self = self.x
""");
var mainMod =
new SourceModule(
QualifiedName.fromString("Main"),
"""
from project.T_Module export My_Type, extension_method
""");
var projDir = tempFolder.newFolder().toPath();
ProjectUtils.createProject("Proj", Set.of(tMod, mainMod), projDir);
try (var ctx =
ContextUtils.defaultContextBuilder()
.option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString())
.build()) {
var polyCtx = new PolyglotContext(ctx);
polyCtx.getTopScope().compile(true);
var mainModExportedSymbols = ModuleUtils.getExportedSymbolsFromModule(ctx, "local.Proj.Main");
assertThat(mainModExportedSymbols, hasKey("extension_method"));
}
}
@Test
public void extensionMethodIsDefinedEntity() throws IOException {
var mainMod =
new SourceModule(
QualifiedName.fromString("Main"),
"""
type My_Type
Value x
My_Type.extension_method self = self.x
""");
var projDir = tempFolder.newFolder().toPath();
ProjectUtils.createProject("Proj", Set.of(mainMod), projDir);
try (var ctx =
ContextUtils.defaultContextBuilder()
.option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString())
.build()) {
var polyCtx = new PolyglotContext(ctx);
polyCtx.getTopScope().compile(true);
var definedEntities = ModuleUtils.getDefinedEntities(ctx, "local.Proj.Main");
assertThat(definedEntities.isEmpty(), is(false));
var entityNames = definedEntities.stream().map(DefinedEntity::name).toList();
assertThat(entityNames, hasItem("extension_method"));
}
}
}

View File

@ -0,0 +1,164 @@
package org.enso.interpreter.test.exports;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasKey;
import static org.hamcrest.Matchers.is;
import java.io.IOException;
import java.util.Set;
import org.enso.pkg.QualifiedName;
import org.enso.polyglot.PolyglotContext;
import org.enso.polyglot.RuntimeOptions;
import org.enso.test.utils.ContextUtils;
import org.enso.test.utils.ModuleUtils;
import org.enso.test.utils.ProjectUtils;
import org.enso.test.utils.SourceModule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
public class ExportStaticMethodTest {
@Rule public TemporaryFolder tempFolder = new TemporaryFolder();
@Test
public void staticMethodCanBeExportedByName() throws IOException {
var tMod =
new SourceModule(
QualifiedName.fromString("T_Module"), """
static_method x = x
""");
var aMod =
new SourceModule(
QualifiedName.fromString("A_Module"),
"""
from project.T_Module export static_method
""");
var mainMod =
new SourceModule(
QualifiedName.fromString("Main"),
"""
from project.A_Module import all
main =
static_method 42
""");
var projDir = tempFolder.newFolder().toPath();
ProjectUtils.createProject("Proj", Set.of(tMod, aMod, mainMod), projDir);
ProjectUtils.testProjectRun(
projDir,
res -> {
assertThat(res.isNumber(), is(true));
assertThat(res.asInt(), is(42));
});
}
@Test
public void staticAndModuleMethodsWithSameNameCanBeImported() throws IOException {
var tMod =
new SourceModule(
QualifiedName.fromString("T_Module"),
"""
type My_Type
method x = x
method x = x
""");
var mainMod =
new SourceModule(
QualifiedName.fromString("Main"),
"""
from project.T_Module import My_Type, method
main =
My_Type.method 42 == method 42
""");
var projDir = tempFolder.newFolder().toPath();
ProjectUtils.createProject("Proj", Set.of(tMod, mainMod), projDir);
ProjectUtils.testProjectRun(
projDir,
res -> {
assertThat(res.isBoolean(), is(true));
assertThat(res.asBoolean(), is(true));
});
}
@Test
public void moduleMethodIsInBindingMap() throws IOException {
var tMod =
new SourceModule(
QualifiedName.fromString("T_Module"), """
module_method x = x
""");
var mainMod =
new SourceModule(
QualifiedName.fromString("Main"),
"""
from project.T_Module export module_method
""");
var projDir = tempFolder.newFolder().toPath();
ProjectUtils.createProject("Proj", Set.of(tMod, mainMod), projDir);
try (var ctx =
ContextUtils.defaultContextBuilder()
.option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString())
.build()) {
var polyCtx = new PolyglotContext(ctx);
polyCtx.getTopScope().compile(true);
var mainModExportedSymbols = ModuleUtils.getExportedSymbolsFromModule(ctx, "local.Proj.Main");
assertThat(mainModExportedSymbols.size(), is(1));
assertThat(mainModExportedSymbols, hasKey("module_method"));
}
}
@Test
public void staticMethodIsInBindingMap() throws IOException {
var tMod =
new SourceModule(
QualifiedName.fromString("T_Module"),
"""
type My_Type
My_Type.static_method x = x
""");
var mainMod =
new SourceModule(
QualifiedName.fromString("Main"),
"""
from project.T_Module export static_method
""");
var projDir = tempFolder.newFolder().toPath();
ProjectUtils.createProject("Proj", Set.of(tMod, mainMod), projDir);
try (var ctx =
ContextUtils.defaultContextBuilder()
.option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString())
.build()) {
var polyCtx = new PolyglotContext(ctx);
polyCtx.getTopScope().compile(true);
var mainModExportedSymbols = ModuleUtils.getExportedSymbolsFromModule(ctx, "local.Proj.Main");
assertThat(mainModExportedSymbols.size(), is(1));
assertThat(mainModExportedSymbols, hasKey("static_method"));
}
}
@Test
public void staticMethodIsDefinedEntity() throws IOException {
var mainMod =
new SourceModule(
QualifiedName.fromString("Main"), """
static_method x = x
""");
var projDir = tempFolder.newFolder().toPath();
ProjectUtils.createProject("Proj", Set.of(mainMod), projDir);
try (var ctx =
ContextUtils.defaultContextBuilder()
.option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString())
.build()) {
var polyCtx = new PolyglotContext(ctx);
polyCtx.getTopScope().compile(true);
var definedEntities = ModuleUtils.getDefinedEntities(ctx, "local.Proj.Main");
assertThat(definedEntities.size(), is(1));
assertThat(definedEntities.get(0).name(), containsString("static_method"));
}
}
}

View File

@ -0,0 +1,146 @@
package org.enso.interpreter.test.scope;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import java.io.IOException;
import java.util.Set;
import org.enso.common.LanguageInfo;
import org.enso.interpreter.runtime.Module;
import org.enso.pkg.QualifiedName;
import org.enso.polyglot.PolyglotContext;
import org.enso.polyglot.RuntimeOptions;
import org.enso.test.utils.ContextUtils;
import org.enso.test.utils.ProjectUtils;
import org.enso.test.utils.SourceModule;
import org.graalvm.polyglot.Source;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
public class ModuleScopeTest {
@Rule public TemporaryFolder tempFolder = new TemporaryFolder();
@Test
public void extensionMethodIsRegisteredInModuleScope() throws IOException {
var mainSrc =
Source.newBuilder(
LanguageInfo.ID,
"""
type My_Type
Value x
My_Type.extension_method self = self.x
""",
"test.enso")
.build();
try (var ctx = ContextUtils.createDefaultContext()) {
var mainMod = ctx.eval(mainSrc);
var mainRuntimeMod = (Module) ContextUtils.unwrapValue(ctx, mainMod);
var myType = mainRuntimeMod.getScope().getType("My_Type", true);
assertThat(myType, is(notNullValue()));
var extensionMethod = mainRuntimeMod.getScope().getMethodForType(myType, "extension_method");
assertThat(extensionMethod, is(notNullValue()));
}
}
@Test
public void staticMethodIsInResolvedExports() throws IOException {
var aMod =
new SourceModule(
QualifiedName.fromString("A_module"),
"""
type My_Type
My_Type.extension_method self = 42
""");
var mainMod =
new SourceModule(
QualifiedName.fromString("Main"),
"""
export project.A_Module.My_Type
export project.A_Module.extension_method
main = 42
""");
var projDir = tempFolder.newFolder().toPath();
ProjectUtils.createProject("Proj", Set.of(aMod, mainMod), projDir);
}
@Test
public void staticMethodIsRegisteredInModuleScope() throws IOException {
var mainSrc =
Source.newBuilder(
LanguageInfo.ID,
"""
type My_Type
static_method _ = 42
""",
"test.enso")
.build();
try (var ctx = ContextUtils.createDefaultContext()) {
var mainMod = ctx.eval(mainSrc);
var mainRuntimeMod = (Module) ContextUtils.unwrapValue(ctx, mainMod);
var myType = mainRuntimeMod.getScope().getType("My_Type", true);
assertThat(myType, is(notNullValue()));
var staticMethod = mainRuntimeMod.getScope().getMethodForType(myType, "static_method");
assertThat(staticMethod, is(notNullValue()));
}
}
@Test
public void moduleMethodIsRegisteredInModuleScope() throws IOException {
var mainSrc =
Source.newBuilder(
LanguageInfo.ID, """
module_method _ = 42
""", "test.enso")
.build();
try (var ctx = ContextUtils.createDefaultContext()) {
// ModuleScope is populated in IrToTruffle - at runtime. So we have to evaluate
// the main module before we inspect the ModuleScope.
var mainMod = ctx.eval(mainSrc);
var mainRuntimeMod = (Module) ContextUtils.unwrapValue(ctx, mainMod);
var assocType = mainRuntimeMod.getScope().getAssociatedType();
assertThat(assocType, is(notNullValue()));
var moduleMethod = mainRuntimeMod.getScope().getMethodForType(assocType, "module_method");
assertThat(moduleMethod, is(notNullValue()));
}
}
@Test
public void importedStaticMethodIsRegisteredInModuleScope() throws IOException {
var mod =
new SourceModule(
QualifiedName.fromString("Mod"),
"""
type My_Type
static_method _ = 1
""");
var mainMod =
new SourceModule(
QualifiedName.fromString("Main"),
"""
from project.Mod import My_Type
main = 2
""");
var projDir = tempFolder.newFolder().toPath();
ProjectUtils.createProject("Proj", Set.of(mod, mainMod), projDir);
var mainSrcPath = projDir.resolve("src").resolve("Main.enso");
try (var ctx =
ContextUtils.defaultContextBuilder()
.option(RuntimeOptions.PROJECT_ROOT, projDir.toAbsolutePath().toString())
.build()) {
var polyCtx = new PolyglotContext(ctx);
var mainRuntimeMod = polyCtx.evalModule(mainSrcPath.toFile());
var mainMethod = mainRuntimeMod.getMethod(mainRuntimeMod.getAssociatedType(), "main").get();
var mainRes = mainMethod.execute();
assertThat(mainRes.asInt(), is(2));
var ensoCtx = ContextUtils.leakContext(ctx);
var runtimeAbstractMod =
ensoCtx.getPackageRepository().getLoadedModule("local.Proj.Mod").get();
var runtimeConcreteMod = Module.fromCompilerModule(runtimeAbstractMod);
var myType = runtimeConcreteMod.getScope().getType("My_Type", true);
var staticMethod = runtimeConcreteMod.getScope().getMethodForType(myType, "static_method");
assertThat(staticMethod, is(notNullValue()));
}
}
}

View File

@ -52,7 +52,7 @@ class IRUtilsTest extends AnyWordSpecLike with Matchers with OptionValues {
} }
} }
private def findUsagesOfStaticMethod( private def findUsagesOfModuleMethod(
moduleName: QualifiedName, moduleName: QualifiedName,
module: Module, module: Module,
ir: IR ir: IR
@ -164,8 +164,8 @@ class IRUtilsTest extends AnyWordSpecLike with Matchers with OptionValues {
|""".stripMargin |""".stripMargin
val module = code.preprocessModule(moduleName) val module = code.preprocessModule(moduleName)
val operator1 = IRUtils.findByExternalId(module, uuid1).get val function1 = IRUtils.findByExternalId(module, uuid1).get
val usages = findUsagesOfStaticMethod(moduleName, module, operator1) val usages = findUsagesOfModuleMethod(moduleName, module, function1)
usages.value.size shouldEqual 1 usages.value.size shouldEqual 1
usages.value.foreach { usages.value.foreach {
@ -192,8 +192,8 @@ class IRUtilsTest extends AnyWordSpecLike with Matchers with OptionValues {
|""".stripMargin |""".stripMargin
val module = code.preprocessModule(moduleName) val module = code.preprocessModule(moduleName)
val operator1 = IRUtils.findByExternalId(module, uuid1).get val function1 = IRUtils.findByExternalId(module, uuid1).get
val usages = findUsagesOfStaticMethod(moduleName, module, operator1) val usages = findUsagesOfModuleMethod(moduleName, module, function1)
usages.value.size shouldEqual 2 usages.value.size shouldEqual 2
usages.value.foreach { usages.value.foreach {
@ -220,8 +220,8 @@ class IRUtilsTest extends AnyWordSpecLike with Matchers with OptionValues {
|""".stripMargin |""".stripMargin
val module = code.preprocessModule(moduleName) val module = code.preprocessModule(moduleName)
val operator1 = IRUtils.findByExternalId(module, uuid1).get val function1 = IRUtils.findByExternalId(module, uuid1).get
val usages = findUsagesOfStaticMethod(moduleName, module, operator1) val usages = findUsagesOfModuleMethod(moduleName, module, function1)
usages.value.size shouldEqual 1 usages.value.size shouldEqual 1
usages.value.foreach { usages.value.foreach {
@ -252,7 +252,7 @@ class IRUtilsTest extends AnyWordSpecLike with Matchers with OptionValues {
val module = code.preprocessModule(moduleName) val module = code.preprocessModule(moduleName)
val operator1 = IRUtils.findByExternalId(module, uuid1).get val operator1 = IRUtils.findByExternalId(module, uuid1).get
val usages = findUsagesOfStaticMethod(moduleName, module, operator1) val usages = findUsagesOfModuleMethod(moduleName, module, operator1)
usages.value.size shouldEqual 1 usages.value.size shouldEqual 1
usages.value.foreach { usages.value.foreach {

View File

@ -8,8 +8,11 @@ import org.enso.compiler.data.BindingsMap
import org.enso.compiler.data.BindingsMap.{ import org.enso.compiler.data.BindingsMap.{
Argument, Argument,
Cons, Cons,
ConversionMethod,
ModuleMethod, ModuleMethod,
PolyglotSymbol, PolyglotSymbol,
ResolvedStaticMethod,
StaticMethod,
Type Type
} }
import org.enso.compiler.pass.analyse.BindingAnalysis import org.enso.compiler.pass.analyse.BindingAnalysis
@ -54,6 +57,80 @@ class BindingAnalysisTest extends CompilerTest {
// === The Tests ============================================================ // === The Tests ============================================================
"Module binding resolution" should { "Module binding resolution" should {
"extension method is a defined entity" in {
implicit val ctx: ModuleContext = mkModuleContext
val ir =
"""
|type My_Type
|My_Type.extension_method = 42
|""".stripMargin.preprocessModule.analyse
val metadata = ir.unsafeGetMetadata(BindingAnalysis, "Should exist.")
metadata.definedEntities should contain theSameElementsAs List(
Type("My_Type", List(), List(), false),
StaticMethod("extension_method", "My_Type")
)
metadata.resolveName("extension_method") shouldEqual Right(
List(
ResolvedStaticMethod(
ctx.moduleReference(),
StaticMethod("extension_method", "My_Type")
)
)
)
}
"extension methods are defined entities" in {
implicit val ctx: ModuleContext = mkModuleContext
val ir =
"""
|type My_Type
|type Other_Type
|My_Type.extension_method = 42
|Other_Type.extension_method = 42
|""".stripMargin.preprocessModule.analyse
val metadata = ir.unsafeGetMetadata(BindingAnalysis, "Should exist.")
metadata.definedEntities should contain theSameElementsAs List(
Type("My_Type", List(), List(), false),
Type("Other_Type", List(), List(), false),
StaticMethod("extension_method", "My_Type"),
StaticMethod("extension_method", "Other_Type")
)
metadata.resolveName("extension_method") shouldBe Right(
List(
ResolvedStaticMethod(
ctx.moduleReference(),
StaticMethod("extension_method", "My_Type")
),
ResolvedStaticMethod(
ctx.moduleReference(),
StaticMethod("extension_method", "Other_Type")
)
)
)
}
"conversion method is a defined entity" in {
implicit val ctx: ModuleContext = mkModuleContext
val ir =
"""
|type Source
|type Target
|Target.from (that:Source) = 42
|Source.from (that:Target) = 42
|""".stripMargin.preprocessModule.analyse
val metadata = ir.unsafeGetMetadata(BindingAnalysis, "Should exist.")
metadata.definedEntities should contain theSameElementsAs List(
Type("Source", List(), List(), false),
Type("Target", List(), List(), false),
ConversionMethod("from", "Source", "Target"),
ConversionMethod("from", "Target", "Source")
)
}
"discover all atoms, methods, and polyglot symbols in a module" in { "discover all atoms, methods, and polyglot symbols in a module" in {
implicit val ctx: ModuleContext = mkModuleContext implicit val ctx: ModuleContext = mkModuleContext
val ir = val ir =
@ -80,6 +157,8 @@ class BindingAnalysisTest extends CompilerTest {
val metadata = ir.unsafeGetMetadata(BindingAnalysis, "Should exist.") val metadata = ir.unsafeGetMetadata(BindingAnalysis, "Should exist.")
metadata.definedEntities should contain theSameElementsAs List( metadata.definedEntities should contain theSameElementsAs List(
PolyglotSymbol("MyClass"),
PolyglotSymbol("Renamed_Class"),
Type( Type(
"Foo", "Foo",
List(), List(),
@ -110,8 +189,9 @@ class BindingAnalysisTest extends CompilerTest {
), ),
Type("Bar", List(), List(), builtinType = false), Type("Bar", List(), List(), builtinType = false),
Type("Baz", List("x", "y"), List(), builtinType = false), Type("Baz", List("x", "y"), List(), builtinType = false),
PolyglotSymbol("MyClass"), StaticMethod("foo", "Baz"),
PolyglotSymbol("Renamed_Class"), StaticMethod("baz", "Bar"),
ConversionMethod("from", "Bar", "Foo"),
ModuleMethod("foo") ModuleMethod("foo")
) )
metadata.currentModule shouldEqual ctx.moduleReference() metadata.currentModule shouldEqual ctx.moduleReference()
@ -133,8 +213,12 @@ class BindingAnalysisTest extends CompilerTest {
ir.getMetadata(BindingAnalysis) ir.getMetadata(BindingAnalysis)
.get .get
.definedEntities .definedEntities
.filter(_.isInstanceOf[BindingsMap.ModuleMethod]) shouldEqual List( .filter(
ModuleMethod("bar") _.isInstanceOf[BindingsMap.Method]
) should contain theSameElementsAs List(
StaticMethod("foo", moduleName),
ModuleMethod("bar"),
StaticMethod("baz", moduleName)
) )
} }

View File

@ -102,14 +102,14 @@ class GlobalNamesTest extends CompilerTest {
.map(expr => expr.asInstanceOf[Expression.Binding].expression) .map(expr => expr.asInstanceOf[Expression.Binding].expression)
"not resolve uppercase method names to applications with no arguments" in { "not resolve uppercase method names to applications with no arguments" in {
val expr = bodyExprs(1) val x2Expr = bodyExprs(1)
expr shouldBe an[errors.Resolution] x2Expr shouldBe an[errors.Resolution]
} }
"resolve method names to applications" in { "resolve method names to applications" in {
val expr = bodyExprs(2) val x3Expr = bodyExprs(2)
expr shouldBe an[Application.Prefix] x3Expr shouldBe an[Application.Prefix]
val app = expr.asInstanceOf[Application.Prefix] val app = x3Expr.asInstanceOf[Application.Prefix]
app.function.asInstanceOf[Name.Literal].name shouldEqual "constant" app.function.asInstanceOf[Name.Literal].name shouldEqual "constant"
app.arguments.length shouldEqual 1 app.arguments.length shouldEqual 1
app.arguments(0).value.getMetadata(GlobalNames) shouldEqual Some( app.arguments(0).value.getMetadata(GlobalNames) shouldEqual Some(
@ -118,16 +118,16 @@ class GlobalNamesTest extends CompilerTest {
} }
"not resolve uppercase method names to applications with arguments" in { "not resolve uppercase method names to applications with arguments" in {
val expr = bodyExprs(3) val x4Expr = bodyExprs(3)
expr shouldBe an[Application.Prefix] x4Expr shouldBe an[Application.Prefix]
val app = expr.asInstanceOf[Application.Prefix] val app = x4Expr.asInstanceOf[Application.Prefix]
app.function shouldBe an[errors.Resolution] app.function shouldBe an[errors.Resolution]
} }
"resolve method names in applications by adding the self argument" in { "resolve method names in applications by adding the self argument" in {
val expr = bodyExprs(4) val x5Expr = bodyExprs(4)
expr shouldBe an[Application.Prefix] x5Expr shouldBe an[Application.Prefix]
val app = expr.asInstanceOf[Application.Prefix] val app = x5Expr.asInstanceOf[Application.Prefix]
app.function.asInstanceOf[Name.Literal].name shouldEqual "add_one" app.function.asInstanceOf[Name.Literal].name shouldEqual "add_one"
app.arguments.length shouldEqual 2 app.arguments.length shouldEqual 2
app.arguments(0).value.getMetadata(GlobalNames) shouldEqual Some( app.arguments(0).value.getMetadata(GlobalNames) shouldEqual Some(
@ -136,9 +136,9 @@ class GlobalNamesTest extends CompilerTest {
} }
"resolve method names in partial applications by adding the self argument" in { "resolve method names in partial applications by adding the self argument" in {
val expr = bodyExprs(5) val yExpr = bodyExprs(5)
expr shouldBe an[Application.Prefix] yExpr shouldBe an[Application.Prefix]
val app = expr.asInstanceOf[Application.Prefix] val app = yExpr.asInstanceOf[Application.Prefix]
app.function.asInstanceOf[Name.Literal].name shouldEqual "add_one" app.function.asInstanceOf[Name.Literal].name shouldEqual "add_one"
app.arguments.length shouldEqual 1 app.arguments.length shouldEqual 1
app.arguments(0).value.getMetadata(GlobalNames) shouldEqual Some( app.arguments(0).value.getMetadata(GlobalNames) shouldEqual Some(
@ -152,6 +152,43 @@ class GlobalNamesTest extends CompilerTest {
} }
} }
"Global names of static methods" should {
"resolve module method name by adding the self argument" in {
implicit val ctx: ModuleContext = mkModuleContext._1
val ir =
"""
|method x = x
|
|type My_Type
| method x = x
|
|main =
| method 42
| 0
|""".stripMargin.preprocessModule.analyse
val mainMethodExprs = ir
.bindings(3)
.asInstanceOf[definition.Method.Explicit]
.body
.asInstanceOf[Function.Lambda]
.body
.asInstanceOf[Expression.Block]
.expressions
val moduleMethodCall = mainMethodExprs(0)
.asInstanceOf[Application.Prefix]
moduleMethodCall.function
.asInstanceOf[Name.Literal]
.name shouldEqual "method"
moduleMethodCall.arguments.length shouldEqual 2
moduleMethodCall
.arguments(0)
.value
.getMetadata(GlobalNames) shouldEqual Some(
Resolution(ResolvedModule(ctx.moduleReference()))
)
}
}
"Undefined names" should { "Undefined names" should {
"be detected and reported" in { "be detected and reported" in {
implicit val ctx: ModuleContext = mkModuleContext._1 implicit val ctx: ModuleContext = mkModuleContext._1

View File

@ -265,7 +265,7 @@ class ImportExportTest
mainBindingMap mainBindingMap
.resolvedImports(1) .resolvedImports(1)
.target .target
.asInstanceOf[BindingsMap.ResolvedMethod] .asInstanceOf[BindingsMap.ResolvedModuleMethod]
.method .method
.name shouldEqual "static_method" .name shouldEqual "static_method"
// In B_Module, we only have ResolvedMethod in the resolvedImports, there is no ResolvedModule // In B_Module, we only have ResolvedMethod in the resolvedImports, there is no ResolvedModule
@ -274,7 +274,7 @@ class ImportExportTest
bBindingMap bBindingMap
.resolvedImports(0) .resolvedImports(0)
.target .target
.asInstanceOf[BindingsMap.ResolvedMethod] .asInstanceOf[BindingsMap.ResolvedModuleMethod]
.method .method
.name shouldEqual "static_method" .name shouldEqual "static_method"
} }

View File

@ -162,7 +162,7 @@ object ImportExport {
moduleOrTypeName: String moduleOrTypeName: String
) extends Reason { ) extends Reason {
override def message(source: (IdentifiedLocation => String)): String = override def message(source: (IdentifiedLocation => String)): String =
s"The symbol $symbolName (module, type, or constructor) does not exist in $moduleOrTypeName." s"The symbol $symbolName (module, type, method, or constructor) does not exist in $moduleOrTypeName."
} }
case class NoSuchConstructor( case class NoSuchConstructor(

View File

@ -17,7 +17,7 @@ final class ExportsBuilder {
def build(moduleName: QualifiedName, ir: IR): ModuleExports = { def build(moduleName: QualifiedName, ir: IR): ModuleExports = {
val symbols = getBindings(ir).exportedSymbols.values.flatten val symbols = getBindings(ir).exportedSymbols.values.flatten
.collect { .collect {
case BindingsMap.ResolvedMethod(module, method) => case BindingsMap.ResolvedModuleMethod(module, method) =>
ExportedSymbol.Method(module.getName.toString, method.name) ExportedSymbol.Method(module.getName.toString, method.name)
case BindingsMap.ResolvedType(module, tp) => case BindingsMap.ResolvedType(module, tp) =>
ExportedSymbol.Type(module.getName.toString, tp.name) ExportedSymbol.Type(module.getName.toString, tp.name)

View File

@ -198,31 +198,35 @@ public final class ImportExportCache
@Persistable( @Persistable(
clazz = org.enso.compiler.data.BindingsMap$ModuleReference$Abstract.class, clazz = org.enso.compiler.data.BindingsMap$ModuleReference$Abstract.class,
id = 33007) id = 33007)
@Persistable(clazz = BindingsMap.ModuleMethod.class, id = 33008)
@Persistable(clazz = BindingsMap.Type.class, id = 33009) @Persistable(clazz = BindingsMap.Type.class, id = 33009)
@Persistable(clazz = BindingsMap.ResolvedImport.class, id = 33010) @Persistable(clazz = BindingsMap.ResolvedImport.class, id = 33010)
@Persistable(clazz = BindingsMap.Cons.class, id = 33011) @Persistable(clazz = BindingsMap.Cons.class, id = 33011)
@Persistable(clazz = BindingsMap.ResolvedModule.class, id = 33012) @Persistable(clazz = BindingsMap.ResolvedModule.class, id = 33012)
@Persistable(clazz = BindingsMap.ResolvedType.class, id = 33013) @Persistable(clazz = BindingsMap.ResolvedType.class, id = 33013)
@Persistable(clazz = BindingsMap.ResolvedMethod.class, id = 33014) @Persistable(clazz = BindingsMap.ResolvedModuleMethod.class, id = 33014)
@Persistable(clazz = BindingsMap.ExportedModule.class, id = 33015) @Persistable(clazz = BindingsMap.ResolvedStaticMethod.class, id = 33015)
@Persistable(clazz = org.enso.compiler.data.BindingsMap$SymbolRestriction$Only.class, id = 33016) @Persistable(clazz = BindingsMap.ResolvedConversionMethod.class, id = 33016)
@Persistable(clazz = org.enso.compiler.data.BindingsMap$SymbolRestriction$Union.class, id = 33017) @Persistable(clazz = BindingsMap.ExportedModule.class, id = 33017)
@Persistable(clazz = org.enso.compiler.data.BindingsMap$SymbolRestriction$Only.class, id = 33018)
@Persistable(clazz = org.enso.compiler.data.BindingsMap$SymbolRestriction$Union.class, id = 33019)
@Persistable( @Persistable(
clazz = org.enso.compiler.data.BindingsMap$SymbolRestriction$Intersect.class, clazz = org.enso.compiler.data.BindingsMap$SymbolRestriction$Intersect.class,
id = 33018) id = 33020)
@Persistable( @Persistable(
clazz = org.enso.compiler.data.BindingsMap$SymbolRestriction$AllowedResolution.class, clazz = org.enso.compiler.data.BindingsMap$SymbolRestriction$AllowedResolution.class,
id = 33019) id = 33021)
@Persistable(clazz = org.enso.compiler.data.BindingsMap$SymbolRestriction$All$.class, id = 33020) @Persistable(clazz = org.enso.compiler.data.BindingsMap$SymbolRestriction$All$.class, id = 33022)
@Persistable( @Persistable(
clazz = org.enso.compiler.data.BindingsMap$SymbolRestriction$Hiding.class, clazz = org.enso.compiler.data.BindingsMap$SymbolRestriction$Hiding.class,
id = 33021) id = 33023)
@Persistable(clazz = BindingsMap.Resolution.class, id = 33029) @Persistable(clazz = BindingsMap.Resolution.class, id = 33024)
@Persistable(clazz = BindingsMap.ResolvedConstructor.class, id = 33030) @Persistable(clazz = BindingsMap.ResolvedConstructor.class, id = 33025)
@Persistable(clazz = BindingsMap.ResolvedPolyglotSymbol.class, id = 33031) @Persistable(clazz = BindingsMap.ResolvedPolyglotSymbol.class, id = 33026)
@Persistable(clazz = BindingsMap.ResolvedPolyglotField.class, id = 33032) @Persistable(clazz = BindingsMap.ResolvedPolyglotField.class, id = 33027)
@Persistable(clazz = BindingsMap.Argument.class, id = 33033) @Persistable(clazz = BindingsMap.ModuleMethod.class, id = 33028)
@Persistable(clazz = BindingsMap.StaticMethod.class, id = 33029)
@Persistable(clazz = BindingsMap.ConversionMethod.class, id = 33030)
@Persistable(clazz = BindingsMap.Argument.class, id = 33031)
@ServiceProvider(service = Persistance.class) @ServiceProvider(service = Persistance.class)
public static final class PersistBindingsMap extends Persistance<BindingsMap> { public static final class PersistBindingsMap extends Persistance<BindingsMap> {
public PersistBindingsMap() { public PersistBindingsMap() {

View File

@ -24,7 +24,13 @@ public final class ModuleScope implements EnsoObject {
private final Map<String, Object> polyglotSymbols; private final Map<String, Object> polyglotSymbols;
private final Map<String, Type> types; private final Map<String, Type> types;
private final Map<Type, Map<String, Supplier<Function>>> methods; private final Map<Type, Map<String, Supplier<Function>>> methods;
/**
* First key is target type, second key is source type. The value is the conversion function from
* source to target.
*/
private final Map<Type, Map<Type, Function>> conversions; private final Map<Type, Map<Type, Function>> conversions;
private final Set<ImportExportScope> imports; private final Set<ImportExportScope> imports;
private final Set<ImportExportScope> exports; private final Set<ImportExportScope> exports;
@ -97,24 +103,37 @@ public final class ModuleScope implements EnsoObject {
.orElse(null); .orElse(null);
} }
/**
* Looks up a conversion method from source type to target type. The conversion method
* implementation looks like this:
*
* <pre>
* Target_Type.from (other : Source_Type) = ...
* </pre>
*
* The conversion method is first looked up in the scope of the source type, then in the scope of
* the target type and finally in all the imported scopes.
*
* @param source Source type
* @param target Target type
* @return The conversion method or null if not found.
*/
@CompilerDirectives.TruffleBoundary @CompilerDirectives.TruffleBoundary
public Function lookupConversionDefinition(Type original, Type target) { public Function lookupConversionDefinition(Type source, Type target) {
Function definedWithOriginal = Function definedWithSource = source.getDefinitionScope().getConversionsFor(target).get(source);
original.getDefinitionScope().getConversionsFor(target).get(original); if (definedWithSource != null) {
if (definedWithOriginal != null) { return definedWithSource;
return definedWithOriginal;
} }
Function definedWithTarget = Function definedWithTarget = target.getDefinitionScope().getConversionsFor(target).get(source);
target.getDefinitionScope().getConversionsFor(target).get(original);
if (definedWithTarget != null) { if (definedWithTarget != null) {
return definedWithTarget; return definedWithTarget;
} }
Function definedHere = getConversionsFor(target).get(original); Function definedHere = getConversionsFor(target).get(source);
if (definedHere != null) { if (definedHere != null) {
return definedHere; return definedHere;
} }
return imports.stream() return imports.stream()
.map(scope -> scope.getExportedConversion(original, target)) .map(scope -> scope.getExportedConversion(source, target))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.findFirst() .findFirst()
.orElse(null); .orElse(null);

View File

@ -94,8 +94,6 @@ import org.enso.interpreter.node.{
MethodRootNode, MethodRootNode,
ExpressionNode => RuntimeExpression ExpressionNode => RuntimeExpression
} }
import org.enso.interpreter.runtime.EnsoContext
import org.enso.interpreter.runtime.callable
import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition import org.enso.interpreter.runtime.callable.argument.ArgumentDefinition
import org.enso.interpreter.runtime.data.atom.{Atom, AtomConstructor} import org.enso.interpreter.runtime.data.atom.{Atom, AtomConstructor}
import org.enso.interpreter.runtime.callable.function.{ import org.enso.interpreter.runtime.callable.function.{
@ -404,9 +402,17 @@ class IrToTruffle(
throw new CompilerError( throw new CompilerError(
"Impossible polyglot field, should be caught by MethodDefinitions pass." "Impossible polyglot field, should be caught by MethodDefinitions pass."
) )
case _: BindingsMap.ResolvedMethod => case _: BindingsMap.ResolvedModuleMethod =>
throw new CompilerError( throw new CompilerError(
"Impossible here, should be caught by MethodDefinitions pass." "Impossible module method here, should be caught by MethodDefinitions pass."
)
case _: BindingsMap.ResolvedStaticMethod =>
throw new CompilerError(
"Impossible static method here, should be caught by MethodDefinitions pass."
)
case _: BindingsMap.ResolvedConversionMethod =>
throw new CompilerError(
"Impossible conversion method here, should be caught by MethodDefinitions pass."
) )
} }
} }
@ -871,9 +877,17 @@ class IrToTruffle(
throw new CompilerError( throw new CompilerError(
"Impossible polyglot field, should be caught by MethodDefinitions pass." "Impossible polyglot field, should be caught by MethodDefinitions pass."
) )
case _: BindingsMap.ResolvedMethod => case _: BindingsMap.ResolvedModuleMethod =>
throw new CompilerError( throw new CompilerError(
"Impossible here, should be caught by MethodDefinitions pass." "Impossible module method here, should be caught by MethodDefinitions pass."
)
case _: BindingsMap.ResolvedStaticMethod =>
throw new CompilerError(
"Impossible static method here, should be caught by MethodDefinitions pass."
)
case _: BindingsMap.ResolvedConversionMethod =>
throw new CompilerError(
"Impossible conversion method here, should be caught by MethodDefinitions pass."
) )
} }
} }
@ -979,7 +993,7 @@ class IrToTruffle(
name, name,
fun fun
) )
case BindingsMap.ResolvedMethod(module, method) => case BindingsMap.ResolvedModuleMethod(module, method) =>
val actualModule = module.unsafeAsModule() val actualModule = module.unsafeAsModule()
val fun = asScope(actualModule) val fun = asScope(actualModule)
.getMethodForType( .getMethodForType(
@ -995,6 +1009,100 @@ class IrToTruffle(
name, name,
fun fun
) )
case BindingsMap.ResolvedStaticMethod(module, staticMethod) =>
val actualModule = module.unsafeAsModule()
val currentScope = asScope(actualModule)
actualModule.getBindingsMap.resolveName(
staticMethod.tpName
) match {
case Right(List(BindingsMap.ResolvedType(modWithTp, _))) =>
val tpScope = asScope(modWithTp.unsafeAsModule())
val tp = tpScope.getType(staticMethod.tpName, true)
assert(
tp != null,
s"Type should be defined in module ${modWithTp.getName}"
)
// We have to search for the method on eigen type, because it is a static method.
// Static methods are always defined on eigen types
val eigenTp = tp.getEigentype
val fun =
currentScope.getMethodForType(
eigenTp,
staticMethod.methodName
)
assert(
fun != null,
s"exported symbol (static method) `${staticMethod.name}` needs to be registered first in the module "
)
scopeBuilder.registerMethod(
scopeAssociatedType,
name,
fun
)
case _ =>
throw new CompilerError(
s"Type ${staticMethod.tpName} should be resolvable in module ${module.getName}"
)
}
case BindingsMap.ResolvedConversionMethod(
module,
conversionMethod
) =>
val actualModule = module.unsafeAsModule()
val actualScope = asScope(actualModule)
actualModule.getBindingsMap.resolveName(
conversionMethod.targetTpName
) match {
case Right(
List(BindingsMap.ResolvedType(modWithTargetTp, _))
) =>
val targetTpScope = asScope(modWithTargetTp.unsafeAsModule())
val targetTp =
targetTpScope.getType(conversionMethod.targetTpName, true)
assert(
targetTp != null,
s"Target type should be defined in module ${module.getName}"
)
actualModule.getBindingsMap.resolveName(
conversionMethod.sourceTpName
) match {
case Right(
List(BindingsMap.ResolvedType(modWithSourceTp, _))
) =>
val sourceTpScope =
asScope(modWithSourceTp.unsafeAsModule())
val sourceTp = sourceTpScope.getType(
conversionMethod.sourceTpName,
true
)
assert(
sourceTp != null,
s"Source type should be defined in module ${module.getName}"
)
val conversionFun =
actualScope.lookupConversionDefinition(
sourceTp,
targetTp
)
assert(
conversionFun != null,
s"Conversion method `$conversionMethod` should be defined in module ${module.getName}"
)
scopeBuilder.registerConversionMethod(
targetTp,
sourceTp,
conversionFun
)
case _ =>
throw new CompilerError(
s"Source type ${conversionMethod.sourceTpName} should be resolvable in module ${module.getName}"
)
}
case _ =>
throw new CompilerError(
s"Target type ${conversionMethod.targetTpName} should be resolvable in module ${module.getName}"
)
}
case BindingsMap.ResolvedPolyglotSymbol(_, _) => case BindingsMap.ResolvedPolyglotSymbol(_, _) =>
case BindingsMap.ResolvedPolyglotField(_, _) => case BindingsMap.ResolvedPolyglotField(_, _) =>
} }
@ -1428,11 +1536,27 @@ class IrToTruffle(
}) })
case Some( case Some(
BindingsMap.Resolution( BindingsMap.Resolution(
BindingsMap.ResolvedMethod(_, _) BindingsMap.ResolvedModuleMethod(_, _)
) )
) => ) =>
throw new CompilerError( throw new CompilerError(
"Impossible method here, should be caught by Patterns resolution pass." "Impossible module method here, should be caught by Patterns resolution pass."
)
case Some(
BindingsMap.Resolution(
BindingsMap.ResolvedStaticMethod(_, _)
)
) =>
throw new CompilerError(
"Impossible static method here, should be caught by Patterns resolution pass."
)
case Some(
BindingsMap.Resolution(
BindingsMap.ResolvedConversionMethod(_, _)
)
) =>
throw new CompilerError(
"Impossible conversion method here, should be caught by Patterns resolution pass."
) )
} }
} }
@ -1800,9 +1924,17 @@ class IrToTruffle(
} }
ConstantObjectNode.build(s) ConstantObjectNode.build(s)
case BindingsMap.ResolvedMethod(_, method) => case BindingsMap.ResolvedModuleMethod(_, method) =>
throw new CompilerError( throw new CompilerError(
s"Impossible here, ${method.name} should be caught when translating application" s"Impossible here, module method ${method.name} should be caught when translating application"
)
case BindingsMap.ResolvedStaticMethod(_, staticMethod) =>
throw new CompilerError(
s"Impossible here, static method ${staticMethod.name} should be caught when translating application"
)
case BindingsMap.ResolvedConversionMethod(_, conversionMethod) =>
throw new CompilerError(
s"Impossible here, conversion method ${conversionMethod.targetTpName}.${conversionMethod.methodName} should be caught when translating application"
) )
} }
} }

View File

@ -21,9 +21,7 @@ import org.graalvm.polyglot.Value;
import org.graalvm.polyglot.io.IOAccess; import org.graalvm.polyglot.io.IOAccess;
import org.graalvm.polyglot.proxy.ProxyExecutable; import org.graalvm.polyglot.proxy.ProxyExecutable;
/** /** A collection of classes and methods useful for testing {@link Context} related stuff. */
* A collection of classes and methods useful for testing {@link Context} related stuff.
*/
public final class ContextUtils { public final class ContextUtils {
private ContextUtils() {} private ContextUtils() {}
@ -90,7 +88,6 @@ public final class ContextUtils {
return res; return res;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private static <E extends Throwable> E raise(Class<E> clazz, Throwable t) throws E { private static <E extends Throwable> E raise(Class<E> clazz, Throwable t) throws E {
throw (E) t; throw (E) t;
@ -150,6 +147,18 @@ public final class ContextUtils {
return mainMethod.execute(); return mainMethod.execute();
} }
public static org.enso.compiler.core.ir.Module compileModule(Context ctx, String src) {
return compileModule(ctx, src, "Test");
}
public static org.enso.compiler.core.ir.Module compileModule(
Context ctx, String src, String moduleName) {
var source = Source.newBuilder(LanguageInfo.ID, src, moduleName + ".enso").buildLiteral();
var module = ctx.eval(source);
var runtimeMod = (org.enso.interpreter.runtime.Module) unwrapValue(ctx, module);
return runtimeMod.getIr();
}
/** /**
* Parses the given module and returns a method by the given name from the module. * Parses the given module and returns a method by the given name from the module.
* *

View File

@ -0,0 +1,47 @@
package org.enso.test.utils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.enso.compiler.context.CompilerContext.Module;
import org.enso.compiler.data.BindingsMap.DefinedEntity;
import org.enso.compiler.data.BindingsMap.ResolvedName;
import org.graalvm.polyglot.Context;
import scala.jdk.javaapi.CollectionConverters;
/** Helper utility methods for manipulating with {@link org.enso.interpreter.runtime.Module}. */
public class ModuleUtils {
private ModuleUtils() {}
/**
* Returns mapping of symbols to exported resolved names from the given module.
*
* @param modName FQN of the module
* @see {@link BindingsMap#exportedSymbols()}
*/
public static Map<String, List<ResolvedName>> getExportedSymbolsFromModule(
Context ctx, String modName) {
var ensoCtx = ContextUtils.leakContext(ctx);
var mod = ensoCtx.getPackageRepository().getLoadedModule(modName).get();
return getExportedSymbols(mod);
}
public static List<DefinedEntity> getDefinedEntities(Context ctx, String modName) {
var ensoCtx = ContextUtils.leakContext(ctx);
var mod = ensoCtx.getPackageRepository().getLoadedModule(modName).get();
return CollectionConverters.asJava(mod.getBindingsMap().definedEntities());
}
private static Map<String, List<ResolvedName>> getExportedSymbols(Module module) {
var bindings = new HashMap<String, List<ResolvedName>>();
var bindingsScala = module.getBindingsMap().exportedSymbols();
bindingsScala.foreach(
entry -> {
var symbol = entry._1;
var resolvedNames = CollectionConverters.asJava(entry._2.toSeq());
bindings.put(symbol, resolvedNames);
return null;
});
return bindings;
}
}

View File

@ -13,13 +13,10 @@ import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Context.Builder; import org.graalvm.polyglot.Context.Builder;
import org.graalvm.polyglot.Value; import org.graalvm.polyglot.Value;
/** /** Utility methods for creating and running Enso projects. */
* Utility methods for creating and running Enso projects.
*/
public class ProjectUtils { public class ProjectUtils {
private ProjectUtils() {} private ProjectUtils() {}
/** /**
* Creates temporary project directory structure with a given main source content. No need to * Creates temporary project directory structure with a given main source content. No need to
* clean it up, as it is managed by JUnit TemporaryFolder rule. Note that we need to create a * clean it up, as it is managed by JUnit TemporaryFolder rule. Note that we need to create a
@ -42,13 +39,14 @@ public class ProjectUtils {
* *
* @param projName Name of the project * @param projName Name of the project
* @param modules Set of modules. Must contain `Main` module. * @param modules Set of modules. Must contain `Main` module.
* @param projDir A directory in which the whole project structure will be created. * @param projDir A directory in which the whole project structure will be created. Must exist and
* Must exist and be a directory. * be a directory.
*/ */
public static void createProject( public static void createProject(String projName, Set<SourceModule> modules, Path projDir)
String projName, Set<SourceModule> modules, Path projDir) throws IOException { throws IOException {
if (!projDir.toFile().exists() || !projDir.toFile().isDirectory()) { if (!projDir.toFile().exists() || !projDir.toFile().isDirectory()) {
throw new IllegalArgumentException("Project directory " + projDir + " must already be created"); throw new IllegalArgumentException(
"Project directory " + projDir + " must already be created");
} }
var projYaml = var projYaml =
""" """

View File

@ -2,9 +2,5 @@ package org.enso.test.utils;
import org.enso.pkg.QualifiedName; import org.enso.pkg.QualifiedName;
/** /** A simple structure corresponding to an Enso module. */
* A simple structure corresponding to an Enso module. public record SourceModule(QualifiedName name, String code) {}
*/
public record SourceModule(QualifiedName name, String code) {
}

View File

@ -8,8 +8,7 @@ import org.enso.interpreter.EnsoLanguage;
/** /**
* An artificial RootNode. Used for tests of nodes that need to be adopted. Just create this root * An artificial RootNode. Used for tests of nodes that need to be adopted. Just create this root
* node inside a context, all the other nodes, and insert them via * node inside a context, all the other nodes, and insert them via {@link #insertChildren(Node...)}.
* {@link #insertChildren(Node...)}.
*/ */
public final class TestRootNode extends RootNode { public final class TestRootNode extends RootNode {
@ -30,9 +29,7 @@ public final class TestRootNode extends RootNode {
} }
} }
/** /** In the tests, do not execute this root node, but execute directly the child nodes. */
* In the tests, do not execute this root node, but execute directly the child nodes.
*/
@Override @Override
public Object execute(VirtualFrame frame) { public Object execute(VirtualFrame frame) {
if (callback == null) { if (callback == null) {