diff --git a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/DynamicTypeAnnotation.java b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/DynamicTypeAnnotation.java new file mode 100644 index 0000000000000..4ae75df2f9496 --- /dev/null +++ b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/DynamicTypeAnnotation.java @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.painless.spi.annotation; + +public class DynamicTypeAnnotation { + + public static final String NAME = "dynamic_type"; + + public static final DynamicTypeAnnotation INSTANCE = new DynamicTypeAnnotation(); + + private DynamicTypeAnnotation() { + + } +} diff --git a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/DynamicTypeAnnotationParser.java b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/DynamicTypeAnnotationParser.java new file mode 100644 index 0000000000000..acd585a61426a --- /dev/null +++ b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/DynamicTypeAnnotationParser.java @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.painless.spi.annotation; + +import java.util.Map; + +public class DynamicTypeAnnotationParser implements WhitelistAnnotationParser { + + public static final DynamicTypeAnnotationParser INSTANCE = new DynamicTypeAnnotationParser(); + + private DynamicTypeAnnotationParser() {} + + @Override + public Object parse(Map arguments) { + if (arguments.isEmpty() == false) { + throw new IllegalArgumentException( + "unexpected parameters for [@" + DynamicTypeAnnotation.NAME + "] annotation, found " + arguments + ); + } + + return DynamicTypeAnnotation.INSTANCE; + } +} diff --git a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/WhitelistAnnotationParser.java b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/WhitelistAnnotationParser.java index 93d1165a05f6d..ab3a19ffff29d 100644 --- a/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/WhitelistAnnotationParser.java +++ b/modules/lang-painless/spi/src/main/java/org/elasticsearch/painless/spi/annotation/WhitelistAnnotationParser.java @@ -27,7 +27,8 @@ public interface WhitelistAnnotationParser { new AbstractMap.SimpleEntry<>(NonDeterministicAnnotation.NAME, NonDeterministicAnnotationParser.INSTANCE), new AbstractMap.SimpleEntry<>(InjectConstantAnnotation.NAME, InjectConstantAnnotationParser.INSTANCE), new AbstractMap.SimpleEntry<>(CompileTimeOnlyAnnotation.NAME, CompileTimeOnlyAnnotationParser.INSTANCE), - new AbstractMap.SimpleEntry<>(AugmentedAnnotation.NAME, AugmentedAnnotationParser.INSTANCE) + new AbstractMap.SimpleEntry<>(AugmentedAnnotation.NAME, AugmentedAnnotationParser.INSTANCE), + new AbstractMap.SimpleEntry<>(DynamicTypeAnnotation.NAME, DynamicTypeAnnotationParser.INSTANCE) ).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)) ); diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClass.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClass.java index 1511eb135e6b0..4a8a7b7f15901 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClass.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClass.java @@ -20,6 +20,7 @@ public final class PainlessClass { public final Map staticFields; public final Map fields; public final PainlessMethod functionalInterfaceMethod; + public final Map, Object> annotations; public final Map runtimeMethods; public final Map getterMethodHandles; @@ -29,6 +30,7 @@ public final class PainlessClass { Map staticMethods, Map methods, Map staticFields, Map fields, PainlessMethod functionalInterfaceMethod, + Map, Object> annotations, Map runtimeMethods, Map getterMethodHandles, Map setterMethodHandles) { @@ -38,6 +40,7 @@ public final class PainlessClass { this.staticFields = Map.copyOf(staticFields); this.fields = Map.copyOf(fields); this.functionalInterfaceMethod = functionalInterfaceMethod; + this.annotations = annotations; this.getterMethodHandles = Map.copyOf(getterMethodHandles); this.setterMethodHandles = Map.copyOf(setterMethodHandles); @@ -61,11 +64,12 @@ public boolean equals(Object object) { Objects.equals(methods, that.methods) && Objects.equals(staticFields, that.staticFields) && Objects.equals(fields, that.fields) && - Objects.equals(functionalInterfaceMethod, that.functionalInterfaceMethod); + Objects.equals(functionalInterfaceMethod, that.functionalInterfaceMethod) && + Objects.equals(annotations, that.annotations); } @Override public int hashCode() { - return Objects.hash(constructors, staticMethods, methods, staticFields, fields, functionalInterfaceMethod); + return Objects.hash(constructors, staticMethods, methods, staticFields, fields, functionalInterfaceMethod, annotations); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClassBuilder.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClassBuilder.java index d5830cc61e8c1..6f614ce12dca2 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClassBuilder.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClassBuilder.java @@ -21,6 +21,7 @@ final class PainlessClassBuilder { final Map staticFields; final Map fields; PainlessMethod functionalInterfaceMethod; + final Map, Object> annotations; final Map runtimeMethods; final Map getterMethodHandles; @@ -33,6 +34,7 @@ final class PainlessClassBuilder { staticFields = new HashMap<>(); fields = new HashMap<>(); functionalInterfaceMethod = null; + annotations = new HashMap<>(); runtimeMethods = new HashMap<>(); getterMethodHandles = new HashMap<>(); @@ -40,7 +42,7 @@ final class PainlessClassBuilder { } PainlessClass build() { - return new PainlessClass(constructors, staticMethods, methods, staticFields, fields, functionalInterfaceMethod, + return new PainlessClass(constructors, staticMethods, methods, staticFields, fields, functionalInterfaceMethod, annotations, runtimeMethods, getterMethodHandles, setterMethodHandles); } @@ -61,11 +63,12 @@ public boolean equals(Object object) { Objects.equals(methods, that.methods) && Objects.equals(staticFields, that.staticFields) && Objects.equals(fields, that.fields) && - Objects.equals(functionalInterfaceMethod, that.functionalInterfaceMethod); + Objects.equals(functionalInterfaceMethod, that.functionalInterfaceMethod) && + Objects.equals(annotations, that.annotations); } @Override public int hashCode() { - return Objects.hash(constructors, staticMethods, methods, staticFields, fields, functionalInterfaceMethod); + return Objects.hash(constructors, staticMethods, methods, staticFields, fields, functionalInterfaceMethod, annotations); } } diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupBuilder.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupBuilder.java index b819b1e134048..02cac53146262 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupBuilder.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupBuilder.java @@ -118,7 +118,7 @@ public static PainlessLookup buildFromWhitelists(List whitelists) { origin = whitelistClass.origin; painlessLookupBuilder.addPainlessClass( whitelist.classLoader, whitelistClass.javaClassName, - whitelistClass.painlessAnnotations.containsKey(NoImportAnnotation.class) == false); + whitelistClass.painlessAnnotations); } } @@ -236,7 +236,8 @@ private Class loadClass(ClassLoader classLoader, String javaClassName, Suppli } } - public void addPainlessClass(ClassLoader classLoader, String javaClassName, boolean importClassName) { + public void addPainlessClass(ClassLoader classLoader, String javaClassName, Map, Object> annotations) { + Objects.requireNonNull(classLoader); Objects.requireNonNull(javaClassName); @@ -255,12 +256,12 @@ public void addPainlessClass(ClassLoader classLoader, String javaClassName, bool clazz = loadClass(classLoader, javaClassName, () -> "class [" + javaClassName + "] not found"); } - addPainlessClass(clazz, importClassName); + addPainlessClass(clazz, annotations); } - public void addPainlessClass(Class clazz, boolean importClassName) { + public void addPainlessClass(Class clazz, Map, Object> annotations) { Objects.requireNonNull(clazz); - //Matcher m = new Matcher(); + Objects.requireNonNull(annotations); if (clazz == def.class) { throw new IllegalArgumentException("cannot add reserved class [" + DEF_CLASS_NAME + "]"); @@ -296,6 +297,7 @@ public void addPainlessClass(Class clazz, boolean importClassName) { if (existingPainlessClassBuilder == null) { PainlessClassBuilder painlessClassBuilder = new PainlessClassBuilder(); + painlessClassBuilder.annotations.putAll(annotations); canonicalClassNamesToClasses.put(canonicalClassName.intern(), clazz); classesToPainlessClassBuilders.put(clazz, painlessClassBuilder); @@ -303,6 +305,7 @@ public void addPainlessClass(Class clazz, boolean importClassName) { String javaClassName = clazz.getName(); String importedCanonicalClassName = javaClassName.substring(javaClassName.lastIndexOf('.') + 1).replace('$', '.'); + boolean importClassName = annotations.containsKey(NoImportAnnotation.class) == false; if (canonicalClassName.equals(importedCanonicalClassName)) { if (importClassName) { diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultSemanticAnalysisPhase.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultSemanticAnalysisPhase.java index 61934ee741f35..bb17fccc36cd6 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultSemanticAnalysisPhase.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultSemanticAnalysisPhase.java @@ -18,6 +18,7 @@ import org.elasticsearch.painless.lookup.PainlessConstructor; import org.elasticsearch.painless.lookup.PainlessField; import org.elasticsearch.painless.lookup.PainlessInstanceBinding; +import org.elasticsearch.painless.lookup.PainlessLookup; import org.elasticsearch.painless.lookup.PainlessLookupUtility; import org.elasticsearch.painless.lookup.PainlessMethod; import org.elasticsearch.painless.lookup.def; @@ -69,6 +70,7 @@ import org.elasticsearch.painless.node.SThrow; import org.elasticsearch.painless.node.STry; import org.elasticsearch.painless.node.SWhile; +import org.elasticsearch.painless.spi.annotation.DynamicTypeAnnotation; import org.elasticsearch.painless.spi.annotation.NonDeterministicAnnotation; import org.elasticsearch.painless.symbol.Decorations; import org.elasticsearch.painless.symbol.Decorations.AllEscape; @@ -83,6 +85,7 @@ import org.elasticsearch.painless.symbol.Decorations.ContinuousLoop; import org.elasticsearch.painless.symbol.Decorations.DefOptimized; import org.elasticsearch.painless.symbol.Decorations.DowncastPainlessCast; +import org.elasticsearch.painless.symbol.Decorations.DynamicInvocation; import org.elasticsearch.painless.symbol.Decorations.EncodingDecoration; import org.elasticsearch.painless.symbol.Decorations.Explicit; import org.elasticsearch.painless.symbol.Decorations.ExpressionPainlessCast; @@ -140,6 +143,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; @@ -2525,9 +2529,8 @@ public void visitDot(EDot userDotNode, SemanticScope semanticScope) { } } else if (prefixValueType != null && prefixValueType.getValueType() == def.class) { TargetType targetType = userDotNode.isNullSafe() ? null : semanticScope.getDecoration(userDotNode, TargetType.class); - // TODO: remove ZonedDateTime exception when JodaCompatibleDateTime is removed - valueType = targetType == null || targetType.getTargetType() == ZonedDateTime.class || - semanticScope.getCondition(userDotNode, Explicit.class) ? def.class : targetType.getTargetType(); + valueType = targetType == null || semanticScope.getCondition(userDotNode, Explicit.class) ? + def.class : targetType.getTargetType(); if (write) { semanticScope.setCondition(userDotNode, DefOptimized.class); @@ -2888,9 +2891,45 @@ public void visitCall(ECall userCallNode, SemanticScope semanticScope) { "[" + semanticScope.getDecoration(userPrefixNode, PartialCanonicalTypeName.class).getPartialCanonicalTypeName() + "]")); } + boolean dynamic = false; + PainlessMethod method = null; + + if (prefixValueType != null) { + Class type = prefixValueType.getValueType(); + PainlessLookup lookup = semanticScope.getScriptScope().getPainlessLookup(); + + if (prefixValueType.getValueType() == def.class) { + dynamic = true; + } else { + method = lookup.lookupPainlessMethod(type, false, methodName, userArgumentsSize); + + if (method == null) { + dynamic = lookup.lookupPainlessClass(type).annotations.containsKey(DynamicTypeAnnotation.class) && + lookup.lookupPainlessSubClassesMethod(type, methodName, userArgumentsSize) != null; + + if (dynamic == false) { + throw userCallNode.createError(new IllegalArgumentException("member method " + + "[" + prefixValueType.getValueCanonicalTypeName() + ", " + methodName + "/" + userArgumentsSize + "] " + + "not found")); + } + } + } + } else if (prefixStaticType != null) { + method = semanticScope.getScriptScope().getPainlessLookup().lookupPainlessMethod( + prefixStaticType.getStaticType(), true, methodName, userArgumentsSize); + + if (method == null) { + throw userCallNode.createError(new IllegalArgumentException("static method " + + "[" + prefixStaticType.getStaticCanonicalTypeName() + ", " + methodName + "/" + userArgumentsSize + "] " + + "not found")); + } + } else { + throw userCallNode.createError(new IllegalStateException("value required: instead found no value")); + } + Class valueType; - if (prefixValueType != null && prefixValueType.getValueType() == def.class) { + if (dynamic) { for (AExpression userArgumentNode : userArgumentNodes) { semanticScope.setCondition(userArgumentNode, Read.class); semanticScope.setCondition(userArgumentNode, Internal.class); @@ -2904,34 +2943,12 @@ public void visitCall(ECall userCallNode, SemanticScope semanticScope) { } TargetType targetType = userCallNode.isNullSafe() ? null : semanticScope.getDecoration(userCallNode, TargetType.class); - // TODO: remove ZonedDateTime exception when JodaCompatibleDateTime is removed - valueType = targetType == null || targetType.getTargetType() == ZonedDateTime.class || - semanticScope.getCondition(userCallNode, Explicit.class) ? def.class : targetType.getTargetType(); - } else { - PainlessMethod method; - - if (prefixValueType != null) { - method = semanticScope.getScriptScope().getPainlessLookup().lookupPainlessMethod( - prefixValueType.getValueType(), false, methodName, userArgumentsSize); - - if (method == null) { - throw userCallNode.createError(new IllegalArgumentException("member method " + - "[" + prefixValueType.getValueCanonicalTypeName() + ", " + methodName + "/" + userArgumentsSize + "] " + - "not found")); - } - } else if (prefixStaticType != null) { - method = semanticScope.getScriptScope().getPainlessLookup().lookupPainlessMethod( - prefixStaticType.getStaticType(), true, methodName, userArgumentsSize); - - if (method == null) { - throw userCallNode.createError(new IllegalArgumentException("static method " + - "[" + prefixStaticType.getStaticCanonicalTypeName() + ", " + methodName + "/" + userArgumentsSize + "] " + - "not found")); - } - } else { - throw userCallNode.createError(new IllegalStateException("value required: instead found no value")); - } + valueType = targetType == null || semanticScope.getCondition(userCallNode, Explicit.class) ? + def.class : targetType.getTargetType(); + semanticScope.setCondition(userCallNode, DynamicInvocation.class); + } else { + Objects.requireNonNull(method); semanticScope.getScriptScope().markNonDeterministic(method.annotations.containsKey(NonDeterministicAnnotation.class)); for (int argument = 0; argument < userArgumentsSize; ++argument) { diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultUserTreeToIRTreePhase.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultUserTreeToIRTreePhase.java index d67cc59b95045..f0ea582e4643b 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultUserTreeToIRTreePhase.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultUserTreeToIRTreePhase.java @@ -154,6 +154,7 @@ import org.elasticsearch.painless.symbol.Decorations.CompoundType; import org.elasticsearch.painless.symbol.Decorations.ContinuousLoop; import org.elasticsearch.painless.symbol.Decorations.DowncastPainlessCast; +import org.elasticsearch.painless.symbol.Decorations.DynamicInvocation; import org.elasticsearch.painless.symbol.Decorations.EncodingDecoration; import org.elasticsearch.painless.symbol.Decorations.Explicit; import org.elasticsearch.painless.symbol.Decorations.ExpressionPainlessCast; @@ -1802,7 +1803,7 @@ public void visitCall(ECall userCallNode, ScriptScope scriptScope) { ValueType prefixValueType = scriptScope.getDecoration(userCallNode.getPrefixNode(), ValueType.class); Class valueType = scriptScope.getDecoration(userCallNode, ValueType.class).getValueType(); - if (prefixValueType != null && prefixValueType.getValueType() == def.class) { + if (scriptScope.getCondition(userCallNode, DynamicInvocation.class)) { InvokeCallDefNode irCallSubDefNode = new InvokeCallDefNode(userCallNode.getLocation()); for (AExpression userArgumentNode : userCallNode.getArgumentNodes()) { diff --git a/modules/lang-painless/src/main/java/org/elasticsearch/painless/symbol/Decorations.java b/modules/lang-painless/src/main/java/org/elasticsearch/painless/symbol/Decorations.java index de6f748928870..38a9d7f2f8b95 100644 --- a/modules/lang-painless/src/main/java/org/elasticsearch/painless/symbol/Decorations.java +++ b/modules/lang-painless/src/main/java/org/elasticsearch/painless/symbol/Decorations.java @@ -365,6 +365,10 @@ public PainlessMethod getStandardPainlessMethod() { } } + public interface DynamicInvocation extends Condition { + + } + public static class GetterPainlessMethod implements Decoration { private final PainlessMethod getterPainlessMethod; diff --git a/modules/lang-painless/src/test/java/org/elasticsearch/painless/DynamicTypeTests.java b/modules/lang-painless/src/test/java/org/elasticsearch/painless/DynamicTypeTests.java new file mode 100644 index 0000000000000..6e46ebb3469d7 --- /dev/null +++ b/modules/lang-painless/src/test/java/org/elasticsearch/painless/DynamicTypeTests.java @@ -0,0 +1,172 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.painless; + +import org.elasticsearch.painless.action.PainlessExecuteAction.PainlessTestScript; +import org.elasticsearch.painless.spi.Whitelist; +import org.elasticsearch.painless.spi.WhitelistLoader; +import org.elasticsearch.script.ScriptContext; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class DynamicTypeTests extends ScriptTestCase { + + @Override + protected Map, List> scriptContexts() { + Map, List> contexts = new HashMap<>(); + List whitelists = new ArrayList<>(PainlessPlugin.BASE_WHITELISTS); + whitelists.add(WhitelistLoader.loadFromResourceFiles(PainlessPlugin.class, "org.elasticsearch.painless.test")); + whitelists.add(WhitelistLoader.loadFromResourceFiles(PainlessPlugin.class, "org.elasticsearch.painless.dynamic")); + contexts.put(PainlessTestScript.CONTEXT, whitelists); + return contexts; + } + + public interface DynI { + + } + + public static class DynA { + } + + public static class DynB extends DynA implements DynI { + } + + public static class DynC extends DynB { + } + + public static class DynD extends DynB { + public char letter() { + return 'D'; + } + } + + public static class DynE extends DynC { + public char letter() { + return 'E'; + } + } + + public static class DynF extends DynE { + } + + public static class DynG extends DynF { + public char letter() { + return 'G'; + } + + public int value() { + return 1; + } + } + + public void testDynamicTypeResolution() { + assertEquals('D', exec("DynamicTypeTests.DynI i = new DynamicTypeTests.DynD(); return i.letter()")); + assertEquals('E', exec("DynamicTypeTests.DynI i = new DynamicTypeTests.DynE(); return i.letter()")); + assertEquals('E', exec("DynamicTypeTests.DynI i = new DynamicTypeTests.DynF(); return i.letter()")); + assertEquals('G', exec("DynamicTypeTests.DynI i = new DynamicTypeTests.DynG(); return i.letter()")); + IllegalArgumentException iae = expectScriptThrows(IllegalArgumentException.class, + () -> exec("DynamicTypeTests.DynI i = new DynamicTypeTests.DynD(); return i.value()")); + assertTrue(iae.getMessage().contains("dynamic method") && iae.getMessage().contains("not found")); + iae = expectScriptThrows(IllegalArgumentException.class, + () -> exec("DynamicTypeTests.DynI i = new DynamicTypeTests.DynE(); return i.value()")); + assertTrue(iae.getMessage().contains("dynamic method") && iae.getMessage().contains("not found")); + iae = expectScriptThrows(IllegalArgumentException.class, + () -> exec("DynamicTypeTests.DynI i = new DynamicTypeTests.DynF(); return i.value()")); + assertTrue(iae.getMessage().contains("dynamic method") && iae.getMessage().contains("not found")); + assertEquals(1, exec("DynamicTypeTests.DynI i = new DynamicTypeTests.DynG(); return i.value()")); + + iae = expectScriptThrows(IllegalArgumentException.class, + () -> exec("DynamicTypeTests.DynA a = new DynamicTypeTests.DynD(); return a.letter()")); + assertTrue(iae.getMessage().contains("member method") && iae.getMessage().contains("not found")); + iae = expectScriptThrows(IllegalArgumentException.class, + () -> exec("DynamicTypeTests.DynA a = new DynamicTypeTests.DynE(); return a.letter()")); + assertTrue(iae.getMessage().contains("member method") && iae.getMessage().contains("not found")); + iae = expectScriptThrows(IllegalArgumentException.class, + () -> exec("DynamicTypeTests.DynA a = new DynamicTypeTests.DynF(); return a.letter()")); + assertTrue(iae.getMessage().contains("member method") && iae.getMessage().contains("not found")); + iae = expectScriptThrows(IllegalArgumentException.class, + () -> exec("DynamicTypeTests.DynA a = new DynamicTypeTests.DynG(); return a.letter()")); + assertTrue(iae.getMessage().contains("member method") && iae.getMessage().contains("not found")); + iae = expectScriptThrows(IllegalArgumentException.class, + () -> exec("DynamicTypeTests.DynA a = new DynamicTypeTests.DynD(); return a.value()")); + assertTrue(iae.getMessage().contains("member method") && iae.getMessage().contains("not found")); + iae = expectScriptThrows(IllegalArgumentException.class, + () -> exec("DynamicTypeTests.DynA a = new DynamicTypeTests.DynE(); return a.value()")); + assertTrue(iae.getMessage().contains("member method") && iae.getMessage().contains("not found")); + iae = expectScriptThrows(IllegalArgumentException.class, + () -> exec("DynamicTypeTests.DynA a = new DynamicTypeTests.DynF(); return a.value()")); + assertTrue(iae.getMessage().contains("member method") && iae.getMessage().contains("not found")); + iae = expectScriptThrows(IllegalArgumentException.class, + () -> exec("DynamicTypeTests.DynA a = new DynamicTypeTests.DynG(); return a.value()")); + assertTrue(iae.getMessage().contains("member method") && iae.getMessage().contains("not found")); + + assertEquals('D', exec("DynamicTypeTests.DynB b = new DynamicTypeTests.DynD(); return b.letter()")); + assertEquals('E', exec("DynamicTypeTests.DynB b = new DynamicTypeTests.DynE(); return b.letter()")); + assertEquals('E', exec("DynamicTypeTests.DynB b = new DynamicTypeTests.DynF(); return b.letter()")); + assertEquals('G', exec("DynamicTypeTests.DynB b = new DynamicTypeTests.DynG(); return b.letter()")); + iae = expectScriptThrows(IllegalArgumentException.class, + () -> exec("DynamicTypeTests.DynB b = new DynamicTypeTests.DynD(); return b.value()")); + assertTrue(iae.getMessage().contains("dynamic method") && iae.getMessage().contains("not found")); + iae = expectScriptThrows(IllegalArgumentException.class, + () -> exec("DynamicTypeTests.DynB b = new DynamicTypeTests.DynE(); return b.value()")); + assertTrue(iae.getMessage().contains("dynamic method") && iae.getMessage().contains("not found")); + iae = expectScriptThrows(IllegalArgumentException.class, + () -> exec("DynamicTypeTests.DynB b = new DynamicTypeTests.DynF(); return b.value()")); + assertTrue(iae.getMessage().contains("dynamic method") && iae.getMessage().contains("not found")); + assertEquals(1, exec("DynamicTypeTests.DynB b = new DynamicTypeTests.DynG(); return b.value()")); + + iae = expectScriptThrows(IllegalArgumentException.class, + () -> exec("DynamicTypeTests.DynC c = new DynamicTypeTests.DynE(); return c.letter()")); + assertTrue(iae.getMessage().contains("member method") && iae.getMessage().contains("not found")); + iae = expectScriptThrows(IllegalArgumentException.class, + () -> exec("DynamicTypeTests.DynC c = new DynamicTypeTests.DynF(); return c.letter()")); + assertTrue(iae.getMessage().contains("member method") && iae.getMessage().contains("not found")); + iae = expectScriptThrows(IllegalArgumentException.class, + () -> exec("DynamicTypeTests.DynC c = new DynamicTypeTests.DynG(); return c.letter()")); + assertTrue(iae.getMessage().contains("member method") && iae.getMessage().contains("not found")); + iae = expectScriptThrows(IllegalArgumentException.class, + () -> exec("DynamicTypeTests.DynC c = new DynamicTypeTests.DynE(); return c.value()")); + assertTrue(iae.getMessage().contains("member method") && iae.getMessage().contains("not found")); + iae = expectScriptThrows(IllegalArgumentException.class, + () -> exec("DynamicTypeTests.DynC c = new DynamicTypeTests.DynF(); return c.value()")); + assertTrue(iae.getMessage().contains("member method") && iae.getMessage().contains("not found")); + iae = expectScriptThrows(IllegalArgumentException.class, + () -> exec("DynamicTypeTests.DynC c = new DynamicTypeTests.DynG(); return c.value()")); + assertTrue(iae.getMessage().contains("member method") && iae.getMessage().contains("not found")); + + assertEquals('D', exec("DynamicTypeTests.DynD d = new DynamicTypeTests.DynD(); return d.letter()")); + iae = expectScriptThrows(IllegalArgumentException.class, + () -> exec("DynamicTypeTests.DynD d = new DynamicTypeTests.DynD(); return d.value()")); + assertTrue(iae.getMessage().contains("member method") && iae.getMessage().contains("not found")); + + assertEquals('E', exec("DynamicTypeTests.DynE e = new DynamicTypeTests.DynE(); return e.letter()")); + assertEquals('E', exec("DynamicTypeTests.DynE e = new DynamicTypeTests.DynF(); return e.letter()")); + assertEquals('G', exec("DynamicTypeTests.DynE e = new DynamicTypeTests.DynG(); return e.letter()")); + iae = expectScriptThrows(IllegalArgumentException.class, + () -> exec("DynamicTypeTests.DynE e = new DynamicTypeTests.DynE(); return e.value()")); + assertTrue(iae.getMessage().contains("dynamic method") && iae.getMessage().contains("not found")); + iae = expectScriptThrows(IllegalArgumentException.class, + () -> exec("DynamicTypeTests.DynE e = new DynamicTypeTests.DynF(); return e.value()")); + assertTrue(iae.getMessage().contains("dynamic method") && iae.getMessage().contains("not found")); + assertEquals(1, exec("DynamicTypeTests.DynE e = new DynamicTypeTests.DynG(); return e.value()")); + + assertEquals('E', exec("DynamicTypeTests.DynF f = new DynamicTypeTests.DynF(); return f.letter()")); + assertEquals('G', exec("DynamicTypeTests.DynF f = new DynamicTypeTests.DynG(); return f.letter()")); + iae = expectScriptThrows(IllegalArgumentException.class, + () -> exec("DynamicTypeTests.DynF f = new DynamicTypeTests.DynF(); return f.value()")); + assertTrue(iae.getMessage().contains("dynamic method") && iae.getMessage().contains("not found")); + assertEquals(1, exec("DynamicTypeTests.DynF f = new DynamicTypeTests.DynG(); return f.value()")); + + assertEquals('G', exec("DynamicTypeTests.DynG g = new DynamicTypeTests.DynG(); return g.letter()")); + assertEquals(1, exec("DynamicTypeTests.DynG g = new DynamicTypeTests.DynG(); return g.value()")); + } +} diff --git a/modules/lang-painless/src/test/resources/org/elasticsearch/painless/org.elasticsearch.painless.dynamic b/modules/lang-painless/src/test/resources/org/elasticsearch/painless/org.elasticsearch.painless.dynamic new file mode 100644 index 0000000000000..ec857f7cb53aa --- /dev/null +++ b/modules/lang-painless/src/test/resources/org/elasticsearch/painless/org.elasticsearch.painless.dynamic @@ -0,0 +1,34 @@ +class org.elasticsearch.painless.DynamicTypeTests$DynI @dynamic_type { +} + +class org.elasticsearch.painless.DynamicTypeTests$DynA { + () +} + +class org.elasticsearch.painless.DynamicTypeTests$DynB @dynamic_type { + () +} + +class org.elasticsearch.painless.DynamicTypeTests$DynC { + () +} + +class org.elasticsearch.painless.DynamicTypeTests$DynD @dynamic_type { + () + char letter() +} + +class org.elasticsearch.painless.DynamicTypeTests$DynE @dynamic_type { + () + char letter() +} + +class org.elasticsearch.painless.DynamicTypeTests$DynF @dynamic_type { + () +} + +class org.elasticsearch.painless.DynamicTypeTests$DynG @dynamic_type { + () + char letter() + int value() +} \ No newline at end of file