Allow Java Enums in "case of" branches (#7607)

Fixes #7551 by looking up static symbols of a `JavaClass` polyglot object.
This commit is contained in:
Jaroslav Tulach 2023-08-21 17:12:57 +02:00 committed by GitHub
parent 06b2280ee6
commit a2946d2d27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 181 additions and 1 deletions

View File

@ -918,6 +918,7 @@
- [Allow users to give a project other than Upper_Snake_Case name][7397] - [Allow users to give a project other than Upper_Snake_Case name][7397]
- [Support renaming variable or function][7515] - [Support renaming variable or function][7515]
- [Only use types as State keys][7585] - [Only use types as State keys][7585]
- [Allow Java Enums in case of branches][7607]
[3227]: https://github.com/enso-org/enso/pull/3227 [3227]: https://github.com/enso-org/enso/pull/3227
[3248]: https://github.com/enso-org/enso/pull/3248 [3248]: https://github.com/enso-org/enso/pull/3248
@ -1050,6 +1051,7 @@
[7397]: https://github.com/enso-org/enso/pull/7397 [7397]: https://github.com/enso-org/enso/pull/7397
[7515]: https://github.com/enso-org/enso/pull/7515 [7515]: https://github.com/enso-org/enso/pull/7515
[7585]: https://github.com/enso-org/enso/pull/7585 [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) # Enso 2.0.0-alpha.18 (2021-10-12)

View File

@ -1,6 +1,7 @@
package org.enso.compiler.codegen package org.enso.compiler.codegen
import com.oracle.truffle.api.source.{Source, SourceSection} 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.CompilerError
import org.enso.compiler.core.IR import org.enso.compiler.core.IR
import org.enso.compiler.core.IR.Module.Scope.Import import org.enso.compiler.core.IR.Module.Scope.Import
@ -369,6 +370,10 @@ class IrToTruffle(
throw new CompilerError( throw new CompilerError(
"Impossible polyglot symbol, should be caught by MethodDefinitions pass." "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 => case _: BindingsMap.ResolvedMethod =>
throw new CompilerError( throw new CompilerError(
"Impossible here, should be caught by MethodDefinitions pass." "Impossible here, should be caught by MethodDefinitions pass."
@ -730,6 +735,10 @@ class IrToTruffle(
throw new CompilerError( throw new CompilerError(
"Impossible polyglot symbol, should be caught by MethodDefinitions pass." "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 => case _: BindingsMap.ResolvedMethod =>
throw new CompilerError( throw new CompilerError(
"Impossible here, should be caught by MethodDefinitions pass." "Impossible here, should be caught by MethodDefinitions pass."
@ -861,6 +870,7 @@ class IrToTruffle(
fun fun
) )
case BindingsMap.ResolvedPolyglotSymbol(_, _) => case BindingsMap.ResolvedPolyglotSymbol(_, _) =>
case BindingsMap.ResolvedPolyglotField(_, _) =>
} }
} }
case _ => throw new CompilerError("Unreachable") case _ => throw new CompilerError("Unreachable")
@ -1218,6 +1228,51 @@ class IrToTruffle(
), ),
BadPatternMatch.NonVisiblePolyglotSymbol(symbol.name) 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( case Some(
BindingsMap.Resolution( BindingsMap.Resolution(
BindingsMap.ResolvedMethod(_, _) BindingsMap.ResolvedMethod(_, _)
@ -1570,6 +1625,14 @@ class IrToTruffle(
.getPolyglotSymbols .getPolyglotSymbols
.get(symbol.name) .get(symbol.name)
) )
case BindingsMap.ResolvedPolyglotField(symbol, name) =>
ConstantObjectNode.build(
symbol.module
.unsafeAsModule()
.getScope
.getPolyglotSymbols
.get(name)
)
case BindingsMap.ResolvedMethod(_, method) => case BindingsMap.ResolvedMethod(_, method) =>
throw new CompilerError( throw new CompilerError(
s"Impossible here, ${method.name} should be caught when translating application" s"Impossible here, ${method.name} should be caught when translating application"

View File

@ -212,6 +212,9 @@ case class BindingsMap(
} }
} }
currentScope.resolveExportedSymbol(finalItem) currentScope.resolveExportedSymbol(finalItem)
case s @ ResolvedPolyglotSymbol(_, _) =>
val found = s.findExportedSymbolFor(finalItem)
Right(found)
case _ => Left(ResolutionNotFound) case _ => Left(ResolutionNotFound)
} }
@ -937,6 +940,21 @@ object BindingsMap {
override def qualifiedName: QualifiedName = override def qualifiedName: QualifiedName =
module.getName.createChild(symbol.name) 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. /** A representation of an error during name resolution.
@ -962,6 +980,8 @@ object BindingsMap {
s" The module ${module.getName};" s" The module ${module.getName};"
case BindingsMap.ResolvedPolyglotSymbol(_, symbol) => case BindingsMap.ResolvedPolyglotSymbol(_, symbol) =>
s" The imported polyglot symbol ${symbol.name};" s" The imported polyglot symbol ${symbol.name};"
case BindingsMap.ResolvedPolyglotField(_, name) =>
s" The imported polyglot field ${name};"
case BindingsMap.ResolvedMethod(module, symbol) => case BindingsMap.ResolvedMethod(module, symbol) =>
s" The method ${symbol.name} defined in module ${module.getName}" s" The method ${symbol.name} defined in module ${module.getName}"
case BindingsMap.ResolvedType(module, typ) => case BindingsMap.ResolvedType(module, typ) =>

View File

@ -43,10 +43,19 @@ object BadPatternMatch {
/** Where the pattern match is matching on a polyglot symbol not visible in the /** Where the pattern match is matching on a polyglot symbol not visible in the
* current scope. * current scope.
* *
* @param name the name of the requested constructor * @param name the name of the requested symbol
*/ */
sealed case class NonVisiblePolyglotSymbol(name: String) sealed case class NonVisiblePolyglotSymbol(name: String)
extends BadPatternMatch { extends BadPatternMatch {
override val message: String = s"$name is not visible in this scope" 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"
}
} }

View File

@ -165,6 +165,13 @@ case object MethodDefinitions extends IRPass {
"a method definition target" "a method definition target"
) )
) )
case Right(_: BindingsMap.ResolvedPolyglotField) =>
IR.Error.Resolution(
typePointer,
IR.Error.Resolution.UnexpectedPolyglot(
"a method definition target"
)
)
case Right(_: BindingsMap.ResolvedMethod) => case Right(_: BindingsMap.ResolvedMethod) =>
IR.Error.Resolution( IR.Error.Resolution(
typePointer, typePointer,

View File

@ -133,6 +133,10 @@ object Patterns extends IRPass {
consName.updateMetadata( consName.updateMetadata(
this -->> BindingsMap.Resolution(value) this -->> BindingsMap.Resolution(value)
) )
case Right(value: BindingsMap.ResolvedPolyglotField) =>
consName.updateMetadata(
this -->> BindingsMap.Resolution(value)
)
case Right(_: BindingsMap.ResolvedMethod) => case Right(_: BindingsMap.ResolvedMethod) =>
IR.Error.Resolution( IR.Error.Resolution(
@ -150,6 +154,7 @@ object Patterns extends IRPass {
case BindingsMap.ResolvedConstructor(_, cons) => cons.arity case BindingsMap.ResolvedConstructor(_, cons) => cons.arity
case BindingsMap.ResolvedModule(_) => 0 case BindingsMap.ResolvedModule(_) => 0
case BindingsMap.ResolvedPolyglotSymbol(_, _) => 0 case BindingsMap.ResolvedPolyglotSymbol(_, _) => 0
case BindingsMap.ResolvedPolyglotField(_, _) => 0
case BindingsMap.ResolvedMethod(_, _) => case BindingsMap.ResolvedMethod(_, _) =>
throw new CompilerError( throw new CompilerError(
"Impossible, should be transformed into an error before." "Impossible, should be transformed into an error before."
@ -207,6 +212,10 @@ object Patterns extends IRPass {
tpeName.updateMetadata( tpeName.updateMetadata(
this -->> BindingsMap.Resolution(value) this -->> BindingsMap.Resolution(value)
) )
case Right(value: BindingsMap.ResolvedPolyglotField) =>
tpeName.updateMetadata(
this -->> BindingsMap.Resolution(value)
)
/*IR.Error.Resolution( /*IR.Error.Resolution(
tpeName, tpeName,
IR.Error.Resolution.UnexpectedPolyglot(s"type pattern case") IR.Error.Resolution.UnexpectedPolyglot(s"type pattern case")

View File

@ -5,6 +5,8 @@ import java.util.function.Function;
/** A class used for testing Java Interop from Enso code */ /** A class used for testing Java Interop from Enso code */
public class TestClass { public class TestClass {
public static final int FINAL_ONE = 1;
public static int nonFinalTwo = 2;
private final Function<Long, Long> function; private final Function<Long, Long> function;

View File

@ -5,6 +5,7 @@ import java.nio.charset.StandardCharsets;
import java.util.List; import java.util.List;
import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.PolyglotException;
import org.graalvm.polyglot.Value; import org.graalvm.polyglot.Value;
import org.junit.AfterClass; import org.junit.AfterClass;
import static org.junit.Assert.assertArrayEquals; 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")); 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 @Test
public void testImportOuterClassAndReferenceInner() { public void testImportOuterClassAndReferenceInner() {
var code = """ var code = """

View File

@ -1,5 +1,6 @@
from Standard.Base import all from Standard.Base import all
import Standard.Base.Errors.Common.No_Such_Method import Standard.Base.Errors.Common.No_Such_Method
import Standard.Base.Errors.Common.Compile_Error
from Standard.Test import Test, Test_Suite from Standard.Test import Test, Test_Suite
import Standard.Test.Extensions 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.Long
polyglot java import java.lang.String polyglot java import java.lang.String
polyglot java import java.lang.StringBuilder as Java_String_Builder 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.util.ArrayList
polyglot java import java.time.LocalDate polyglot java import java.time.LocalDate
polyglot java import java.time.LocalTime polyglot java import java.time.LocalTime
@ -66,4 +68,27 @@ spec =
april1st.month.should_equal 4 april1st.month.should_equal 4
april1st.day.should_equal 1 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 main = Test_Suite.run_main spec