Fix reserved variable availability in lambdas in Painless
This commit is contained in:
parent
4a7b70cc08
commit
ced433e9a8
|
@ -54,11 +54,21 @@ public final class Locals {
|
|||
/** Reserved word: unused */
|
||||
public static final String DOC = "doc";
|
||||
|
||||
/** Map of always reserved keywords */
|
||||
public static final Set<String> KEYWORDS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
|
||||
/** Map of always reserved keywords for the main scope */
|
||||
public static final Set<String> MAIN_KEYWORDS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
|
||||
THIS,PARAMS,SCORER,DOC,VALUE,SCORE,CTX,LOOP
|
||||
)));
|
||||
|
||||
/** Map of always reserved keywords for a function scope */
|
||||
public static final Set<String> FUNCTION_KEYWORDS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
|
||||
THIS,LOOP
|
||||
)));
|
||||
|
||||
/** Map of always reserved keywords for a lambda scope */
|
||||
public static final Set<String> LAMBDA_KEYWORDS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
|
||||
THIS,LOOP
|
||||
)));
|
||||
|
||||
/** Creates a new local variable scope (e.g. loop) inside the current scope */
|
||||
public static Locals newLocalScope(Locals currentScope) {
|
||||
return new Locals(currentScope);
|
||||
|
@ -71,7 +81,7 @@ public final class Locals {
|
|||
*/
|
||||
public static Locals newLambdaScope(Locals programScope, Type returnType, List<Parameter> parameters,
|
||||
int captureCount, int maxLoopCounter) {
|
||||
Locals locals = new Locals(programScope, returnType);
|
||||
Locals locals = new Locals(programScope, returnType, LAMBDA_KEYWORDS);
|
||||
for (int i = 0; i < parameters.size(); i++) {
|
||||
Parameter parameter = parameters.get(i);
|
||||
// TODO: allow non-captures to be r/w:
|
||||
|
@ -90,7 +100,7 @@ public final class Locals {
|
|||
|
||||
/** Creates a new function scope inside the current scope */
|
||||
public static Locals newFunctionScope(Locals programScope, Type returnType, List<Parameter> parameters, int maxLoopCounter) {
|
||||
Locals locals = new Locals(programScope, returnType);
|
||||
Locals locals = new Locals(programScope, returnType, FUNCTION_KEYWORDS);
|
||||
for (Parameter parameter : parameters) {
|
||||
locals.addVariable(parameter.location, parameter.type, parameter.name, false);
|
||||
}
|
||||
|
@ -103,7 +113,7 @@ public final class Locals {
|
|||
|
||||
/** Creates a new main method scope */
|
||||
public static Locals newMainMethodScope(Locals programScope, boolean usesScore, boolean usesCtx, int maxLoopCounter) {
|
||||
Locals locals = new Locals(programScope, Definition.OBJECT_TYPE);
|
||||
Locals locals = new Locals(programScope, Definition.OBJECT_TYPE, MAIN_KEYWORDS);
|
||||
// This reference. Internal use only.
|
||||
locals.defineVariable(null, Definition.getType("Object"), THIS, true);
|
||||
|
||||
|
@ -140,7 +150,7 @@ public final class Locals {
|
|||
|
||||
/** Creates a new program scope: the list of methods. It is the parent for all methods */
|
||||
public static Locals newProgramScope(Collection<Method> methods) {
|
||||
Locals locals = new Locals(null, null);
|
||||
Locals locals = new Locals(null, null, null);
|
||||
for (Method method : methods) {
|
||||
locals.addMethod(method);
|
||||
}
|
||||
|
@ -188,7 +198,7 @@ public final class Locals {
|
|||
if (hasVariable(name)) {
|
||||
throw location.createError(new IllegalArgumentException("Variable [" + name + "] is already defined."));
|
||||
}
|
||||
if (KEYWORDS.contains(name)) {
|
||||
if (keywords.contains(name)) {
|
||||
throw location.createError(new IllegalArgumentException("Variable [" + name + "] is reserved."));
|
||||
}
|
||||
return defineVariable(location, type, name, readonly);
|
||||
|
@ -214,6 +224,8 @@ public final class Locals {
|
|||
private final Locals parent;
|
||||
// return type of this scope
|
||||
private final Type returnType;
|
||||
// keywords for this scope
|
||||
private final Set<String> keywords;
|
||||
// next slot number to assign
|
||||
private int nextSlotNumber;
|
||||
// variable name -> variable
|
||||
|
@ -225,15 +237,16 @@ public final class Locals {
|
|||
* Create a new Locals
|
||||
*/
|
||||
private Locals(Locals parent) {
|
||||
this(parent, parent.getReturnType());
|
||||
this(parent, parent.returnType, parent.keywords);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Locals with specified return type
|
||||
*/
|
||||
private Locals(Locals parent, Type returnType) {
|
||||
private Locals(Locals parent, Type returnType, Set<String> keywords) {
|
||||
this.parent = parent;
|
||||
this.returnType = returnType;
|
||||
this.keywords = keywords;
|
||||
if (parent == null) {
|
||||
this.nextSlotNumber = 0;
|
||||
} else {
|
||||
|
|
|
@ -53,9 +53,6 @@ import static org.elasticsearch.painless.WriterConstants.CLASS_TYPE;
|
|||
*/
|
||||
public final class SFunction extends AStatement {
|
||||
public static final class FunctionReserved implements Reserved {
|
||||
public static final String THIS = "#this";
|
||||
public static final String LOOP = "#loop";
|
||||
|
||||
private int maxLoopCounter = 0;
|
||||
|
||||
public void markReserved(String name) {
|
||||
|
@ -63,7 +60,7 @@ public final class SFunction extends AStatement {
|
|||
}
|
||||
|
||||
public boolean isReserved(String name) {
|
||||
return name.equals(THIS) || name.equals(LOOP);
|
||||
return Locals.FUNCTION_KEYWORDS.contains(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -173,7 +170,7 @@ public final class SFunction extends AStatement {
|
|||
}
|
||||
|
||||
if (reserved.getMaxLoopCounter() > 0) {
|
||||
loop = locals.getVariable(null, FunctionReserved.LOOP);
|
||||
loop = locals.getVariable(null, Locals.LOOP);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@ public final class SSource extends AStatement {
|
|||
|
||||
@Override
|
||||
public boolean isReserved(String name) {
|
||||
return Locals.KEYWORDS.contains(name);
|
||||
return Locals.MAIN_KEYWORDS.contains(name);
|
||||
}
|
||||
|
||||
public boolean usesScore() {
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
|
||||
package org.elasticsearch.painless;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class LambdaTests extends ScriptTestCase {
|
||||
|
||||
public void testNoArgLambda() {
|
||||
|
@ -204,4 +207,28 @@ public class LambdaTests extends ScriptTestCase {
|
|||
public void testLambdaCaptureFunctionParam() {
|
||||
assertEquals(5, exec("def foo(int x) { Optional.empty().orElseGet(() -> x) } return foo(5);"));
|
||||
}
|
||||
|
||||
public void testReservedCapture() {
|
||||
String compare = "boolean compare(Supplier s, def v) {s.get() == v}";
|
||||
assertEquals(true, exec(compare + "compare(() -> new ArrayList(), new ArrayList())"));
|
||||
assertEquals(true, exec(compare + "compare(() -> { new ArrayList() }, new ArrayList())"));
|
||||
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
params.put("key", "value");
|
||||
params.put("number", 2);
|
||||
|
||||
assertEquals(true, exec(compare + "compare(() -> { return params['key'] }, 'value')", params, true));
|
||||
assertEquals(false, exec(compare + "compare(() -> { return params['nokey'] }, 'value')", params, true));
|
||||
assertEquals(true, exec(compare + "compare(() -> { return params['nokey'] }, null)", params, true));
|
||||
assertEquals(true, exec(compare + "compare(() -> { return params['number'] }, 2)", params, true));
|
||||
assertEquals(false, exec(compare + "compare(() -> { return params['number'] }, 'value')", params, true));
|
||||
assertEquals(false, exec(compare + "compare(() -> { if (params['number'] == 2) { return params['number'] }" +
|
||||
"else { return params['key'] } }, 'value')", params, true));
|
||||
assertEquals(true, exec(compare + "compare(() -> { if (params['number'] == 2) { return params['number'] }" +
|
||||
"else { return params['key'] } }, 2)", params, true));
|
||||
assertEquals(true, exec(compare + "compare(() -> { if (params['number'] == 1) { return params['number'] }" +
|
||||
"else { return params['key'] } }, 'value')", params, true));
|
||||
assertEquals(false, exec(compare + "compare(() -> { if (params['number'] == 1) { return params['number'] }" +
|
||||
"else { return params['key'] } }, 2)", params, true));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,32 @@ setup:
|
|||
x: "bbb"
|
||||
|
||||
- match: { hits.hits.0.fields.bar.0: "aaabbb"}
|
||||
---
|
||||
"Scripted Field Doing Compare":
|
||||
- do:
|
||||
search:
|
||||
body:
|
||||
script_fields:
|
||||
bar:
|
||||
script:
|
||||
inline: "boolean compare(Supplier s, def v) {return s.get() == v;}
|
||||
compare(() -> { return doc['foo'].value }, params.x);"
|
||||
params:
|
||||
x: "aaa"
|
||||
|
||||
- match: { hits.hits.0.fields.bar.0: true}
|
||||
- do:
|
||||
search:
|
||||
body:
|
||||
script_fields:
|
||||
bar:
|
||||
script:
|
||||
inline: "boolean compare(Supplier s, def v) {return s.get() == v;}
|
||||
compare(() -> { return doc['foo'].value }, params.x);"
|
||||
params:
|
||||
x: "bbb"
|
||||
|
||||
- match: { hits.hits.0.fields.bar.0: false}
|
||||
---
|
||||
"Scripted Field with a null safe dereference (non-null)":
|
||||
- do:
|
||||
|
|
Loading…
Reference in New Issue