Fix Meta.get_qualified_type_name when run as single file (#11401)

`Meta.get_qualified_type_name` correctly returns fully qualified type name when running a single file from a project with `enso --run Proj/src/Main.enso`.
This commit is contained in:
Pavel Marek 2024-10-31 16:25:45 +01:00 committed by GitHub
parent aad1107a8e
commit 536a49f35d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 700 additions and 562 deletions

View File

@ -1,5 +1,8 @@
package org.enso.interpreter.caches;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayOutputStream;
@ -29,9 +32,13 @@ public class HelloWorldCacheTest {
// the second run must read Hello_World from its .ir file!
var secondMsgs = executeOnce(helloWorld);
assertTrue("Contains hello world:\n" + secondMsgs, secondMsgs.contains("Hello World"));
assertTrue(
assertThat(
"Properly deserialized:\n" + secondMsgs,
secondMsgs.contains("Deserializing module Hello_World from IR file: true"));
secondMsgs,
allOf(
containsString("Deserializing module"),
containsString("Hello_World"),
containsString("from IR file: true")));
}
private static String executeOnce(File src) throws Exception {

View File

@ -0,0 +1,37 @@
package org.enso.interpreter.test.meta;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import java.io.IOException;
import org.enso.test.utils.ProjectUtils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
public class QualifiedNameTest {
@Rule public TemporaryFolder temporaryFolder = new TemporaryFolder();
private static final String mainModSrc =
"""
from Standard.Base import all
type My_Type
Value x
main =
obj = My_Type.Value 42
Meta.get_qualified_type_name obj
""";
@Test
public void qualifiedTypeNameWorks_WhenRunningSingleFile() throws IOException {
var projDir = temporaryFolder.newFolder().toPath();
ProjectUtils.createProject("Proj", mainModSrc, projDir);
ProjectUtils.testProjectRun(
projDir,
(res) -> {
assertThat(res.asString(), is("local.Proj.Main.My_Type"));
});
}
}

View File

@ -22,23 +22,19 @@ class IRUtilsTest extends AnyWordSpecLike with Matchers with OptionValues {
.invokeMember(MethodNames.TopScope.LEAK_CONTEXT)
.asHostObject[EnsoContext]()
private val moduleName = QualifiedName.simpleName("Test")
implicit private class PreprocessModule(code: String) {
private val Module = QualifiedName(List("Unnamed"), "Test")
def preprocessModule(name: QualifiedName): Module = {
def preprocessModule(): Module = {
val module = new runtime.Module(
name,
moduleName,
null,
code.stripMargin.linesIterator.mkString("\n")
)
langCtx.getCompiler.run(module.asCompilerModule())
module.getIr
}
def preprocessModule: Module =
preprocessModule(Module)
}
private def findUsagesOfLiteral(
@ -86,7 +82,7 @@ class IRUtilsTest extends AnyWordSpecLike with Matchers with OptionValues {
|[]
|""".stripMargin
val module = code.preprocessModule
val module = code.preprocessModule()
val operator1 = IRUtils.findByExternalId(module, uuid1).get
val usages = findUsagesOfLiteral(module, operator1)
@ -111,7 +107,7 @@ class IRUtilsTest extends AnyWordSpecLike with Matchers with OptionValues {
|[]
|""".stripMargin
val module = code.preprocessModule
val module = code.preprocessModule()
val operator1 = IRUtils.findByExternalId(module, uuid1).get
val usages = findUsagesOfLiteral(module, operator1)
@ -136,7 +132,7 @@ class IRUtilsTest extends AnyWordSpecLike with Matchers with OptionValues {
|[]
|""".stripMargin
val module = code.preprocessModule
val module = code.preprocessModule()
val operator1 = IRUtils.findByExternalId(module, uuid1).get
val usages = findUsagesOfLiteral(module, operator1)
@ -148,8 +144,7 @@ class IRUtilsTest extends AnyWordSpecLike with Matchers with OptionValues {
}
"find usages of a static method call in main body" in {
val uuid1 = new UUID(0, 1)
val moduleName = QualifiedName(List("Unnamed"), "Test")
val uuid1 = new UUID(0, 1)
val code =
s"""function1 x = x + 1
|
@ -164,7 +159,7 @@ class IRUtilsTest extends AnyWordSpecLike with Matchers with OptionValues {
|[]
|""".stripMargin
val module = code.preprocessModule(moduleName)
val module = code.preprocessModule()
val function1 = IRUtils.findByExternalId(module, uuid1).get
val usages = findUsagesOfModuleMethod(moduleName, module, function1)
@ -176,8 +171,7 @@ class IRUtilsTest extends AnyWordSpecLike with Matchers with OptionValues {
}
"find usages of a static call in lambda" in {
val uuid1 = new UUID(0, 1)
val moduleName = QualifiedName(List("Unnamed"), "Test")
val uuid1 = new UUID(0, 1)
val code =
s"""function1 x = x
|
@ -192,7 +186,7 @@ class IRUtilsTest extends AnyWordSpecLike with Matchers with OptionValues {
|[]
|""".stripMargin
val module = code.preprocessModule(moduleName)
val module = code.preprocessModule()
val function1 = IRUtils.findByExternalId(module, uuid1).get
val usages = findUsagesOfModuleMethod(moduleName, module, function1)
@ -204,8 +198,7 @@ class IRUtilsTest extends AnyWordSpecLike with Matchers with OptionValues {
}
"find usages of a static method call in presence of an instance method" in {
val uuid1 = new UUID(0, 1)
val moduleName = QualifiedName(List("Unnamed"), "Test")
val uuid1 = new UUID(0, 1)
val code =
s"""function1 x = x
|
@ -220,7 +213,7 @@ class IRUtilsTest extends AnyWordSpecLike with Matchers with OptionValues {
|[]
|""".stripMargin
val module = code.preprocessModule(moduleName)
val module = code.preprocessModule()
val function1 = IRUtils.findByExternalId(module, uuid1).get
val usages = findUsagesOfModuleMethod(moduleName, module, function1)
@ -232,8 +225,7 @@ class IRUtilsTest extends AnyWordSpecLike with Matchers with OptionValues {
}
"find usages of a static method call in presence of a type method" in {
val uuid1 = new UUID(0, 1)
val moduleName = QualifiedName(List("Unnamed"), "Test")
val uuid1 = new UUID(0, 1)
val code =
s"""function1 x = x
|
@ -251,7 +243,7 @@ class IRUtilsTest extends AnyWordSpecLike with Matchers with OptionValues {
|[]
|""".stripMargin
val module = code.preprocessModule(moduleName)
val module = code.preprocessModule()
val operator1 = IRUtils.findByExternalId(module, uuid1).get
val usages = findUsagesOfModuleMethod(moduleName, module, operator1)

View File

@ -1,5 +1,6 @@
package org.enso.compiler.test.semantic
import com.oracle.truffle.api.TruffleFile
import org.enso.compiler.core.Implicits.AsMetadata
import org.enso.compiler.core.ir.{Module, ProcessingPass, Warning}
import org.enso.compiler.core.ir.expression.errors
@ -11,17 +12,18 @@ import org.enso.interpreter.runtime
import org.enso.interpreter.runtime.EnsoContext
import org.enso.persist.Persistance
import org.enso.pkg.QualifiedName
import org.enso.pkg.Package
import org.enso.common.LanguageInfo
import org.enso.common.MethodNames
import org.enso.compiler.phase.exports.Node
import org.enso.common.RuntimeOptions
import org.enso.test.utils.{ContextUtils, ProjectUtils}
import org.graalvm.polyglot.{Context, Engine}
import org.scalatest.BeforeAndAfter
import org.scalatest.{BeforeAndAfter}
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
import java.io.ByteArrayOutputStream
import java.nio.file.Paths
import java.nio.file.{Files, Path, Paths}
import java.util.logging.Level
import java.io.IOException
@ -35,12 +37,17 @@ class ImportExportTest
with BeforeAndAfter {
private val out = new ByteArrayOutputStream()
private val namespace = "local"
private val packageName = "My_Package"
private val packageQualifiedName =
QualifiedName.fromString(namespace + "." + packageName)
private val engine = Engine
.newBuilder("enso")
.allowExperimentalOptions(true)
.build()
private val ctx = Context
private val ctxBldr = Context
.newBuilder(LanguageInfo.ID)
.engine(engine)
.allowExperimentalOptions(true)
@ -51,7 +58,7 @@ class ImportExportTest
.option(RuntimeOptions.LOG_LEVEL, Level.WARNING.getName())
.option(RuntimeOptions.DISABLE_IR_CACHES, "true")
.option(RuntimeOptions.STRICT_ERRORS, "false")
.logHandler(System.err)
.logHandler(out)
.option(
RuntimeOptions.LANGUAGE_HOME_OVERRIDE,
Paths
@ -60,23 +67,34 @@ class ImportExportTest
.getAbsolutePath
)
.option(RuntimeOptions.EDITION_OVERRIDE, "0.0.0-dev")
.build()
private val langCtx: EnsoContext = ctx
.getBindings(LanguageInfo.ID)
.invokeMember(MethodNames.TopScope.LEAK_CONTEXT)
.asHostObject[EnsoContext]()
private var ctx: Context = _
private var langCtx: EnsoContext = _
private var tmpDir: Path = _
private var pkg: Package[TruffleFile] = _
private val namespace = "my_pkg"
private val packageName = "My_Package"
private val packageQualifiedName =
QualifiedName.fromString(namespace + "." + packageName)
before {
// Create temp dir with skeleton project structure. In the tests, we will create
// synthetic modules that will be part of this project.
tmpDir = Files.createTempDirectory("enso-test")
ProjectUtils.createProject(packageName, "main = 42", tmpDir)
ctxBldr.option(RuntimeOptions.PROJECT_ROOT, tmpDir.toAbsolutePath.toString)
ctx = ctxBldr.build()
langCtx = ContextUtils.leakContext(ctx)
langCtx.getPackageRepository.getMainProjectPackage shouldBe defined
pkg = langCtx.getPackageRepository.getMainProjectPackage.get
ctx.enter()
}
langCtx.getPackageRepository.registerSyntheticPackage(namespace, packageName)
after {
ctx.leave()
out.reset()
ProjectUtils.deleteRecursively(tmpDir)
}
implicit private class CreateModule(moduleCode: String) {
def createModule(moduleName: QualifiedName): runtime.Module = {
val module = new runtime.Module(moduleName, null, moduleCode)
val module = new runtime.Module(moduleName, pkg, moduleCode)
langCtx.getPackageRepository.registerModuleCreatedInRuntime(
module.asCompilerModule()
)
@ -114,15 +132,6 @@ class ImportExportTest
)
}
before {
ctx.enter()
}
after {
ctx.leave()
out.reset()
}
"Import resolution with just two modules" should {
"resolve one import symbol from a module" in {
val moduleCode =

View File

@ -6,15 +6,21 @@ import org.enso.compiler.core.ir
import org.enso.compiler.core.ir.module.scope.definition
import org.enso.compiler.core.ir.`type`
import org.enso.compiler.pass.resolve.{TypeNames, TypeSignatures}
import org.enso.interpreter.runtime
import org.enso.interpreter.runtime.EnsoContext
import org.enso.interpreter.test.InterpreterContext
import org.enso.pkg.QualifiedName
import org.enso.common.{LanguageInfo, MethodNames}
import org.enso.common.{LanguageInfo, MethodNames, RuntimeOptions}
import org.enso.editions.LibraryName
import org.enso.test.utils.{ProjectUtils, SourceModule}
import org.enso.testkit.WithTemporaryDirectory
import org.scalatest.BeforeAndAfterAll
import org.scalatest.matchers.{MatchResult, Matcher}
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike
import java.io.ByteArrayOutputStream
import scala.jdk.CollectionConverters.SetHasAsJava
trait TypeMatchers {
sealed trait Sig {
def ->:(that: Sig): Sig = this match {
@ -127,36 +133,67 @@ trait TypeMatchers {
class TypeSignaturesTest
extends AnyWordSpecLike
with Matchers
with TypeMatchers {
with TypeMatchers
with WithTemporaryDirectory
with BeforeAndAfterAll {
private val ctx = new InterpreterContext()
private val langCtx = ctx
.ctx()
.getBindings(LanguageInfo.ID)
.invokeMember(MethodNames.TopScope.LEAK_CONTEXT)
.asHostObject[EnsoContext]()
private var ctx: InterpreterContext = _
private var langCtx: EnsoContext = _
private var devNull: ByteArrayOutputStream = new ByteArrayOutputStream()
private val Module = QualifiedName.fromString("Unnamed.Test")
override def afterAll(): Unit = {
ctx.close()
devNull.close()
devNull = null
ctx = null
langCtx = null
}
langCtx.getPackageRepository.registerSyntheticPackage("my_pkg", "My_Lib")
langCtx.getTopScope.createModule(
QualifiedName.fromString("my_pkg.My_Lib.Util"),
null,
s"""
|type Util_1
|type Util_2
|
|type Util_Sum
| Util_Sum_1
| Util_Sum_2
|""".stripMargin
)
private val libName: LibraryName = LibraryName("local", "My_Lib")
implicit private class PreprocessModule(code: String) {
def preprocessModule: Module = {
val module = new runtime.Module(Module, null, code)
langCtx.getCompiler.run(module.asCompilerModule())
module.getIr
val utilMod = new SourceModule(
QualifiedName.simpleName("Util"),
"""
|type Util_1
|type Util_2
|
|type Util_Sum
| Util_Sum_1
| Util_Sum_2
|""".stripMargin
)
val testMod = new SourceModule(QualifiedName.simpleName("Test"), code)
ProjectUtils.createProject(
libName.name,
Set(utilMod, testMod).asJava,
getTestDirectory
)
ctx = new InterpreterContext(bldr =>
bldr
.option(
RuntimeOptions.PROJECT_ROOT,
getTestDirectory.toFile.getAbsolutePath
)
.logHandler(devNull)
)
langCtx = ctx
.ctx()
.getBindings(LanguageInfo.ID)
.invokeMember(MethodNames.TopScope.LEAK_CONTEXT)
.asHostObject[EnsoContext]()
val pkgWasLoaded =
langCtx.getPackageRepository.ensurePackageIsLoaded(libName)
pkgWasLoaded.isRight shouldBe true
val testModName = QualifiedName.fromString(libName.toString + ".Test")
val module =
langCtx.getPackageRepository.getLoadedModule(testModName.toString)
module shouldBe defined
val compilerRes = langCtx.getCompiler.run(module.get)
compilerRes.compiledModules.isEmpty shouldBe false
module.get.getIr
}
}
@ -202,41 +239,41 @@ class TypeSignaturesTest
|foo a = 42""".stripMargin
val module = code.preprocessModule
getSignature(module, "foo") should typeAs(
"Unnamed.Test.A" ->: "Unnamed.Test.B" ->: "Unnamed.Test.C" ->: "Unnamed.Test.C" ->: "Unnamed.Test.A"
"local.My_Lib.Test.A" ->: "local.My_Lib.Test.B" ->: "local.My_Lib.Test.C" ->: "local.My_Lib.Test.C" ->: "local.My_Lib.Test.A"
)
}
"resolve imported names" in {
"XX resolve imported names" in {
val code =
"""
|from my_pkg.My_Lib.Util import all
|from project.Util import all
|
|foo : Util_1 -> Util_2
|foo a = 23
|""".stripMargin
val module = code.preprocessModule
getSignature(module, "foo") should typeAs(
"my_pkg.My_Lib.Util.Util_1" ->: "my_pkg.My_Lib.Util.Util_2"
"local.My_Lib.Util.Util_1" ->: "local.My_Lib.Util.Util_2"
)
}
"resolve imported union type names" in {
val code =
"""
|from my_pkg.My_Lib.Util import all
|from project.Util import all
|
|foo : Util_Sum -> Util_2
|foo a = 23
|""".stripMargin
val module = code.preprocessModule
getSignature(module, "foo") should typeAs(
"my_pkg.My_Lib.Util.Util_Sum" ->: "my_pkg.My_Lib.Util.Util_2"
"local.My_Lib.Util.Util_Sum" ->: "local.My_Lib.Util.Util_2"
)
}
"resolve anonymous sum types" in {
val code =
"""from my_pkg.My_Lib.Util import all
"""from project.Util import all
|
|type Foo
|
@ -245,7 +282,7 @@ class TypeSignaturesTest
|""".stripMargin
val module = code.preprocessModule
getSignature(module, "baz") should typeAs(
("Unnamed.Test.Foo" | "my_pkg.My_Lib.Util.Util_2" | "my_pkg.My_Lib.Util.Util_Sum") ->: "Unnamed.Test.Foo"
("local.My_Lib.Test.Foo" | "local.My_Lib.Util.Util_2" | "local.My_Lib.Util.Util_Sum") ->: "local.My_Lib.Test.Foo"
)
}

View File

@ -52,14 +52,21 @@ public class ProgramRootNode extends RootNode {
public Object execute(VirtualFrame frame) {
if (module == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
QualifiedName name = QualifiedName.simpleName(canonicalizeName(sourceCode.getName()));
QualifiedName simpleName = QualifiedName.simpleName(canonicalizeName(sourceCode.getName()));
EnsoContext ctx = EnsoContext.get(this);
if (sourceCode.getPath() != null) {
TruffleFile src = ctx.getTruffleFile(new File(sourceCode.getPath()));
Package<TruffleFile> pkg = ctx.getPackageOf(src).orElse(null);
module = new Module(name, pkg, src);
QualifiedName qualifiedName;
if (pkg != null) {
qualifiedName =
QualifiedName.fromString(pkg.libraryName().toString() + "." + simpleName.item());
} else {
qualifiedName = simpleName;
}
module = new Module(qualifiedName, pkg, src);
} else {
module = new Module(name, null, sourceCode.getCharacters().toString());
module = new Module(simpleName, null, sourceCode.getCharacters().toString());
}
ctx.getPackageRepository().registerModuleCreatedInRuntime(module.asCompilerModule());
if (ctx.isStrictErrors()) {

View File

@ -87,6 +87,7 @@ public final class Module implements EnsoObject {
* @param sourceFile the module's source file.
*/
public Module(QualifiedName name, Package<TruffleFile> pkg, TruffleFile sourceFile) {
ensureConsistentName(name, pkg);
this.sources = ModuleSources.NONE.newWith(sourceFile);
this.name = name;
this.scopeBuilder = new ModuleScope.Builder(this);
@ -105,6 +106,7 @@ public final class Module implements EnsoObject {
* @param literalSource the module's source.
*/
public Module(QualifiedName name, Package<TruffleFile> pkg, String literalSource) {
ensureConsistentName(name, pkg);
this.sources = ModuleSources.NONE.newWith(Rope.apply(literalSource));
this.name = name;
this.scopeBuilder = new ModuleScope.Builder(this);
@ -124,6 +126,7 @@ public final class Module implements EnsoObject {
* @param literalSource the module's source.
*/
public Module(QualifiedName name, Package<TruffleFile> pkg, Rope literalSource) {
ensureConsistentName(name, pkg);
this.sources = ModuleSources.NONE.newWith(literalSource);
this.name = name;
this.scopeBuilder = new ModuleScope.Builder(this);
@ -143,6 +146,7 @@ public final class Module implements EnsoObject {
*/
private Module(
QualifiedName name, Package<TruffleFile> pkg, boolean synthetic, Rope literalSource) {
ensureConsistentName(name, pkg);
this.sources =
literalSource == null ? ModuleSources.NONE : ModuleSources.NONE.newWith(literalSource);
this.name = name;
@ -159,6 +163,27 @@ public final class Module implements EnsoObject {
}
}
private void ensureConsistentName(QualifiedName name, Package<TruffleFile> pkg) {
if (name.toString().equals(Builtins.MODULE_NAME)) {
return;
}
if (pkg != null && name.isSimple()) {
throw new IllegalArgumentException(
"Simple module name must not be in a package, i.e., trying to initialize a module in a"
+ " package '"
+ pkg.libraryName().toString()
+ "' with a simple name '"
+ name
+ "'");
} else if (pkg == null && !name.isSimple()) {
throw new IllegalArgumentException(
"Qualified module name must be in a package, i.e., trying to initialize "
+ "a module with a qualified name '"
+ name
+ "' without a package");
}
}
/**
* Unwraps runtime module from compiler module.
*

View File

@ -16,6 +16,7 @@ import java.util.Optional;
import java.util.concurrent.ExecutionException;
import org.enso.common.MethodNames;
import org.enso.compiler.PackageRepository;
import org.enso.editions.LibraryName;
import org.enso.interpreter.EnsoLanguage;
import org.enso.interpreter.runtime.EnsoContext;
import org.enso.interpreter.runtime.Module;
@ -151,7 +152,15 @@ public final class TopLevelScope implements EnsoObject {
Types.extractArguments(arguments, String.class, String.class);
QualifiedName qualName = QualifiedName.fromString(args.getFirst());
File location = new File(args.getSecond());
Module module = new Module(qualName, null, context.getTruffleFile(location));
var libName = LibraryName.fromModuleName(qualName.toString());
Package<TruffleFile> pkg = null;
if (libName.isDefined()) {
var pkgOpt = context.getPackageRepository().getPackageForLibraryJava(libName.get());
if (pkgOpt.isPresent()) {
pkg = pkgOpt.get();
}
}
Module module = new Module(qualName, pkg, context.getTruffleFile(location));
scope.packageRepository.registerModuleCreatedInRuntime(module.asCompilerModule());
return module;
}

View File

@ -49,6 +49,12 @@ case class QualifiedName(path: List[String], item: String) {
path.asJava
}
/** Returns true if this a simple name, not a fully qualified name.
*/
def isSimple(): Boolean = {
path.isEmpty
}
def fullPath(): List[String] = path :+ item
}

View File

@ -27,7 +27,7 @@ add_specs suite_builder = suite_builder.group "Meta-Value Inspection" group_buil
y = My_Type.Value 1 2 3
Meta.get_qualified_type_name x . should_equal "Standard.Base.Data.Numbers.Integer"
Meta.get_simple_type_name x . should_equal "Integer"
Meta.get_qualified_type_name y . should_end_with "Meta_Location_Spec.My_Type"
Meta.get_qualified_type_name y . should_equal "enso_dev.Base_Tests.Semantic.Meta_Location_Spec.My_Type"
Meta.get_simple_type_name y . should_equal "My_Type"
group_builder.specify "should allow access to package names" <|