From 289c9affc129e00973c5d139657b32c9c4a4f721 Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 5 Jun 2023 11:35:14 +0200 Subject: [PATCH] Allow import of Ordering when Main.Data.Ordering exposes it transitively --- .../pass/analyse/ImportApiAnalysis.java | 97 +++++++++++++------ .../test/semantic/ImportExportTest.scala | 22 +++++ 2 files changed, 91 insertions(+), 28 deletions(-) diff --git a/engine/runtime/src/main/java/org/enso/compiler/pass/analyse/ImportApiAnalysis.java b/engine/runtime/src/main/java/org/enso/compiler/pass/analyse/ImportApiAnalysis.java index 189482c66adb..8209558a3aa0 100644 --- a/engine/runtime/src/main/java/org/enso/compiler/pass/analyse/ImportApiAnalysis.java +++ b/engine/runtime/src/main/java/org/enso/compiler/pass/analyse/ImportApiAnalysis.java @@ -1,5 +1,10 @@ package org.enso.compiler.pass.analyse; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.Set; import java.util.UUID; import org.enso.compiler.context.InlineContext; import org.enso.compiler.context.ModuleContext; @@ -17,6 +22,7 @@ import org.enso.compiler.pass.resolve.Patterns$; import org.enso.interpreter.runtime.Module; import org.enso.interpreter.util.ScalaConversions; +import org.enso.pkg.QualifiedName; import scala.collection.immutable.Seq; /** Verifies that all imported symbols are also accessible via {@code Main.enso}. @@ -53,7 +59,7 @@ public Seq invalidatedPasses() { @Override public IR.Module runModule(IR.Module ir, ModuleContext moduleContext) { var map = (BindingsMap) ir.passData().get(BindingAnalysis$.MODULE$).get(); - var forbiddenImports = new java.util.HashMap(); + var forbiddenImports = new HashMap(); for (var imp : ScalaConversions.asJava(map.resolvedImports())) { var mod = switch (imp.target()) { @@ -79,34 +85,11 @@ public IR.Module runModule(IR.Module ir, ModuleContext moduleContext) { forbidden = false; } else { // if different that Main mod is requested, do a check - SET_FORBIDDEN: if (mainModule.getIr() == null) { // if main mod IR isn't loaded, then certainly the import didn't go thru Main forbidden = true; } else { - var mainMap = (BindingsMap) mainModule.getIr().passData().get(BindingAnalysis$.MODULE$).get(); - var itMainMap = mainMap.exportedSymbols().iterator(); - while (itMainMap.hasNext()) { - for (var module : ScalaConversions.asJava(itMainMap.next()._2())) { - switch (module) { - case BindingsMap.ResolvedModule rm -> { - switch (rm.module()) { - case BindingsMap$ModuleReference$Concrete allowed -> { - if (allowed.module() == mod) { - forbidden = false; - break SET_FORBIDDEN; - } - } - default -> { - } - } - } - default -> { - } - } - } - } - forbidden = true; + forbidden = findLogicalImport(mod, mainModule); } } if (forbidden) { @@ -128,9 +111,9 @@ public IR.Module runModule(IR.Module ir, ModuleContext moduleContext) { var segments = name.split("\\."); var last = segments[segments.length - 1]; yield new IR$Error$ImportExport( - imp, - new IR$Error$ImportExport$SymbolDoesNotExist(last, pkg), - imp.passData(), imp.diagnostics() + imp, + new IR$Error$ImportExport$SymbolDoesNotExist(last, pkg), + imp.passData(), imp.diagnostics() ); } else { yield mod; @@ -160,6 +143,64 @@ public T updateMetadataInDuplicate(T sourceIr, T copyOfIr) { return copyOfIr; } + /** @return {@code true} if the {@code mod} isn't allowed to be imported */ + private static boolean findLogicalImport(Module mod, Module rootModule) { + var checked = new HashSet<>(); + var toProcess = new LinkedList(); + toProcess.add(rootModule); + + for (;;) { + if (toProcess.isEmpty()) { + break; + } + var current = toProcess.remove(); + if (!checked.add(current)) { + continue; + } + var map = (BindingsMap) current.getIr().passData().get(BindingAnalysis$.MODULE$).get(); + + var itMainMap = map.exportedSymbols().iterator(); + while (itMainMap.hasNext()) { + for (var module : ScalaConversions.asJava(itMainMap.next()._2())) { + switch (module) { + case BindingsMap.ResolvedModule rm -> { + switch (rm.module()) { + case BindingsMap$ModuleReference$Concrete allowed -> { + if (allowed.module() == mod) { + // found import + return false; + } else { + if (isParentModule(allowed.getName(), mod.getName())) { + toProcess.add(allowed.module()); + } + } + } + default -> { + } + } + } + default -> { + } + } + } + } + } + return true; + } + + private static boolean isParentModule(QualifiedName parent, QualifiedName mod) { + var path = parent.pathAsJava(); + for (int i = 0; i < path.size(); i++) { + if (mod.pathAsJava().size() >= i) { + break; + } + if (!mod.pathAsJava().get(i).equals(path.get(i))) { + return false; + } + } + return true; + } + @SuppressWarnings("unchecked") private static scala.collection.immutable.List nil() { return (scala.collection.immutable.List) scala.collection.immutable.Nil$.MODULE$; diff --git a/engine/runtime/src/test/scala/org/enso/compiler/test/semantic/ImportExportTest.scala b/engine/runtime/src/test/scala/org/enso/compiler/test/semantic/ImportExportTest.scala index a83f1372fc2f..386b64f49a04 100644 --- a/engine/runtime/src/test/scala/org/enso/compiler/test/semantic/ImportExportTest.scala +++ b/engine/runtime/src/test/scala/org/enso/compiler/test/semantic/ImportExportTest.scala @@ -439,6 +439,28 @@ class ImportExportTest errors.size shouldEqual 0 } + "resolve Element from Main" in { + val mainIr = """ + |import Test.Logical_Export.Api.Element.Element + | + |main = + | element = Element.create + | element.describe + |""".stripMargin + .createModule(packageQualifiedName.createChild("Main")) + .getIr + + mainIr.imports.size shouldEqual 1 + val in = mainIr.imports.head + .asInstanceOf[IR.Module.Scope.Import.Module] + + in.name.name.toString() should include("Test.Logical_Export") + in.onlyNames shouldEqual None + + val errors = mainIr.preorder.filter(x => x.isInstanceOf[IR.Error]) + errors.size shouldEqual 0 + } + "don't expose Impl from Main" in { val mainIr = """ |from Test.Logical_Export import Impl