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-server`,
testkit,
`test-utils`,
`common-polyglot-core-utils`,
`std-base`,
`std-database`,

View File

@ -100,15 +100,17 @@ binds the function name. This means that:
## Methods
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
`this` argument, while functions are not.
`self` argument, while functions are not.
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`
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
type Maybe a
@ -120,9 +122,9 @@ type Maybe a
Maybe.Just _ -> True
```
2. **As an Extension Method:** A function defined _explicitly_ on an atom counts
as an extension method on that atom. It can be defined on a typeset to apply
to all the atoms within that typeset.
2. **As an Extension Method:** A function defined _explicitly_ on a type counts
as an extension method on that type. An _extension_ method can be _static_ or
_instance_, depending on whether the `self` argument is present or not.
```ruby
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
the type of the `this` argument specified to be a type.
3. **As a Function with an Explicit `self` Argument:** A function defined with
the type of the `self` argument specified to be a type.
```ruby
floor (this : Number) = case this of
floor (self : Number) = case self of
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 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

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

View File

@ -125,16 +125,18 @@ public class PrivateSymbolsAnalysis implements IRPass {
private Pattern processCasePattern(Pattern pattern, BindingsMap bindingsMap) {
if (pattern instanceof Pattern.Constructor cons) {
var consName = cons.constructor();
var resolvedCons = tryResolveName(consName, bindingsMap);
if (resolvedCons != null && isProjectPrivate(resolvedCons)) {
var curProjName = getProjName(bindingsMap.currentModule().getName());
var resolvedProjName = getProjName(resolvedCons.module().getName());
if (!curProjName.equals(resolvedProjName)) {
var reason =
new org.enso.compiler.core.ir.expression.errors.Pattern.PrivateConstructor(
consName.name(), curProjName, resolvedProjName);
return new org.enso.compiler.core.ir.expression.errors.Pattern(
cons, reason, cons.passData(), cons.diagnostics());
var resolvedNames = tryResolveName(consName, bindingsMap);
for (var resolvedName : resolvedNames) {
if (isProjectPrivate(resolvedName)) {
var curProjName = getProjName(bindingsMap.currentModule().getName());
var resolvedProjName = getProjName(resolvedName.module().getName());
if (!curProjName.equals(resolvedProjName)) {
var reason =
new org.enso.compiler.core.ir.expression.errors.Pattern.PrivateConstructor(
consName.name(), curProjName, resolvedProjName);
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) {
var resolvedName = tryResolveName(name, bindingsMap);
if (resolvedName != null && isProjectPrivate(resolvedName)) {
var curProjName = getProjName(bindingsMap.currentModule().getName());
var resolvedProjName = getProjName(resolvedName.module().getName());
if (!curProjName.equals(resolvedProjName)) {
var reason =
new org.enso.compiler.core.ir.expression.errors.Resolution.PrivateEntity(
curProjName, resolvedProjName);
return new org.enso.compiler.core.ir.expression.errors.Resolution(
name, reason, name.passData(), name.diagnostics());
var resolvedNames = tryResolveName(name, bindingsMap);
for (var resolvedName : resolvedNames) {
if (isProjectPrivate(resolvedName)) {
var curProjName = getProjName(bindingsMap.currentModule().getName());
var resolvedProjName = getProjName(resolvedName.module().getName());
if (!curProjName.equals(resolvedProjName)) {
var reason =
new org.enso.compiler.core.ir.expression.errors.Resolution.PrivateEntity(
curProjName, resolvedProjName);
return new org.enso.compiler.core.ir.expression.errors.Resolution(
name, reason, name.passData(), name.diagnostics());
}
}
}
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) {
case Name.Literal lit -> {
var resolved = bindingsMap.resolveName(lit.name());
if (resolved.isRight()) {
yield (ResolvedName) resolved.getOrElse(() -> null);
var resolvedNames = resolved.toOption().get();
yield CollectionConverters.asJava(resolvedNames);
} else {
yield null;
yield List.of();
}
}
case Name.Qualified qual -> {
var nameParts = qual.parts().map(Name::name);
var resolved = bindingsMap.resolveQualifiedName(nameParts);
if (resolved.isRight()) {
yield (ResolvedName) resolved.getOrElse(() -> null);
var resolvedNames = resolved.toOption().get();
yield CollectionConverters.asJava(resolvedNames);
} 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 scala.annotation.unused
import scala.collection.mutable.ArrayBuffer
/** A utility structure for resolving symbols in a given module.
*
@ -174,15 +175,18 @@ case class BindingsMap(
}).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.
* @return a resolution for `name` or an error, if the name could not be
* resolved.
* @param name (Unqualified) name of the symbol to resolve
* @return A list of all resolutions for the given name or an error if no resolution
* was found
*/
def resolveName(
name: String
): Either[ResolutionError, ResolvedName] = {
): Either[ResolutionError, List[ResolvedName]] = {
val local = findLocalCandidates(name)
if (local.nonEmpty) {
return BindingsMap.handleAmbiguity(local)
@ -200,14 +204,14 @@ case class BindingsMap(
scope: ResolvedName,
submoduleNames: List[String],
finalItem: String
): Either[ResolutionError, ResolvedName] = scope match {
): Either[ResolutionError, List[ResolvedName]] = scope match {
case scoped: ImportTarget =>
var currentScope = scoped
for (modName <- submoduleNames) {
val resolution = currentScope.resolveExportedSymbol(modName)
resolution match {
val resolutions = currentScope.resolveExportedSymbol(modName)
resolutions match {
case Left(err) => return Left(err)
case Right(t: ImportTarget) =>
case Right(List(t: ImportTarget)) =>
currentScope = t
case _ => return Left(ResolutionNotFound)
}
@ -215,7 +219,7 @@ case class BindingsMap(
currentScope.resolveExportedSymbol(finalItem)
case s @ ResolvedPolyglotSymbol(_, _) =>
val found = s.findExportedSymbolFor(finalItem)
Right(found)
Right(List(found))
case _ => Left(ResolutionNotFound)
}
@ -223,37 +227,64 @@ case class BindingsMap(
*
* @param name the name to resolve
* @return a resolution for `name`
* @see [[resolveName]]
*/
def resolveQualifiedName(
name: List[String]
): Either[ResolutionError, ResolvedName] =
): Either[ResolutionError, List[ResolvedName]] =
name match {
case List() => Left(ResolutionNotFound)
case List(item) => resolveName(item)
case firstModuleName :: rest =>
resolveName(firstModuleName).flatMap { firstModule =>
// 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.
val isQualifiedLocalImport =
firstModule == ResolvedModule(currentModule)
if (isQualifiedLocalImport) {
resolveLocalName(rest)
} else {
val consName = rest.last
val modNames = rest.init
resolveQualifiedNameIn(firstModule, modNames, consName)
}
val firstResolvedNamesOpt = resolveName(firstModuleName)
firstResolvedNamesOpt match {
case err @ Left(_) => err
case Right(firstResolvedNames) =>
// 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.
val isQualifiedLocalImport =
firstResolvedNames == List(ResolvedModule(currentModule))
if (isQualifiedLocalImport) {
resolveLocalName(rest)
} 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(
name: List[String]
): Either[ResolutionError, ResolvedName] = name match {
): Either[ResolutionError, List[ResolvedName]] = name match {
case List() => Left(ResolutionNotFound)
case List(singleItem) =>
handleAmbiguity(findLocalCandidates(singleItem))
case firstName :: rest =>
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(
@ -269,7 +300,7 @@ case class BindingsMap(
*/
def resolveExportedName(
name: String
): Either[ResolutionError, ResolvedName] = {
): Either[ResolutionError, List[ResolvedName]] = {
handleAmbiguity(findExportedSymbolsFor(name))
}
@ -323,11 +354,25 @@ object BindingsMap {
private def handleAmbiguity(
candidates: List[ResolvedName]
): Either[ResolutionError, ResolvedName] = {
): Either[ResolutionError, List[ResolvedName]] = {
candidates.distinct match {
case List() => Left(ResolutionNotFound)
case List(it) => Right(it)
case items => Left(ResolutionAmbiguous(items))
case List(it) => Right(List(it))
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 toConcrete(moduleMap: ModuleMap): Option[ImportTarget]
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(
name: String
): Either[ResolutionError, ResolvedName] =
): Either[ResolutionError, List[ResolvedName]] =
BindingsMap.handleAmbiguity(findExportedSymbolsFor(name))
def exportedSymbols: Map[String, List[ResolvedName]]
}
@ -720,14 +775,21 @@ object BindingsMap {
sealed trait DefinedEntity {
def name: String
def resolvedIn(module: ModuleReference): ResolvedName = this match {
case t: Type => ResolvedType(module, t)
case m: ModuleMethod => ResolvedMethod(module, m)
case t: Type => ResolvedType(module, t)
case staticMethod: StaticMethod =>
ResolvedStaticMethod(module, staticMethod)
case conversionMethod: ConversionMethod =>
ResolvedConversionMethod(module, conversionMethod)
case m: ModuleMethod => ResolvedModuleMethod(module, m)
case p: PolyglotSymbol => ResolvedPolyglotSymbol(module, p)
}
def resolvedIn(module: Module): ResolvedName = resolvedIn(
ModuleReference.Concrete(module)
)
// Determines if this entity can be exported during export resolution pass
def canExport: Boolean
}
@ -811,12 +873,43 @@ object BindingsMap {
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.
*/
case class ModuleMethod(override val name: String) extends DefinedEntity {
override def canExport: Boolean = true
case class ModuleMethod(override val name: String) extends Method {}
/** 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.
@ -938,23 +1031,27 @@ object BindingsMap {
.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 method the method representation.
*/
case class ResolvedMethod(module: ModuleReference, method: ModuleMethod)
extends ResolvedName {
case class ResolvedModuleMethod(module: ModuleReference, method: ModuleMethod)
extends ResolvedMethod {
/** @inheritdoc */
override def toAbstract: ResolvedMethod = {
override def toAbstract: ResolvedModuleMethod = {
this.copy(module = module.toAbstract)
}
/** @inheritdoc */
override def toConcrete(
moduleMap: ModuleMap
): Option[ResolvedMethod] = {
): Option[ResolvedModuleMethod] = {
module.toConcrete(moduleMap).map(module => this.copy(module = module))
}
@ -979,6 +1076,58 @@ object BindingsMap {
override def qualifiedName: QualifiedName =
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.
@ -1046,8 +1195,12 @@ object BindingsMap {
s" The imported polyglot symbol ${symbol.name};"
case BindingsMap.ResolvedPolyglotField(_, 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}"
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) =>
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.data.BindingsMap
import org.enso.compiler.core.CompilerError
import org.enso.compiler.data.BindingsMap.ResolvedName
import org.enso.compiler.pass.IRPass
import scala.collection.mutable
@ -106,33 +107,34 @@ case object AmbiguousImportsAnalysis extends IRPass {
case Some(importTarget) =>
val encounteredErrors: ListBuffer[errors.ImportExport] =
ListBuffer()
val imp =
onlyNames.foldLeft(moduleImport: Import) { case (imp, symbol) =>
val symbolName = symbol.name
importTarget.resolveExportedSymbol(symbolName) match {
case Right(resolvedName) =>
onlyNames.foreach { symbol =>
val symbolName = symbol.name
importTarget.resolveExportedSymbol(symbolName) match {
case Right(resolvedNames) =>
resolvedNames.foreach { resolvedName =>
val symbolPath = resolvedName.qualifiedName.toString
tryAddEncounteredSymbol(
encounteredSymbols,
imp,
moduleImport,
symbolName,
symbolPath
symbolPath,
Some(resolvedName)
) match {
case Left(error) =>
encounteredErrors += error
imp
case Right(imp) => imp
case Right(_) => ()
}
case Left(resolutionError) =>
throw new CompilerError(
s"Unreachable: (should have been resolved in previous passes) $resolutionError"
)
}
}
case Left(resolutionError) =>
throw new CompilerError(
s"Unreachable: (should have been resolved in previous passes) $resolutionError"
)
}
}
if (encounteredErrors.nonEmpty) {
Left(encounteredErrors.toList)
} else {
Right(imp)
Right(moduleImport)
}
case None =>
@ -164,32 +166,32 @@ case object AmbiguousImportsAnalysis extends IRPass {
}
val encounteredErrors: ListBuffer[errors.ImportExport] =
ListBuffer()
val imp =
symbolsToIterate.foldLeft(moduleImport: Import) {
case (imp, symbolName) =>
importTarget.resolveExportedSymbol(symbolName) match {
case Left(resolutionError) =>
throw new CompilerError(
s"Unreachable: (should have been resolved in previous passes) $resolutionError"
)
case Right(resolvedName) =>
tryAddEncounteredSymbol(
encounteredSymbols,
imp,
symbolName,
resolvedName.qualifiedName.toString
) match {
case Left(error) =>
encounteredErrors += error
imp
case Right(imp) => imp
}
symbolsToIterate.foreach { symbolName =>
importTarget.resolveExportedSymbol(symbolName) match {
case Left(resolutionError) =>
throw new CompilerError(
s"Unreachable: (should have been resolved in previous passes) $resolutionError"
)
case Right(List(resolvedName)) =>
tryAddEncounteredSymbol(
encounteredSymbols,
moduleImport,
symbolName,
resolvedName.qualifiedName.toString,
Some(resolvedName)
) match {
case Left(error) =>
encounteredErrors += error
case Right(_) => ()
}
// If the symbolName is resolved to multiple objects, we ignore it.
case Right(_) => ()
}
}
if (encounteredErrors.nonEmpty) {
Left(encounteredErrors.toList)
} else {
Right(imp)
Right(moduleImport)
}
case None =>
@ -213,7 +215,8 @@ case object AmbiguousImportsAnalysis extends IRPass {
encounteredSymbols,
moduleImport,
rename.name,
symbolPath
symbolPath,
None
) match {
case Left(error) => Left(List(error))
case Right(imp) => Right(imp)
@ -235,7 +238,8 @@ case object AmbiguousImportsAnalysis extends IRPass {
encounteredSymbols,
moduleImport,
importPath.parts.last.name,
importPath.name
importPath.name,
None
) match {
case Left(err) => Left(List(err))
case Right(imp) => Right(imp)
@ -252,7 +256,8 @@ case object AmbiguousImportsAnalysis extends IRPass {
encounteredSymbols,
polyImport,
symbolName,
symbolPath
symbolPath,
None
) match {
case Left(err) => Left(List(err))
case Right(imp) => Right(imp)
@ -288,7 +293,8 @@ case object AmbiguousImportsAnalysis extends IRPass {
encounteredSymbols: EncounteredSymbols,
currentImport: Import,
symbolName: String,
symbolPath: String
symbolPath: String,
resolvedName: Option[ResolvedName]
): Either[errors.ImportExport, Import] = {
if (encounteredSymbols.containsSymbol(symbolName)) {
val encounteredFullName =
@ -304,18 +310,31 @@ case object AmbiguousImportsAnalysis extends IRPass {
)
Right(currentImport.addDiagnostic(warn))
} else {
Left(
createErrorForAmbiguousImport(
originalImport,
encounteredFullName,
currentImport,
symbolName,
symbolPath
)
// The symbol was encountered before and the physical path is different.
val ambiguousImpErr = createErrorForAmbiguousImport(
originalImport,
encounteredFullName,
currentImport,
symbolName,
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 {
encounteredSymbols.addSymbol(currentImport, symbolName, symbolPath)
encounteredSymbols.addSymbol(
currentImport,
symbolName,
symbolPath,
resolvedName
)
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,
* along with the entity path. The entity path is vital to decide whether an imported symbol is duplicated
* or ambiguous.
* Note that there are some exceptions that are allowed to be ambiguous, like extension methods.
*/
private class EncounteredSymbols(
private val encounteredSymbols: mutable.Map[
String,
(Import, String)
SymbolTarget
] = mutable.HashMap.empty
) {
@ -370,9 +401,13 @@ case object AmbiguousImportsAnalysis extends IRPass {
def addSymbol(
imp: Import,
symbol: String,
symbolPath: String
symbolPath: String,
resolvedName: Option[ResolvedName]
): Unit = {
encounteredSymbols.put(symbol, (imp, symbolPath))
encounteredSymbols.put(
symbol,
SymbolTarget(symbolPath, resolvedName, imp)
)
}
/** Returns the entity path for the symbol.
@ -381,8 +416,19 @@ case object AmbiguousImportsAnalysis extends IRPass {
symbol: String
): String = {
encounteredSymbols.get(symbol) match {
case Some((_, fullName)) =>
fullName
case Some(symbolTarget) =>
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 =>
throw new IllegalStateException("unreachable")
}
@ -393,7 +439,7 @@ case object AmbiguousImportsAnalysis extends IRPass {
def getOriginalImportForSymbol(
symbol: String
): 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
}
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.ir.expression.{Application, Operator}
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
// arguments.
app.function.getMetadata(MethodCalls) match {
case Some(Resolution(method: ResolvedMethod)) =>
case Some(Resolution(method: ResolvedModuleMethod)) =>
val methodIr = method.unsafeGetIr("Invalid method call resolution.")
val isParallelize = methodIr
.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.MetadataStorage.MetadataPair
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.desugar.{
ComplexType,
@ -60,33 +65,61 @@ case object BindingAnalysis extends IRPass {
val importedPolyglot = ir.imports.collect { case poly: imports.Polyglot =>
BindingsMap.PolyglotSymbol(poly.getVisibleName)
}
val moduleMethods = ir.bindings
.collect { case method: definition.Method.Explicit =>
val staticMethods: List[BindingsMap.Method] = ir.bindings.collect {
case method: definition.Method.Explicit =>
val ref = method.methodReference
ref.typePointer match {
case Some(Name.Qualified(List(), _, _, _)) =>
Some(ref.methodName.name)
Some(ModuleMethod(ref.methodName.name))
case Some(Name.Qualified(List(n), _, _, _)) =>
val shadowed = definedSumTypes.exists(_.name == n.name)
if (!shadowed && n.name == moduleContext.getName().item)
Some(ref.methodName.name)
else None
Some(ModuleMethod(ref.methodName.name))
else {
Some(
StaticMethod(ref.methodName.name, n.name)
)
}
case Some(literal: Name.Literal) =>
val shadowed = definedSumTypes.exists(_.name == literal.name)
if (!shadowed && literal.name == moduleContext.getName().item)
Some(ref.methodName.name)
else None
case None => Some(ref.methodName.name)
Some(ModuleMethod(ref.methodName.name))
else {
Some(
StaticMethod(ref.methodName.name, literal.name)
)
}
case None => Some(ModuleMethod(ref.methodName.name))
case _ => None
}
}
.flatten
.map(BindingsMap.ModuleMethod)
case conversion: definition.Method.Conversion =>
val targetTpNameOpt = conversion.typeName match {
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(
new MetadataPair(
this,
BindingsMap(
definedSumTypes ++ importedPolyglot ++ moduleMethods,
definedSumTypes ++ importedPolyglot ++ staticMethods,
moduleContext.moduleReference()
)
)

View File

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

View File

@ -19,8 +19,8 @@ import org.enso.compiler.data.BindingsMap
import org.enso.compiler.data.BindingsMap.{
Resolution,
ResolutionNotFound,
ResolvedMethod,
ResolvedModule
ResolvedModule,
ResolvedModuleMethod
}
import org.enso.compiler.core.CompilerError
import org.enso.compiler.core.ConstantsNames
@ -122,15 +122,21 @@ case object GlobalNames extends IRPass {
case tp: Definition.Type =>
tp.copy(members =
tp.members.map(
_.mapExpressions(
_.mapExpressions { expr =>
val selfTypeResolution =
bindings.resolveName(tp.name.name) match {
case Right(List(resolvedName)) =>
Some(Resolution(resolvedName))
case _ => None
}
processExpression(
_,
expr,
bindings,
tp.params,
freshNameSupply,
bindings.resolveName(tp.name.name).toOption.map(Resolution)
selfTypeResolution
)
)
}
)
)
@ -181,10 +187,17 @@ case object GlobalNames extends IRPass {
lit,
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) {
lit.updateMetadata(
new MetadataPair(this, BindingsMap.Resolution(r))
new MetadataPair(
this,
BindingsMap.Resolution(resolvedModuleMethod)
)
)
} else {
val self = freshNameSupply
@ -193,14 +206,15 @@ case object GlobalNames extends IRPass {
new MetadataPair(
this,
BindingsMap.Resolution(
BindingsMap.ResolvedModule(mod)
BindingsMap
.ResolvedModule(resolvedModuleMethod.module)
)
)
)
// The synthetic applications gets the location so that instrumentation
// identifies the node correctly
val fun = lit.copy(
name = method.name,
name = resolvedModuleMethod.method.name,
location = None
)
val app = Application.Prefix(
@ -222,9 +236,11 @@ case object GlobalNames extends IRPass {
fun.passData.remove(ExpressionAnnotations)
app
}
case Right(value) =>
lit.updateMetadata(
new MetadataPair(this, BindingsMap.Resolution(value))
case Right(values) =>
values.foldLeft(lit)((lit, value) =>
lit.updateMetadata(
new MetadataPair(this, BindingsMap.Resolution(value))
)
)
}
@ -297,7 +313,7 @@ case object GlobalNames extends IRPass {
)
)
processedFun.getMetadata(this) match {
case Some(Resolution(ResolvedMethod(mod, _))) if !isLocalVar(fun) =>
case Some(Resolution(ResolvedModuleMethod(mod, _))) if !isLocalVar(fun) =>
val self = freshNameSupply
.newName()
.updateMetadata(
@ -408,8 +424,8 @@ case object GlobalNames extends IRPass {
)
.resolveExportedName(consName.name)
resolution match {
case Right(res) => Some(res)
case _ => None
case Right(List(res)) => Some(res)
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.expression.Application
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.analyse.BindingAnalysis
@ -63,9 +67,9 @@ object MethodCalls extends IRPass {
app.function match {
case name: Name if name.isMethod =>
app.arguments match {
case first :: _ =>
case selfArgument :: _ =>
val targetBindings =
first.value.getMetadata(GlobalNames) match {
selfArgument.value.getMetadata(GlobalNames) match {
case Some(Resolution(ResolvedModule(module))) =>
val moduleIr = module.unsafeAsModule().getIr
Option
@ -77,13 +81,23 @@ object MethodCalls extends IRPass {
}
targetBindings match {
case Some(bindings) =>
val resolution =
val resolutionsOpt =
bindings.exportedSymbols.get(name.name)
resolution match {
case Some(List(resolution)) =>
val resolvedModuleMethodOpt = resolutionsOpt match {
case Some(resolutions) =>
resolutions.collectFirst { case x: ResolvedModuleMethod =>
x
}
case None => None
}
resolvedModuleMethodOpt match {
case Some(resolvedModuleMethod) =>
val newName =
name.updateMetadata(
new MetadataPair(this, Resolution(resolution))
new MetadataPair(
this,
Resolution(resolvedModuleMethod)
)
)
val newArgs =
app.arguments.map(

View File

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

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(
expr: Expression,
bindings: BindingsMap,
@ -93,7 +138,8 @@ object Patterns extends IRPass {
qual.parts match {
case (_: Name.SelfType) :: (others :+ item) =>
selfTypeResolution.map(
bindings.resolveQualifiedNameIn(
resolveSingleQualifiedNameIn(
bindings,
_,
others.map(_.name),
item.name
@ -102,11 +148,11 @@ object Patterns extends IRPass {
case _ =>
val parts = qual.parts.map(_.name)
Some(
bindings.resolveQualifiedName(parts)
resolveSingleQualifiedName(bindings, parts)
)
}
case lit: Name.Literal =>
Some(bindings.resolveName(lit.name))
Some(resolveSingleName(bindings, lit.name))
case _: Name.SelfType =>
selfTypeResolution.map(Right(_))
case _ => None
@ -140,14 +186,34 @@ object Patterns extends IRPass {
new MetadataPair(this, BindingsMap.Resolution(value))
)
case Right(_: BindingsMap.ResolvedMethod) =>
case Right(_: BindingsMap.ResolvedModuleMethod) =>
val r = errors.Resolution(
consName,
errors.Resolution.UnexpectedMethod(
"a pattern match"
"method inside pattern match"
)
)
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)
@ -158,7 +224,15 @@ object Patterns extends IRPass {
case BindingsMap.ResolvedModule(_) => 0
case BindingsMap.ResolvedPolyglotSymbol(_, _) => 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(
"Impossible, should be transformed into an error before."
)
@ -186,10 +260,10 @@ object Patterns extends IRPass {
case qual: Name.Qualified =>
val parts = qual.parts.map(_.name)
Some(
bindings.resolveQualifiedName(parts)
resolveSingleQualifiedName(bindings, parts)
)
case lit: Name.Literal =>
Some(bindings.resolveName(lit.name))
Some(resolveSingleName(bindings, lit.name))
case _: Name.SelfType =>
selfTypeResolution.map(Right(_))
case _ => None
@ -223,15 +297,31 @@ object Patterns extends IRPass {
tpeName,
errors.Resolution.UnexpectedPolyglot(s"type pattern case")
)*/
case Right(_: BindingsMap.ResolvedMethod) =>
case Right(_: BindingsMap.ResolvedModuleMethod) =>
errors.Resolution(
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) =>
errors.Resolution(
tpeName,
errors.Resolution.UnexpectedModule(s"type pattern case")
errors.Resolution
.UnexpectedModule(s"module inside type pattern case")
)
}
.getOrElse(tpeName)

View File

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

View File

@ -65,7 +65,7 @@ trait IRUtils {
.flatMap { symbol =>
symbol.getMetadata(MethodCalls).flatMap { resolution =>
resolution.target match {
case BindingsMap.ResolvedMethod(module, _)
case BindingsMap.ResolvedModuleMethod(module, _)
if module.getName == moduleName =>
Some(symbol)
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("defined multiple times"));
private static final Matcher<String> ambiguousResolutionErrorMessageMatcher =
allOf(
containsString("resolved ambiguously to"),
containsString("The symbol was first resolved to"));
@Test
public void twoExtensionMethodsWithSameNameInOneModuleShouldFail() throws IOException {
var src = """
@ -330,7 +335,10 @@ public class ExtensionMethodResolutionTest {
topScope.compile(true);
fail("Expected compilation error: " + out);
} 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);
}
}

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

View File

@ -8,8 +8,11 @@ import org.enso.compiler.data.BindingsMap
import org.enso.compiler.data.BindingsMap.{
Argument,
Cons,
ConversionMethod,
ModuleMethod,
PolyglotSymbol,
ResolvedStaticMethod,
StaticMethod,
Type
}
import org.enso.compiler.pass.analyse.BindingAnalysis
@ -54,6 +57,80 @@ class BindingAnalysisTest extends CompilerTest {
// === The Tests ============================================================
"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 {
implicit val ctx: ModuleContext = mkModuleContext
val ir =
@ -80,6 +157,8 @@ class BindingAnalysisTest extends CompilerTest {
val metadata = ir.unsafeGetMetadata(BindingAnalysis, "Should exist.")
metadata.definedEntities should contain theSameElementsAs List(
PolyglotSymbol("MyClass"),
PolyglotSymbol("Renamed_Class"),
Type(
"Foo",
List(),
@ -110,8 +189,9 @@ class BindingAnalysisTest extends CompilerTest {
),
Type("Bar", List(), List(), builtinType = false),
Type("Baz", List("x", "y"), List(), builtinType = false),
PolyglotSymbol("MyClass"),
PolyglotSymbol("Renamed_Class"),
StaticMethod("foo", "Baz"),
StaticMethod("baz", "Bar"),
ConversionMethod("from", "Bar", "Foo"),
ModuleMethod("foo")
)
metadata.currentModule shouldEqual ctx.moduleReference()
@ -133,8 +213,12 @@ class BindingAnalysisTest extends CompilerTest {
ir.getMetadata(BindingAnalysis)
.get
.definedEntities
.filter(_.isInstanceOf[BindingsMap.ModuleMethod]) shouldEqual List(
ModuleMethod("bar")
.filter(
_.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)
"not resolve uppercase method names to applications with no arguments" in {
val expr = bodyExprs(1)
expr shouldBe an[errors.Resolution]
val x2Expr = bodyExprs(1)
x2Expr shouldBe an[errors.Resolution]
}
"resolve method names to applications" in {
val expr = bodyExprs(2)
expr shouldBe an[Application.Prefix]
val app = expr.asInstanceOf[Application.Prefix]
val x3Expr = bodyExprs(2)
x3Expr shouldBe an[Application.Prefix]
val app = x3Expr.asInstanceOf[Application.Prefix]
app.function.asInstanceOf[Name.Literal].name shouldEqual "constant"
app.arguments.length shouldEqual 1
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 {
val expr = bodyExprs(3)
expr shouldBe an[Application.Prefix]
val app = expr.asInstanceOf[Application.Prefix]
val x4Expr = bodyExprs(3)
x4Expr shouldBe an[Application.Prefix]
val app = x4Expr.asInstanceOf[Application.Prefix]
app.function shouldBe an[errors.Resolution]
}
"resolve method names in applications by adding the self argument" in {
val expr = bodyExprs(4)
expr shouldBe an[Application.Prefix]
val app = expr.asInstanceOf[Application.Prefix]
val x5Expr = bodyExprs(4)
x5Expr shouldBe an[Application.Prefix]
val app = x5Expr.asInstanceOf[Application.Prefix]
app.function.asInstanceOf[Name.Literal].name shouldEqual "add_one"
app.arguments.length shouldEqual 2
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 {
val expr = bodyExprs(5)
expr shouldBe an[Application.Prefix]
val app = expr.asInstanceOf[Application.Prefix]
val yExpr = bodyExprs(5)
yExpr shouldBe an[Application.Prefix]
val app = yExpr.asInstanceOf[Application.Prefix]
app.function.asInstanceOf[Name.Literal].name shouldEqual "add_one"
app.arguments.length shouldEqual 1
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 {
"be detected and reported" in {
implicit val ctx: ModuleContext = mkModuleContext._1

View File

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

View File

@ -162,7 +162,7 @@ object ImportExport {
moduleOrTypeName: String
) extends Reason {
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(

View File

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

View File

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

View File

@ -24,7 +24,13 @@ public final class ModuleScope implements EnsoObject {
private final Map<String, Object> polyglotSymbols;
private final Map<String, Type> types;
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 Set<ImportExportScope> imports;
private final Set<ImportExportScope> exports;
@ -97,24 +103,37 @@ public final class ModuleScope implements EnsoObject {
.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
public Function lookupConversionDefinition(Type original, Type target) {
Function definedWithOriginal =
original.getDefinitionScope().getConversionsFor(target).get(original);
if (definedWithOriginal != null) {
return definedWithOriginal;
public Function lookupConversionDefinition(Type source, Type target) {
Function definedWithSource = source.getDefinitionScope().getConversionsFor(target).get(source);
if (definedWithSource != null) {
return definedWithSource;
}
Function definedWithTarget =
target.getDefinitionScope().getConversionsFor(target).get(original);
Function definedWithTarget = target.getDefinitionScope().getConversionsFor(target).get(source);
if (definedWithTarget != null) {
return definedWithTarget;
}
Function definedHere = getConversionsFor(target).get(original);
Function definedHere = getConversionsFor(target).get(source);
if (definedHere != null) {
return definedHere;
}
return imports.stream()
.map(scope -> scope.getExportedConversion(original, target))
.map(scope -> scope.getExportedConversion(source, target))
.filter(Objects::nonNull)
.findFirst()
.orElse(null);

View File

@ -94,8 +94,6 @@ import org.enso.interpreter.node.{
MethodRootNode,
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.data.atom.{Atom, AtomConstructor}
import org.enso.interpreter.runtime.callable.function.{
@ -404,9 +402,17 @@ class IrToTruffle(
throw new CompilerError(
"Impossible polyglot field, should be caught by MethodDefinitions pass."
)
case _: BindingsMap.ResolvedMethod =>
case _: BindingsMap.ResolvedModuleMethod =>
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(
"Impossible polyglot field, should be caught by MethodDefinitions pass."
)
case _: BindingsMap.ResolvedMethod =>
case _: BindingsMap.ResolvedModuleMethod =>
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,
fun
)
case BindingsMap.ResolvedMethod(module, method) =>
case BindingsMap.ResolvedModuleMethod(module, method) =>
val actualModule = module.unsafeAsModule()
val fun = asScope(actualModule)
.getMethodForType(
@ -995,6 +1009,100 @@ class IrToTruffle(
name,
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.ResolvedPolyglotField(_, _) =>
}
@ -1428,11 +1536,27 @@ class IrToTruffle(
})
case Some(
BindingsMap.Resolution(
BindingsMap.ResolvedMethod(_, _)
BindingsMap.ResolvedModuleMethod(_, _)
)
) =>
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)
case BindingsMap.ResolvedMethod(_, method) =>
case BindingsMap.ResolvedModuleMethod(_, method) =>
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.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 {
private ContextUtils() {}
@ -90,7 +88,6 @@ public final class ContextUtils {
return res;
}
@SuppressWarnings("unchecked")
private static <E extends Throwable> E raise(Class<E> clazz, Throwable t) throws E {
throw (E) t;
@ -150,6 +147,18 @@ public final class ContextUtils {
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.
*

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.Value;
/**
* Utility methods for creating and running Enso projects.
*/
/** Utility methods for creating and running Enso projects. */
public class ProjectUtils {
private ProjectUtils() {}
/**
* 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
@ -42,13 +39,14 @@ public class ProjectUtils {
*
* @param projName Name of the project
* @param modules Set of modules. Must contain `Main` module.
* @param projDir A directory in which the whole project structure will be created.
* Must exist and be a directory.
* @param projDir A directory in which the whole project structure will be created. Must exist and
* be a directory.
*/
public static void createProject(
String projName, Set<SourceModule> modules, Path projDir) throws IOException {
public static void createProject(String projName, Set<SourceModule> modules, Path projDir)
throws IOException {
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 =
"""

View File

@ -2,9 +2,5 @@ package org.enso.test.utils;
import org.enso.pkg.QualifiedName;
/**
* A simple structure corresponding to an Enso module.
*/
public record SourceModule(QualifiedName name, String code) {
}
/** A simple structure corresponding to an Enso module. */
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
* node inside a context, all the other nodes, and insert them via
* {@link #insertChildren(Node...)}.
* node inside a context, all the other nodes, and insert them via {@link #insertChildren(Node...)}.
*/
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
public Object execute(VirtualFrame frame) {
if (callback == null) {