diff --git a/build.sbt b/build.sbt index 7df8079680..f563a34556 100644 --- a/build.sbt +++ b/build.sbt @@ -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`, diff --git a/docs/syntax/functions.md b/docs/syntax/functions.md index a043703d4b..27fabfca70 100644 --- a/docs/syntax/functions.md +++ b/docs/syntax/functions.md @@ -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 diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/dump/IRDumper.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/dump/IRDumper.java index 3c34d72695..9b838d29e1 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/dump/IRDumper.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/dump/IRDumper.java @@ -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); diff --git a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PrivateSymbolsAnalysis.java b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PrivateSymbolsAnalysis.java index 45e8c34c99..df86dfcd74 100644 --- a/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PrivateSymbolsAnalysis.java +++ b/engine/runtime-compiler/src/main/java/org/enso/compiler/pass/analyse/PrivateSymbolsAnalysis.java @@ -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 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(); }; } diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/data/BindingsMap.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/data/BindingsMap.scala index caba60aa7c..472e777ce6 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/data/BindingsMap.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/data/BindingsMap.scala @@ -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}" } diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/AmbiguousImportsAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/AmbiguousImportsAnalysis.scala index d29b67fea5..5014c99674 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/AmbiguousImportsAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/AmbiguousImportsAnalysis.scala @@ -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) } } } diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/AutomaticParallelism.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/AutomaticParallelism.scala index add711ac08..a2c2d9e09f 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/AutomaticParallelism.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/AutomaticParallelism.scala @@ -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) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/BindingAnalysis.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/BindingAnalysis.scala index 8f8899f4cb..ecc72411c3 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/BindingAnalysis.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/analyse/BindingAnalysis.scala @@ -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() ) ) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/FullyQualifiedNames.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/FullyQualifiedNames.scala index a7f17ae4b0..1d60a44120 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/FullyQualifiedNames.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/FullyQualifiedNames.scala @@ -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 ) - ) + }) ) ) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/GlobalNames.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/GlobalNames.scala index 146c4f0583..2ef38afb3e 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/GlobalNames.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/GlobalNames.scala @@ -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 } diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/MethodCalls.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/MethodCalls.scala index 1293aeaea8..e45c98cb67 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/MethodCalls.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/MethodCalls.scala @@ -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( diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/MethodDefinitions.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/MethodDefinitions.scala index c6614db7d8..1d052ae2fb 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/MethodDefinitions.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/MethodDefinitions.scala @@ -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 _ => diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/Patterns.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/Patterns.scala index 03b215518c..8107959abb 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/Patterns.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/Patterns.scala @@ -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) diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/TypeNames.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/TypeNames.scala index 24f5db9ee2..861deb8600 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/TypeNames.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/pass/resolve/TypeNames.scala @@ -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)), diff --git a/engine/runtime-compiler/src/main/scala/org/enso/compiler/refactoring/IRUtils.scala b/engine/runtime-compiler/src/main/scala/org/enso/compiler/refactoring/IRUtils.scala index 9fb2fea5f8..f0468342a7 100644 --- a/engine/runtime-compiler/src/main/scala/org/enso/compiler/refactoring/IRUtils.scala +++ b/engine/runtime-compiler/src/main/scala/org/enso/compiler/refactoring/IRUtils.scala @@ -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 _ => diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/compiler/passes/MethodCallsTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/passes/MethodCallsTest.java new file mode 100644 index 0000000000..ae14fdd6e5 --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/compiler/passes/MethodCallsTest.java @@ -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(); + } +} diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/ExtensionMethodResolutionTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/ExtensionMethodResolutionTest.java index d28e6d9dd0..3a59af63ab 100644 --- a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/ExtensionMethodResolutionTest.java +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/ExtensionMethodResolutionTest.java @@ -36,6 +36,11 @@ public class ExtensionMethodResolutionTest { containsString("Method overloads are not supported"), containsString("defined multiple times")); + private static final Matcher 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); } } diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/exports/ExportExtensionMethodTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/exports/ExportExtensionMethodTest.java new file mode 100644 index 0000000000..f795d0f405 --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/exports/ExportExtensionMethodTest.java @@ -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")); + } + } +} diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/exports/ExportStaticMethodTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/exports/ExportStaticMethodTest.java new file mode 100644 index 0000000000..8506ee78d9 --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/exports/ExportStaticMethodTest.java @@ -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")); + } + } +} diff --git a/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/scope/ModuleScopeTest.java b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/scope/ModuleScopeTest.java new file mode 100644 index 0000000000..2f8eb30e31 --- /dev/null +++ b/engine/runtime-integration-tests/src/test/java/org/enso/interpreter/test/scope/ModuleScopeTest.java @@ -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())); + } + } +} diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/refactoring/IRUtilsTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/refactoring/IRUtilsTest.scala index f439ece0b1..3820d82525 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/refactoring/IRUtilsTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/refactoring/IRUtilsTest.scala @@ -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 { diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/BindingAnalysisTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/BindingAnalysisTest.scala index b1b9e2743d..3d61b6a413 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/BindingAnalysisTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/analyse/BindingAnalysisTest.scala @@ -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) ) } diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/resolve/GlobalNamesTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/resolve/GlobalNamesTest.scala index d2ddf6a4c1..1669d6397c 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/resolve/GlobalNamesTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/pass/resolve/GlobalNamesTest.scala @@ -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 diff --git a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/semantic/ImportExportTest.scala b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/semantic/ImportExportTest.scala index 6a52daa0e0..75054eace9 100644 --- a/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/semantic/ImportExportTest.scala +++ b/engine/runtime-integration-tests/src/test/scala/org/enso/compiler/test/semantic/ImportExportTest.scala @@ -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" } diff --git a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/expression/errors/ImportExport.scala b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/expression/errors/ImportExport.scala index 4034d58ce2..dc0c9b1572 100644 --- a/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/expression/errors/ImportExport.scala +++ b/engine/runtime-parser/src/main/scala/org/enso/compiler/core/ir/expression/errors/ImportExport.scala @@ -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( diff --git a/engine/runtime-suggestions/src/main/scala/org/enso/compiler/suggestions/ExportsBuilder.scala b/engine/runtime-suggestions/src/main/scala/org/enso/compiler/suggestions/ExportsBuilder.scala index 93db2c9f1c..eb1ce1e002 100644 --- a/engine/runtime-suggestions/src/main/scala/org/enso/compiler/suggestions/ExportsBuilder.scala +++ b/engine/runtime-suggestions/src/main/scala/org/enso/compiler/suggestions/ExportsBuilder.scala @@ -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) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/caches/ImportExportCache.java b/engine/runtime/src/main/java/org/enso/interpreter/caches/ImportExportCache.java index 75f86ce885..410059232f 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/caches/ImportExportCache.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/caches/ImportExportCache.java @@ -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 { public PersistBindingsMap() { diff --git a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java index 5572cd505e..6149ee178d 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/runtime/scope/ModuleScope.java @@ -24,7 +24,13 @@ public final class ModuleScope implements EnsoObject { private final Map polyglotSymbols; private final Map types; private final Map>> methods; + + /** + * First key is target type, second key is source type. The value is the conversion function from + * source to target. + */ private final Map> conversions; + private final Set imports; private final Set 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: + * + *
+   *   Target_Type.from (other : Source_Type) = ...
+   * 
+ * + * 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); diff --git a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala index fc6ddf9f78..0a36ec02b4 100644 --- a/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala +++ b/engine/runtime/src/main/scala/org/enso/interpreter/runtime/IrToTruffle.scala @@ -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" ) } } diff --git a/lib/java/test-utils/src/main/java/org/enso/test/utils/ContextUtils.java b/lib/java/test-utils/src/main/java/org/enso/test/utils/ContextUtils.java index 63b2511ffc..744a78b205 100644 --- a/lib/java/test-utils/src/main/java/org/enso/test/utils/ContextUtils.java +++ b/lib/java/test-utils/src/main/java/org/enso/test/utils/ContextUtils.java @@ -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 raise(Class 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. * diff --git a/lib/java/test-utils/src/main/java/org/enso/test/utils/ModuleUtils.java b/lib/java/test-utils/src/main/java/org/enso/test/utils/ModuleUtils.java new file mode 100644 index 0000000000..5161ce2146 --- /dev/null +++ b/lib/java/test-utils/src/main/java/org/enso/test/utils/ModuleUtils.java @@ -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> getExportedSymbolsFromModule( + Context ctx, String modName) { + var ensoCtx = ContextUtils.leakContext(ctx); + var mod = ensoCtx.getPackageRepository().getLoadedModule(modName).get(); + return getExportedSymbols(mod); + } + + public static List 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> getExportedSymbols(Module module) { + var bindings = new HashMap>(); + 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; + } +} diff --git a/lib/java/test-utils/src/main/java/org/enso/test/utils/ProjectUtils.java b/lib/java/test-utils/src/main/java/org/enso/test/utils/ProjectUtils.java index 26f5c3e0fd..878d0b663b 100644 --- a/lib/java/test-utils/src/main/java/org/enso/test/utils/ProjectUtils.java +++ b/lib/java/test-utils/src/main/java/org/enso/test/utils/ProjectUtils.java @@ -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 modules, Path projDir) throws IOException { + public static void createProject(String projName, Set 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 = """ diff --git a/lib/java/test-utils/src/main/java/org/enso/test/utils/SourceModule.java b/lib/java/test-utils/src/main/java/org/enso/test/utils/SourceModule.java index 4b19fbba13..5cfc8aa271 100644 --- a/lib/java/test-utils/src/main/java/org/enso/test/utils/SourceModule.java +++ b/lib/java/test-utils/src/main/java/org/enso/test/utils/SourceModule.java @@ -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) {} diff --git a/lib/java/test-utils/src/main/java/org/enso/test/utils/TestRootNode.java b/lib/java/test-utils/src/main/java/org/enso/test/utils/TestRootNode.java index c97e1d7c64..19d6be3046 100644 --- a/lib/java/test-utils/src/main/java/org/enso/test/utils/TestRootNode.java +++ b/lib/java/test-utils/src/main/java/org/enso/test/utils/TestRootNode.java @@ -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) {