From a2946d2d2740b44314eef1a02ab02cb94762327b Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Mon, 21 Aug 2023 17:12:57 +0200 Subject: [PATCH] Allow Java Enums in "case of" branches (#7607) Fixes #7551 by looking up static symbols of a `JavaClass` polyglot object. --- CHANGELOG.md | 2 + .../enso/compiler/codegen/IrToTruffle.scala | 63 +++++++++++++++++++ .../org/enso/compiler/data/BindingsMap.scala | 20 ++++++ .../compiler/exception/BadPatternMatch.scala | 11 +++- .../pass/resolve/MethodDefinitions.scala | 7 +++ .../enso/compiler/pass/resolve/Patterns.scala | 9 +++ .../test/java/org/enso/example/TestClass.java | 2 + .../interpreter/test/JavaInteropTest.java | 43 +++++++++++++ .../Tests/src/Semantic/Java_Interop_Spec.enso | 25 ++++++++ 9 files changed, 181 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6544fcaa588..a7a47dc29e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -918,6 +918,7 @@ - [Allow users to give a project other than Upper_Snake_Case name][7397] - [Support renaming variable or function][7515] - [Only use types as State keys][7585] +- [Allow Java Enums in case of branches][7607] [3227]: https://github.com/enso-org/enso/pull/3227 [3248]: https://github.com/enso-org/enso/pull/3248 @@ -1050,6 +1051,7 @@ [7397]: https://github.com/enso-org/enso/pull/7397 [7515]: https://github.com/enso-org/enso/pull/7515 [7585]: https://github.com/enso-org/enso/pull/7585 +[7607]: https://github.com/enso-org/enso/pull/7607 # Enso 2.0.0-alpha.18 (2021-10-12) diff --git a/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala index 99266e67ab9..d902ced493b 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/codegen/IrToTruffle.scala @@ -1,6 +1,7 @@ package org.enso.compiler.codegen import com.oracle.truffle.api.source.{Source, SourceSection} +import com.oracle.truffle.api.interop.InteropLibrary import org.enso.compiler.core.CompilerError import org.enso.compiler.core.IR import org.enso.compiler.core.IR.Module.Scope.Import @@ -369,6 +370,10 @@ class IrToTruffle( throw new CompilerError( "Impossible polyglot symbol, should be caught by MethodDefinitions pass." ) + case BindingsMap.ResolvedPolyglotField(_, _) => + throw new CompilerError( + "Impossible polyglot field, should be caught by MethodDefinitions pass." + ) case _: BindingsMap.ResolvedMethod => throw new CompilerError( "Impossible here, should be caught by MethodDefinitions pass." @@ -730,6 +735,10 @@ class IrToTruffle( throw new CompilerError( "Impossible polyglot symbol, should be caught by MethodDefinitions pass." ) + case BindingsMap.ResolvedPolyglotField(_, _) => + throw new CompilerError( + "Impossible polyglot field, should be caught by MethodDefinitions pass." + ) case _: BindingsMap.ResolvedMethod => throw new CompilerError( "Impossible here, should be caught by MethodDefinitions pass." @@ -861,6 +870,7 @@ class IrToTruffle( fun ) case BindingsMap.ResolvedPolyglotSymbol(_, _) => + case BindingsMap.ResolvedPolyglotField(_, _) => } } case _ => throw new CompilerError("Unreachable") @@ -1218,6 +1228,51 @@ class IrToTruffle( ), BadPatternMatch.NonVisiblePolyglotSymbol(symbol.name) ) + case Some( + BindingsMap.Resolution( + BindingsMap.ResolvedPolyglotField(typ, symbol) + ) + ) => + val mod = typ.module + val polyClass = mod + .unsafeAsModule() + .getScope + .getPolyglotSymbols + .get(typ.symbol.name) + + val polyValueOrError = + if (polyClass == null) + Left( + BadPatternMatch.NonVisiblePolyglotSymbol( + typ.symbol.name + ) + ) + else + try { + val iop = InteropLibrary.getUncached() + if (!iop.isMemberReadable(polyClass, symbol)) { + Left(BadPatternMatch.NonVisiblePolyglotSymbol(symbol)) + } else { + if (iop.isMemberModifiable(polyClass, symbol)) { + Left( + BadPatternMatch.NonConstantPolyglotSymbol(symbol) + ) + } else { + Right(iop.readMember(polyClass, symbol)) + } + } + } catch { + case _: Throwable => + Left(BadPatternMatch.NonVisiblePolyglotSymbol(symbol)) + } + polyValueOrError.map(polyValue => { + ObjectEqualityBranchNode + .build( + branchCodeNode.getCallTarget, + polyValue, + branch.terminalBranch + ) + }) case Some( BindingsMap.Resolution( BindingsMap.ResolvedMethod(_, _) @@ -1570,6 +1625,14 @@ class IrToTruffle( .getPolyglotSymbols .get(symbol.name) ) + case BindingsMap.ResolvedPolyglotField(symbol, name) => + ConstantObjectNode.build( + symbol.module + .unsafeAsModule() + .getScope + .getPolyglotSymbols + .get(name) + ) case BindingsMap.ResolvedMethod(_, method) => throw new CompilerError( s"Impossible here, ${method.name} should be caught when translating application" diff --git a/engine/runtime/src/main/scala/org/enso/compiler/data/BindingsMap.scala b/engine/runtime/src/main/scala/org/enso/compiler/data/BindingsMap.scala index 1a20010c7a7..27740971133 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/data/BindingsMap.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/data/BindingsMap.scala @@ -212,6 +212,9 @@ case class BindingsMap( } } currentScope.resolveExportedSymbol(finalItem) + case s @ ResolvedPolyglotSymbol(_, _) => + val found = s.findExportedSymbolFor(finalItem) + Right(found) case _ => Left(ResolutionNotFound) } @@ -937,6 +940,21 @@ object BindingsMap { override def qualifiedName: QualifiedName = module.getName.createChild(symbol.name) + + def findExportedSymbolFor( + name: String + ): org.enso.compiler.data.BindingsMap.ResolvedName = + ResolvedPolyglotField(this, name) + } + + case class ResolvedPolyglotField(symbol: ResolvedPolyglotSymbol, name: String) + extends ResolvedName { + def module: BindingsMap.ModuleReference = symbol.module + def qualifiedName: QualifiedName = symbol.qualifiedName.createChild(name) + def toAbstract: ResolvedName = + ResolvedPolyglotField(symbol.toAbstract, name) + def toConcrete(moduleMap: ModuleMap): Option[ResolvedName] = + symbol.toConcrete(moduleMap).map(ResolvedPolyglotField(_, name)) } /** A representation of an error during name resolution. @@ -962,6 +980,8 @@ object BindingsMap { s" The module ${module.getName};" case BindingsMap.ResolvedPolyglotSymbol(_, symbol) => s" The imported polyglot symbol ${symbol.name};" + case BindingsMap.ResolvedPolyglotField(_, name) => + s" The imported polyglot field ${name};" case BindingsMap.ResolvedMethod(module, symbol) => s" The method ${symbol.name} defined in module ${module.getName}" case BindingsMap.ResolvedType(module, typ) => diff --git a/engine/runtime/src/main/scala/org/enso/compiler/exception/BadPatternMatch.scala b/engine/runtime/src/main/scala/org/enso/compiler/exception/BadPatternMatch.scala index 82de47c9132..87c20c1fa64 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/exception/BadPatternMatch.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/exception/BadPatternMatch.scala @@ -43,10 +43,19 @@ object BadPatternMatch { /** Where the pattern match is matching on a polyglot symbol not visible in the * current scope. * - * @param name the name of the requested constructor + * @param name the name of the requested symbol */ sealed case class NonVisiblePolyglotSymbol(name: String) extends BadPatternMatch { override val message: String = s"$name is not visible in this scope" } + + /** Where the pattern match is matching on a non-final polyglot symbol. + * + * @param name the name of the requested symbol + */ + sealed case class NonConstantPolyglotSymbol(name: String) + extends BadPatternMatch { + override val message: String = s"$name is not a constant" + } } diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/MethodDefinitions.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/MethodDefinitions.scala index 66394d7947f..5ab2063471c 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/MethodDefinitions.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/MethodDefinitions.scala @@ -165,6 +165,13 @@ case object MethodDefinitions extends IRPass { "a method definition target" ) ) + case Right(_: BindingsMap.ResolvedPolyglotField) => + IR.Error.Resolution( + typePointer, + IR.Error.Resolution.UnexpectedPolyglot( + "a method definition target" + ) + ) case Right(_: BindingsMap.ResolvedMethod) => IR.Error.Resolution( typePointer, diff --git a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/Patterns.scala b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/Patterns.scala index 56b7c7e0213..84a2f9b2bc3 100644 --- a/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/Patterns.scala +++ b/engine/runtime/src/main/scala/org/enso/compiler/pass/resolve/Patterns.scala @@ -133,6 +133,10 @@ object Patterns extends IRPass { consName.updateMetadata( this -->> BindingsMap.Resolution(value) ) + case Right(value: BindingsMap.ResolvedPolyglotField) => + consName.updateMetadata( + this -->> BindingsMap.Resolution(value) + ) case Right(_: BindingsMap.ResolvedMethod) => IR.Error.Resolution( @@ -150,6 +154,7 @@ object Patterns extends IRPass { case BindingsMap.ResolvedConstructor(_, cons) => cons.arity case BindingsMap.ResolvedModule(_) => 0 case BindingsMap.ResolvedPolyglotSymbol(_, _) => 0 + case BindingsMap.ResolvedPolyglotField(_, _) => 0 case BindingsMap.ResolvedMethod(_, _) => throw new CompilerError( "Impossible, should be transformed into an error before." @@ -207,6 +212,10 @@ object Patterns extends IRPass { tpeName.updateMetadata( this -->> BindingsMap.Resolution(value) ) + case Right(value: BindingsMap.ResolvedPolyglotField) => + tpeName.updateMetadata( + this -->> BindingsMap.Resolution(value) + ) /*IR.Error.Resolution( tpeName, IR.Error.Resolution.UnexpectedPolyglot(s"type pattern case") diff --git a/engine/runtime/src/test/java/org/enso/example/TestClass.java b/engine/runtime/src/test/java/org/enso/example/TestClass.java index 5404758177a..a3b7736916f 100644 --- a/engine/runtime/src/test/java/org/enso/example/TestClass.java +++ b/engine/runtime/src/test/java/org/enso/example/TestClass.java @@ -5,6 +5,8 @@ import java.util.function.Function; /** A class used for testing Java Interop from Enso code */ public class TestClass { + public static final int FINAL_ONE = 1; + public static int nonFinalTwo = 2; private final Function function; diff --git a/engine/runtime/src/test/java/org/enso/interpreter/test/JavaInteropTest.java b/engine/runtime/src/test/java/org/enso/interpreter/test/JavaInteropTest.java index f2cb26b1f0a..12dc301babb 100644 --- a/engine/runtime/src/test/java/org/enso/interpreter/test/JavaInteropTest.java +++ b/engine/runtime/src/test/java/org/enso/interpreter/test/JavaInteropTest.java @@ -5,6 +5,7 @@ import java.nio.charset.StandardCharsets; import java.util.List; import org.graalvm.polyglot.Context; +import org.graalvm.polyglot.PolyglotException; import org.graalvm.polyglot.Value; import org.junit.AfterClass; import static org.junit.Assert.assertArrayEquals; @@ -97,6 +98,48 @@ public class JavaInteropTest extends TestBase { checkPrint(code, List.of("ENUM_VALUE_1", "ENUM_VALUE_2")); } + @Test + public void testCaseOnEnum() { + var code = """ + from Standard.Base import IO + polyglot java import org.enso.example.TestClass + polyglot java import org.enso.example.TestClass.InnerEnum + + to_string x = case x of + InnerEnum.ENUM_VALUE_1 -> "one" + InnerEnum.ENUM_VALUE_2 -> "two" + _ -> "none" + + main = + IO.println <| to_string TestClass.InnerEnum.ENUM_VALUE_1 + IO.println <| to_string TestClass.InnerEnum.ENUM_VALUE_2 + """; + checkPrint(code, List.of("one", "two")); + } + + @Test + public void testCaseNonFinal() { + var code = """ + from Standard.Base import IO + polyglot java import org.enso.example.TestClass + + to_string x = case x of + TestClass.FINAL_ONE -> "one" + TestClass.nonFinalTwo -> "two" + _ -> "none" + + main = + IO.println <| to_string 1 + IO.println <| to_string 2 + """; + try { + checkPrint(code, List.of()); + fail("Expecting exception"); + } catch (PolyglotException e) { + assertEquals("Compile error: nonFinalTwo is not a constant.", e.getMessage()); + } + } + @Test public void testImportOuterClassAndReferenceInner() { var code = """ diff --git a/test/Tests/src/Semantic/Java_Interop_Spec.enso b/test/Tests/src/Semantic/Java_Interop_Spec.enso index 0d62618f1e5..1b3bb83642a 100644 --- a/test/Tests/src/Semantic/Java_Interop_Spec.enso +++ b/test/Tests/src/Semantic/Java_Interop_Spec.enso @@ -1,5 +1,6 @@ from Standard.Base import all import Standard.Base.Errors.Common.No_Such_Method +import Standard.Base.Errors.Common.Compile_Error from Standard.Test import Test, Test_Suite import Standard.Test.Extensions @@ -9,6 +10,7 @@ polyglot java import java.lang.Integer as Java_Integer polyglot java import java.lang.Long polyglot java import java.lang.String polyglot java import java.lang.StringBuilder as Java_String_Builder +polyglot java import java.lang.Thread.State polyglot java import java.util.ArrayList polyglot java import java.time.LocalDate polyglot java import java.time.LocalTime @@ -66,4 +68,27 @@ spec = april1st.month.should_equal 4 april1st.day.should_equal 1 + Test.group "Java case of" <| + Test.specify "case on Thread.State enum" <| + match x = case x of + State.NEW -> "new" + _ -> "unknown" + match State.NEW . should_equal "new" + match State.BLOCKED . should_equal "unknown" + + Test.specify "case on String static field" <| + match x = case x of + String.CASE_INSENSITIVE_ORDER -> "match" + _ -> "unknown" + match String.CASE_INSENSITIVE_ORDER . should_equal "match" + + Test.specify "case on non-existing field yields Compile_Error" <| + match x = case x of + State.NON_EXISTING -> "match" + _ -> "unknown" + err = Panic.recover Any (match State.BLOCKED) + err . should_fail_with Compile_Error + err.to_text . contains "NON_EXISTING" . should_be_true + err.to_text . contains "is not visible in this scope" . should_be_true + main = Test_Suite.run_main spec