From d463a4363378da11d83eacb5ffb5fcfb5d87c0c7 Mon Sep 17 00:00:00 2001 From: Hubert Plociniczak Date: Wed, 18 Jan 2023 21:19:36 +0100 Subject: [PATCH] Resolve fully qualified names (#4056) Added a separate pass, `FullyQualifiedNames`, that partially resolves fully qualified names. The pass only resolves the library part of the name and replaces it with a reference to the `Main` module. There are 2 scenarios that could be potentially: 1) the code uses a fully qualified name to a component that has been parsed/compiled 2) the code uses a fully qualified name to a component that has **not** be imported For the former case, it is sufficient to just check `PackageRepository` for the presence of the library name. In the latter we have to ensure that the library has been already parsed and all its imports are resolved. That would require the reference to `Compiler` in the `FullyQualifiedNames` pass, which could then trigger a full compilation for missing library. Since it has some undesired consequences (tracking of dependencies becomes rather complex) we decided to exclude that scenario until it is really needed. # Important Notes With this change, one can use a fully qualified name directly. e.g. ``` import Standard.Base main = Standard.Base.IO.println "Hello world!" ``` --- CHANGELOG.md | 2 + .../org/enso/interpreter/EnsoLanguage.java | 3 +- .../scala/org/enso/compiler/Compiler.scala | 3 +- .../org/enso/compiler/PackageRepository.scala | 14 + .../main/scala/org/enso/compiler/Passes.scala | 1 + .../enso/compiler/context/InlineContext.scala | 10 +- .../enso/compiler/context/ModuleContext.scala | 6 +- .../scala/org/enso/compiler/core/IR.scala | 7 + .../enso/compiler/pass/desugar/Imports.scala | 2 +- .../pass/resolve/FullyQualifiedNames.scala | 367 ++++++++++++++++++ .../compiler/pass/resolve/GlobalNames.scala | 100 ++--- .../compiler/phase/BuiltinsIrBuilder.scala | 8 +- .../package.yaml | 6 + .../src/Main.enso | 3 + .../package.yaml | 6 + .../src/Main.enso | 5 + .../test/semantic/ImportsTest.scala | 22 ++ test/Tests/src/Semantic/Names_Spec.enso | 4 + 18 files changed, 512 insertions(+), 57 deletions(-) create mode 100644 engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/FullyQualifiedNames.scala create mode 100644 engine/runtime/src/test/resources/Test_Fully_Qualified_Name_Failure/package.yaml create mode 100644 engine/runtime/src/test/resources/Test_Fully_Qualified_Name_Failure/src/Main.enso create mode 100644 engine/runtime/src/test/resources/Test_Fully_Qualified_Name_Success/package.yaml create mode 100644 engine/runtime/src/test/resources/Test_Fully_Qualified_Name_Success/src/Main.enso diff --git a/CHANGELOG.md b/CHANGELOG.md index 038bf6a1f8..2324c63663 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -517,6 +517,7 @@ - [Introducing Meta.atom_with_hole][4023] - [Report failures in name resolution in type signatures][4030] - [Attach visualizations to sub-expressions][4048] +- [Resolve Fully Qualified Names][4056] [3227]: https://github.com/enso-org/enso/pull/3227 [3248]: https://github.com/enso-org/enso/pull/3248 @@ -602,6 +603,7 @@ [4023]: https://github.com/enso-org/enso/pull/4023 [4030]: https://github.com/enso-org/enso/pull/4030 [4048]: https://github.com/enso-org/enso/pull/4048 +[4056]: https://github.com/enso-org/enso/pull/4056 # Enso 2.0.0-alpha.18 (2021-10-12) diff --git a/engine/runtime/src/main/java/org/enso/interpreter/EnsoLanguage.java b/engine/runtime/src/main/java/org/enso/interpreter/EnsoLanguage.java index 058acdfe5c..a4a62b4db1 100644 --- a/engine/runtime/src/main/java/org/enso/interpreter/EnsoLanguage.java +++ b/engine/runtime/src/main/java/org/enso/interpreter/EnsoLanguage.java @@ -242,11 +242,12 @@ public final class EnsoLanguage extends TruffleLanguage { ); var inlineContext = new InlineContext( module, + redirectConfigWithStrictErrors, scala.Some.apply(localScope), scala.Some.apply(false), scala.Option.empty(), scala.Option.empty(), - redirectConfigWithStrictErrors + scala.Option.empty() ); Compiler silentCompiler = context.getCompiler().duplicateWithConfig(redirectConfigWithStrictErrors); scala.Option exprNode; diff --git a/engine/runtime/src/main/scala/org/enso/compiler/Compiler.scala b/engine/runtime/src/main/scala/org/enso/compiler/Compiler.scala index 3d2436bd1e..f1b6c729f8 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/Compiler.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/Compiler.scala @@ -322,7 +322,8 @@ class Compiler( val moduleContext = ModuleContext( module = module, freshNameSupply = Some(freshNameSupply), - compilerConfig = config + compilerConfig = config, + pkgRepo = Some(packageRepository) ) val compilerOutput = runMethodBodyPasses(module.getIr, moduleContext) module.unsafeSetIr(compilerOutput) diff --git a/engine/runtime/src/main/scala/org/enso/compiler/PackageRepository.scala b/engine/runtime/src/main/scala/org/enso/compiler/PackageRepository.scala index 5016b9d09b..de9bf7d077 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/PackageRepository.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/PackageRepository.scala @@ -53,6 +53,9 @@ trait PackageRepository { libraryName: LibraryName ): Either[PackageRepository.Error, Unit] + /** Checks if the library has already been loaded */ + def isPackageLoaded(libraryName: LibraryName): Boolean + /** Get a sequence of currently loaded packages. */ def getLoadedPackages: Seq[Package[TruffleFile]] @@ -107,6 +110,9 @@ trait PackageRepository { /** Modifies package and module names to reflect the project name change. */ def renameProject(namespace: String, oldName: String, newName: String): Unit + + /** Checks if any library with a given namespace has been registered */ + def isNamespaceRegistered(namespace: String): Boolean } object PackageRepository { @@ -526,6 +532,11 @@ object PackageRepository { } } + /** @inheritdoc */ + def isPackageLoaded(libraryName: LibraryName): Boolean = { + loadedPackages.keySet.contains(libraryName) + } + /** @inheritdoc */ override def getLoadedModules: Seq[Module] = loadedModules.values.toSeq @@ -627,6 +638,9 @@ object PackageRepository { loadedModules.put(module.getName.toString, module) } } + + override def isNamespaceRegistered(namespace: String): Boolean = + loadedPackages.keySet.exists(_.namespace == namespace) } /** Creates a [[PackageRepository]] for the run. diff --git a/engine/runtime/src/main/scala/org/enso/compiler/Passes.scala b/engine/runtime/src/main/scala/org/enso/compiler/Passes.scala index 467abd5ba5..59e2a07680 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/Passes.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/Passes.scala @@ -60,6 +60,7 @@ class Passes( List( ExpressionAnnotations, AliasAnalysis, + FullyQualifiedNames, GlobalNames, TypeNames, MethodCalls, diff --git a/engine/runtime/src/main/scala/org/enso/compiler/context/InlineContext.scala b/engine/runtime/src/main/scala/org/enso/compiler/context/InlineContext.scala index 780a2bb5b3..5b275fad63 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/context/InlineContext.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/context/InlineContext.scala @@ -1,5 +1,6 @@ package org.enso.compiler.context +import org.enso.compiler.PackageRepository import org.enso.compiler.data.CompilerConfig import org.enso.compiler.pass.PassConfiguration import org.enso.interpreter.node.BaseNode.TailStatus @@ -10,20 +11,22 @@ import org.enso.interpreter.runtime.scope.{LocalScope, ModuleScope} * expression. * * @param module the module in which the expression is being executed + * @param compilerConfig the compiler configuration * @param localScope the local scope in which the expression is being executed * @param isInTailPosition whether or not the inline expression occurs in tail * position ([[None]] indicates no information) * @param freshNameSupply the compiler's supply of fresh names * @param passConfiguration the pass configuration - * @param compilerConfig the compiler configuration + * @param pkgRepo the compiler's package repository */ case class InlineContext( module: Module, + compilerConfig: CompilerConfig, localScope: Option[LocalScope] = None, isInTailPosition: Option[Boolean] = None, freshNameSupply: Option[FreshNameSupply] = None, passConfiguration: Option[PassConfiguration] = None, - compilerConfig: CompilerConfig + pkgRepo: Option[PackageRepository] = None ) object InlineContext { @@ -63,7 +66,8 @@ object InlineContext { isInTailPosition = None, freshNameSupply = moduleContext.freshNameSupply, passConfiguration = moduleContext.passConfiguration, - compilerConfig = moduleContext.compilerConfig + compilerConfig = moduleContext.compilerConfig, + pkgRepo = moduleContext.pkgRepo ) } } diff --git a/engine/runtime/src/main/scala/org/enso/compiler/context/ModuleContext.scala b/engine/runtime/src/main/scala/org/enso/compiler/context/ModuleContext.scala index fa76f9f528..a01f683c5e 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/context/ModuleContext.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/context/ModuleContext.scala @@ -1,5 +1,6 @@ package org.enso.compiler.context +import org.enso.compiler.PackageRepository import org.enso.compiler.data.CompilerConfig import org.enso.compiler.pass.PassConfiguration import org.enso.interpreter.runtime.Module @@ -10,11 +11,14 @@ import org.enso.interpreter.runtime.Module * @param freshNameSupply the compiler's supply of fresh names * @param passConfiguration the pass configuration * @param compilerConfig the compiler configuration + * @param isGeneratingDocs if true, should generate docs for IR + * @param pkgRepo the compiler's package repository */ case class ModuleContext( module: Module, + compilerConfig: CompilerConfig, freshNameSupply: Option[FreshNameSupply] = None, passConfiguration: Option[PassConfiguration] = None, isGeneratingDocs: Boolean = false, - compilerConfig: CompilerConfig + pkgRepo: Option[PackageRepository] = None ) diff --git a/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala b/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala index 8f6b4713b2..b4c07897f8 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/core/IR.scala @@ -7395,6 +7395,13 @@ object IR { } } + + case class MissingLibraryImportInFQNError(namespace: String) + extends Reason { + override def explain(originalName: IR.Name): String = + s"Fully qualified name references a library $namespace.${originalName.name} but an import statement for it is missing." + } + } /** A representation of an error resulting from wrong pattern matches. diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/Imports.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/Imports.scala index 160fe39e31..765173b214 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/Imports.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/desugar/Imports.scala @@ -22,7 +22,7 @@ case object Imports extends IRPass { /** The passes that are invalidated by running this pass. */ override val invalidatedPasses: Seq[IRPass] = Seq() - private val mainModuleName = + val mainModuleName = IR.Name.Literal( "Main", isMethod = false, diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/FullyQualifiedNames.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/FullyQualifiedNames.scala new file mode 100644 index 0000000000..d9ac323bff --- /dev/null +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/FullyQualifiedNames.scala @@ -0,0 +1,367 @@ +package org.enso.compiler.pass.resolve + +import org.enso.compiler.{Compiler, PackageRepository} +import org.enso.compiler.context.{FreshNameSupply, InlineContext, ModuleContext} +import org.enso.compiler.core.IR +import org.enso.compiler.core.IR.Error.Resolution.MissingLibraryImportInFQNError +import org.enso.compiler.core.ir.MetadataStorage.ToPair +import org.enso.compiler.data.BindingsMap +import org.enso.compiler.data.BindingsMap.{ModuleReference, Resolution} +import org.enso.compiler.exception.CompilerError +import org.enso.compiler.pass.IRPass +import org.enso.compiler.pass.analyse.{AliasAnalysis, BindingAnalysis} +import org.enso.compiler.pass.desugar.Imports +import org.enso.editions.LibraryName + +/** Partially resolves fully qualified names corresponding to the library names + * + * 1. Identifies potential library names e.g., `Standard.Base` + * 2. If the component has not be compiled yet, compilation is triggered + * 3. Replaces the library name with a fresh name and a resolved Main module + */ +case object FullyQualifiedNames extends IRPass { + + /** The type of the metadata object that the pass writes to the IR. */ + override type Metadata = FullyQualifiedNames.FQNResolution + + /** The type of configuration for the pass. */ + override type Config = IRPass.Configuration.Default + + /** The passes that this pass depends _directly_ on to run. */ + override val precursorPasses: Seq[IRPass] = + Seq(AliasAnalysis, BindingAnalysis) + + /** The passes that are invalidated by running this pass. */ + override val invalidatedPasses: Seq[IRPass] = Nil + + /** Executes the pass on the provided `ir`, and returns a possibly transformed + * or annotated version of `ir`. + * + * @param ir the Enso IR to process + * @param moduleContext a context object that contains the information needed + * to process a module + * @return `ir`, possibly having made transformations or annotations to that + * IR. + */ + override def runModule( + ir: IR.Module, + moduleContext: ModuleContext + ): IR.Module = { + val scopeMap = ir.unsafeGetMetadata( + BindingAnalysis, + "No binding analysis on the module" + ) + val freshNameSupply = moduleContext.freshNameSupply.getOrElse( + throw new CompilerError( + "No fresh name supply passed to UppercaseNames resolver." + ) + ) + val new_bindings = + ir.bindings.map( + processModuleDefinition( + _, + scopeMap, + freshNameSupply, + moduleContext.pkgRepo + ) + ) + ir.copy(bindings = new_bindings) + + } + + /** Executes the pass on the provided `ir`, and returns a possibly transformed + * or annotated version of `ir` in an inline context. + * + * @param ir the Enso IR to process + * @param inlineContext a context object that contains the information needed + * for inline evaluation + * @return `ir`, possibly having made transformations or annotations to that + * IR. + */ + override def runExpression( + ir: IR.Expression, + inlineContext: InlineContext + ): IR.Expression = { + val scopeMap = inlineContext.module.getIr.unsafeGetMetadata( + BindingAnalysis, + "No binding analysis on the module" + ) + val freshNameSupply = inlineContext.freshNameSupply.getOrElse( + throw new CompilerError( + "No fresh name supply passed to UppercaseNames resolver." + ) + ) + processExpression( + ir, + scopeMap, + freshNameSupply, + None, + inlineContext.pkgRepo + ) + + } + + private def processModuleDefinition( + definition: IR.Module.Scope.Definition, + bindings: BindingsMap, + freshNameSupply: FreshNameSupply, + pkgRepo: Option[PackageRepository] + ): IR.Module.Scope.Definition = { + definition match { + case asc: IR.Type.Ascription => asc + case method: IR.Module.Scope.Definition.Method => + val resolution = method.methodReference.typePointer.flatMap( + _.getMetadata(MethodDefinitions) + ) + method.mapExpressions( + processExpression(_, bindings, freshNameSupply, resolution, pkgRepo) + ) + case tp: IR.Module.Scope.Definition.Type => + tp.copy(members = + tp.members.map( + _.mapExpressions( + processExpression( + _, + bindings, + freshNameSupply, + bindings.resolveName(tp.name.name).toOption.map(Resolution), + pkgRepo + ) + ) + ) + ) + + case a => + a.mapExpressions( + processExpression(_, bindings, freshNameSupply, None, pkgRepo) + ) + } + } + + private def processExpression( + ir: IR.Expression, + bindings: BindingsMap, + freshNameSupply: FreshNameSupply, + selfTypeResolution: Option[Resolution], + pkgRepo: Option[PackageRepository] + ): IR.Expression = + ir.transformExpressions { + case lit: IR.Name.Literal => + if (!lit.isMethod && !isLocalVar(lit)) { + val resolution = bindings.resolveName(lit.name) + resolution match { + case Left(_) => + if ( + pkgRepo + .map(_.isNamespaceRegistered(lit.name)) + .getOrElse(false) + ) { + lit.updateMetadata( + this -->> FQNResolution(ResolvedLibrary(lit.name)) + ) + } else { + lit + } + case Right(_) => + lit + } + } else { + lit + } + case app @ IR.Application.Prefix(_, List(_), _, _, _, _) => + app.function match { + case lit: IR.Name.Literal => + if (lit.isMethod) + resolveLocalApplication( + app, + bindings, + freshNameSupply, + pkgRepo, + selfTypeResolution + ) + else + app.mapExpressions( + processExpression( + _, + bindings, + freshNameSupply, + selfTypeResolution, + pkgRepo + ) + ) + case _ => + app.mapExpressions( + processExpression( + _, + bindings, + freshNameSupply, + selfTypeResolution, + pkgRepo + ) + ) + + } + + } + + private def resolveLocalApplication( + app: IR.Application.Prefix, + bindings: BindingsMap, + freshNameSupply: FreshNameSupply, + pkgRepo: Option[PackageRepository], + selfTypeResolution: Option[Resolution] + ): IR.Expression = { + val processedFun = + processExpression( + app.function, + bindings, + freshNameSupply, + selfTypeResolution, + pkgRepo + ) + val processedArgs = + app.arguments.map( + _.mapExpressions( + processExpression( + _, + bindings, + freshNameSupply, + selfTypeResolution, + pkgRepo + ) + ) + ) + + val processedApp = processedArgs match { + case List(thisArg) => + (thisArg.value.getMetadata(this).map(_.target), processedFun) match { + case (Some(resolved @ ResolvedLibrary(_)), name: IR.Name.Literal) => + resolveQualName(resolved, name, pkgRepo).fold( + err => Some(err), + _.map(resolvedMod => + freshNameSupply + .newName() + .updateMetadata(this -->> resolvedMod) + .setLocation(name.location) + ) + ) + case _ => + None + } + case _ => + None + } + + processedApp.getOrElse( + app.copy(function = processedFun, arguments = processedArgs) + ) + } + + private def resolveQualName( + thisResolution: ResolvedLibrary, + consName: IR.Name.Literal, + optPkgRepo: Option[PackageRepository] + ): Either[IR.Expression, Option[FQNResolution]] = { + optPkgRepo + .flatMap { pkgRepo => + val libName = LibraryName(thisResolution.namespace, consName.name) + if (pkgRepo.isPackageLoaded(libName)) { + pkgRepo + .getLoadedModule( + s"${libName.toString}.${Imports.mainModuleName.name}" + ) + .map { m => + if (m.getIr == null) { + // Limitation of Fully Qualified Names: + // If the library has not been imported explicitly, then we won't have + // IR for it. Triggering a full compilation at this stage may have + // undesired consequences and is therefore prohibited on purpose. + Left( + IR.Error.Resolution( + consName, + MissingLibraryImportInFQNError(thisResolution.namespace) + ) + ) + } else { + Right( + Some( + FQNResolution(ResolvedModule(ModuleReference.Concrete(m))) + ) + ) + } + } + } else { + Some( + Left( + IR.Error.Resolution( + consName, + MissingLibraryImportInFQNError(thisResolution.namespace) + ) + ) + ) + } + } + .getOrElse(Right(None)) + } + + /** Updates the metadata in a copy of the IR when updating that metadata + * requires global state. + * + * This is usually the case in the presence of structures that are shared + * throughout the IR, and need to maintain that sharing for correctness. This + * must be called with `copyOfIr` as the result of an `ir.duplicate` call. + * + * Additionally this method _must not_ alter the structure of the IR. It + * should only update its metadata. + * + * @param sourceIr the IR being copied + * @param copyOfIr a duplicate of `sourceIr` + * @tparam T the concrete [[IR]] type + * @return the result of updating metadata in `copyOfIr` globally using + * information from `sourceIr` + */ + override def updateMetadataInDuplicate[T <: IR](sourceIr: T, copyOfIr: T): T = + copyOfIr + + private def isLocalVar(name: IR.Name.Literal): Boolean = { + val aliasInfo = name + .unsafeGetMetadata( + AliasAnalysis, + "no alias analysis info on a name" + ) + .unsafeAs[AliasAnalysis.Info.Occurrence] + val defLink = aliasInfo.graph.defLinkFor(aliasInfo.id) + defLink.isDefined + } + + /** The FQN resolution metadata for a node. + * + * @param target the partially resolved name + */ + sealed case class FQNResolution(target: PartiallyResolvedFQN) + extends IRPass.Metadata { + + override val metadataName: String = + "FullyQualifiedNames.Resolution" + + /** @inheritdoc */ + override def prepareForSerialization(compiler: Compiler): FQNResolution = + this + + /** @inheritdoc */ + override def restoreFromSerialization( + compiler: Compiler + ): Option[FQNResolution] = + Some(this) + + /** @inheritdoc */ + override def duplicate(): Option[IRPass.Metadata] = Some(this) + } + + sealed trait PartiallyResolvedFQN + + case class ResolvedLibrary(namespace: String) extends PartiallyResolvedFQN + case class ResolvedModule(moduleRef: ModuleReference) + extends PartiallyResolvedFQN + +} diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/GlobalNames.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/GlobalNames.scala index c1b5272f54..8d1f68addf 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/GlobalNames.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/GlobalNames.scala @@ -7,7 +7,8 @@ import org.enso.compiler.data.BindingsMap import org.enso.compiler.data.BindingsMap.{ Resolution, ResolutionNotFound, - ResolvedMethod + ResolvedMethod, + ResolvedModule } import org.enso.compiler.exception.CompilerError import org.enso.compiler.pass.IRPass @@ -36,7 +37,7 @@ case object GlobalNames extends IRPass { /** The passes that this pass depends _directly_ on to run. */ override val precursorPasses: Seq[IRPass] = - Seq(AliasAnalysis, BindingAnalysis) + Seq(AliasAnalysis, BindingAnalysis, FullyQualifiedNames) /** The passes that are invalidated by running this pass. */ override val invalidatedPasses: Seq[IRPass] = Seq(AliasAnalysis) @@ -150,53 +151,62 @@ case object GlobalNames extends IRPass { ) ) case lit: IR.Name.Literal => - if (!lit.isMethod && !isLocalVar(lit)) { - val resolution = bindings.resolveName(lit.name) - resolution match { - case Left(error) => - IR.Error.Resolution( - lit, - IR.Error.Resolution.ResolverError(error) - ) - case Right(r @ BindingsMap.ResolvedMethod(mod, method)) => - if (isInsideApplication) { - lit.updateMetadata(this -->> BindingsMap.Resolution(r)) - } else { - val self = freshNameSupply - .newName() - .updateMetadata( - this -->> BindingsMap.Resolution( - BindingsMap.ResolvedModule(mod) - ) - ) - // The synthetic applications gets the location so that instrumentation - // identifies the node correctly - val fun = lit.copy( - name = method.name, - location = None + lit.getMetadata(FullyQualifiedNames) match { + case Some( + FullyQualifiedNames.FQNResolution( + FullyQualifiedNames.ResolvedModule(modRef) ) - val app = IR.Application.Prefix( - fun, - List(IR.CallArgument.Specified(None, self, None)), - hasDefaultsSuspended = false, - lit.location - ) - fun - .getMetadata(ExpressionAnnotations) - .foreach(annotationsMeta => - app.updateMetadata( - ExpressionAnnotations -->> annotationsMeta - ) + ) => + lit.updateMetadata(this -->> Resolution(ResolvedModule(modRef))) + case _ => + if (!lit.isMethod && !isLocalVar(lit)) { + val resolution = bindings.resolveName(lit.name) + resolution match { + case Left(error) => + IR.Error.Resolution( + lit, + IR.Error.Resolution.ResolverError(error) ) - fun.passData.remove(ExpressionAnnotations) - app + case Right(r @ BindingsMap.ResolvedMethod(mod, method)) => + if (isInsideApplication) { + lit.updateMetadata(this -->> BindingsMap.Resolution(r)) + } else { + val self = freshNameSupply + .newName() + .updateMetadata( + this -->> BindingsMap.Resolution( + BindingsMap.ResolvedModule(mod) + ) + ) + // The synthetic applications gets the location so that instrumentation + // identifies the node correctly + val fun = lit.copy( + name = method.name, + location = None + ) + val app = IR.Application.Prefix( + fun, + List(IR.CallArgument.Specified(None, self, None)), + hasDefaultsSuspended = false, + lit.location + ) + fun + .getMetadata(ExpressionAnnotations) + .foreach(annotationsMeta => + app.updateMetadata( + ExpressionAnnotations -->> annotationsMeta + ) + ) + fun.passData.remove(ExpressionAnnotations) + app + } + case Right(value) => + lit.updateMetadata(this -->> BindingsMap.Resolution(value)) } - case Right(value) => - lit.updateMetadata(this -->> BindingsMap.Resolution(value)) - } - } else { - lit + } else { + lit + } } case app: IR.Application.Prefix => app.function match { diff --git a/engine/runtime/src/main/scala/org/enso/compiler/phase/BuiltinsIrBuilder.scala b/engine/runtime/src/main/scala/org/enso/compiler/phase/BuiltinsIrBuilder.scala index af3c7c2e79..e80d142ec8 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/phase/BuiltinsIrBuilder.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/phase/BuiltinsIrBuilder.scala @@ -8,8 +8,6 @@ import org.enso.interpreter.runtime.Module import org.enso.interpreter.runtime.Module.CompilationStage import org.enso.syntax.text.Parser -import scala.annotation.unused - /** A phase responsible for initializing the builtins' IR from the provided * source. */ @@ -30,9 +28,9 @@ object BuiltinsIrBuilder { * @param passes the compiler's pass manager */ def build( - @unused module: Module, - @unused freshNameSupply: FreshNameSupply, - @unused passes: Passes + module: Module, + freshNameSupply: FreshNameSupply, + passes: Passes ): Unit = { val passManager = passes.passManager val moduleContext = ModuleContext( diff --git a/engine/runtime/src/test/resources/Test_Fully_Qualified_Name_Failure/package.yaml b/engine/runtime/src/test/resources/Test_Fully_Qualified_Name_Failure/package.yaml new file mode 100644 index 0000000000..ba096676c0 --- /dev/null +++ b/engine/runtime/src/test/resources/Test_Fully_Qualified_Name_Failure/package.yaml @@ -0,0 +1,6 @@ +name: Test_Fully_Qualified_Name_Failure +license: APLv2 +enso-version: default +version: "0.0.1" +author: "Enso Team " +maintainer: "Enso Team " diff --git a/engine/runtime/src/test/resources/Test_Fully_Qualified_Name_Failure/src/Main.enso b/engine/runtime/src/test/resources/Test_Fully_Qualified_Name_Failure/src/Main.enso new file mode 100644 index 0000000000..18ad2e0d2b --- /dev/null +++ b/engine/runtime/src/test/resources/Test_Fully_Qualified_Name_Failure/src/Main.enso @@ -0,0 +1,3 @@ +main = + Standard.Base.IO.println "Hello world!" + 0 diff --git a/engine/runtime/src/test/resources/Test_Fully_Qualified_Name_Success/package.yaml b/engine/runtime/src/test/resources/Test_Fully_Qualified_Name_Success/package.yaml new file mode 100644 index 0000000000..d59f6a365b --- /dev/null +++ b/engine/runtime/src/test/resources/Test_Fully_Qualified_Name_Success/package.yaml @@ -0,0 +1,6 @@ +name: Test_Fully_Qualified_Name_Success +license: APLv2 +enso-version: default +version: "0.0.1" +author: "Enso Team " +maintainer: "Enso Team " diff --git a/engine/runtime/src/test/resources/Test_Fully_Qualified_Name_Success/src/Main.enso b/engine/runtime/src/test/resources/Test_Fully_Qualified_Name_Success/src/Main.enso new file mode 100644 index 0000000000..0e4e58cbfe --- /dev/null +++ b/engine/runtime/src/test/resources/Test_Fully_Qualified_Name_Success/src/Main.enso @@ -0,0 +1,5 @@ +import Standard.Base + +main = + Standard.Base.IO.println "Hello world!" + 0 diff --git a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/ImportsTest.scala b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/ImportsTest.scala index a742e62d09..2271d37a76 100644 --- a/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/ImportsTest.scala +++ b/engine/runtime/src/test/scala/org/enso/interpreter/test/semantic/ImportsTest.scala @@ -158,4 +158,26 @@ class ImportsTest extends PackageTest { "Constructors" should "be exportable" in { evalTestProject("Test_Type_Exports").toString shouldEqual "(Some 10)" } + + "Fully qualified names" should "not be resolved when lacking imports" in { + the[InterpreterException] thrownBy evalTestProject( + "Test_Fully_Qualified_Name_Failure" + ) should have message "Compilation aborted due to errors." + + val outLines = consumeOut + outLines should have length 3 + outLines( + 2 + ) shouldEqual "Main.enso[2:14-2:17]: Fully qualified name references a library Standard.Base but an import statement for it is missing." + } + + "Fully qualified names" should "be resolved when library has already been loaded" in { + evalTestProject( + "Test_Fully_Qualified_Name_Success" + ).toString shouldEqual "0" + val outLines = consumeOut + outLines should have length 1 + outLines(0) shouldEqual "Hello world!" + } + } diff --git a/test/Tests/src/Semantic/Names_Spec.enso b/test/Tests/src/Semantic/Names_Spec.enso index 0e40ebd04a..d73b058cec 100644 --- a/test/Tests/src/Semantic/Names_Spec.enso +++ b/test/Tests/src/Semantic/Names_Spec.enso @@ -50,3 +50,7 @@ spec = Test.specify "should be allowed to be called statically" pending="Needs changes to method dispatch logic" <| b = Bar.Value 1 Bar.meh b 2 . should_equal 3 + Test.group "Fully Qualified Names" <| + Test.specify "should be correctly resolved" <| + a = Standard.Base.Data.Array.Array.new 10 + a.length . should_equal 10