You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
5.4.4 (#693 / #704) let variables support dotted property chains + array indexing without the # prefix, e.g. foo.bars[0].name and response.model_details[0].score now evaluate correctly.
But the property chain breaks as soon as it follows an array access / method call / parenthesized expression. Verified on 5.4.4:
Expression
Result
o.oprninfos[0].name (pure dotted-variable prefix)
✅ OK
xs[0].name (single variable + index + property)
❌ unexpect token ']'
(xs[0]).name
❌ unexpect token ')'
seq.get(o.oprninfos, 0).name (property on a function return value)
❌ unexpect token ')'
take_while(coll, fn)[0].name
❌ unexpect token ']'
See #606 for the user-facing case (taking an element's property from a take_while result — currently no pure-expression form).
Root cause
Property access . is not a first-class parser / codegen operation today; it is folded into the "dotted variable name":
ExpressionLexer.scanVariable scans foo.bars[0].name (with a mid-chain integer-literal index) into a single Variable token, lexeme = "foo.bars[0].name".
At evaluation time AviatorJavaType detects the . in the lexeme and delegates to Reflector for level-by-level resolution (full-key first, then property chain).
Array access [...] (CodeGenerator.onArray/onArrayIndexStart/onArrayIndexEnd) and method call (onMethodName/onMethodInvoke) are independent runtime operations in the parser.
CodeGenerator has no property-access primitive (nothing like onGetProperty).
So: as long as the chain prefix can be merged by the lexer into one dotted variable, it works; once the chain contains an array access / method call / parenthesized expression, the top of stack becomes an intermediate result, and the following .prop has no "get property of the top-of-stack object" operation to lower to. parseFactor0 does not handle . after ExpressionParser.arrayAccess(), so it reports a syntax error.
#704 already established that the lexer can only merge a mid-chain integer literal index (Reflector.fastGetProperty's bracket handling only supports Integer.valueOf); dynamic indices / function return values cannot be merged at the lexer stage. So "make the lexer absorb more" is not a viable path.
Proposed approach
Promote property access to a first-class postfix operation in the parser, unifying the primary suffix into:
Add a property-access primitive to CodeGenerator, e.g. onGetProperty(Token<?> propertyName) — semantics: "pop the top-of-stack object, get its property named propertyName, push the result".
Implement in both backends:
InterpretCodeGenerator: add a GetPropertyIR, resolved against the top-of-stack value in InterpretContext using the existing property-resolution logic.
ASMCodeGenerator: emit bytecode that calls a runtime helper (reusing Reflector's property resolution).
ExpressionParser: in the unified suffix loop after variable / method / array access in parseFactor0, handle . identifier and emit onGetProperty.
Compatibility & semantics:
Keep the lexer's merge fast-path for pure dotted-variable prefixes to preserve the current full-key precedence semantics and performance; only fall back to the parser postfix when the chain contains an array access / method call / parenthesized expression.
Reuse Reflector's existing property resolution and NoSuchProperty semantics for consistency.
Mind the interaction with future nil-safe navigation ([建议]支持null-safety引用变量 #608) and options like USE_USER_ENV_AS_TOP_ENV_DIRECTLY.
Impact / risk
Touches the CodeGenerator interface + ASM / interpreter backends + the parser suffix chain — a medium-to-large change.
The ASM backend needs a stable runtime helper and correct bytecode.
Must cover: dynamic index a[i].b, multi-dimensional a[0][1].b, function return value f(x).b, parenthesized (x).b, and combinations with the existing dotted-variable chain.
Payoff: supports any primary(.prop | [idx] | (args))* combination in one shot, removing the current "only pure dotted-variable prefix works" inconsistency.
Background
5.4.4 (#693 / #704) let variables support dotted property chains + array indexing without the
#prefix, e.g.foo.bars[0].nameandresponse.model_details[0].scorenow evaluate correctly.But the property chain breaks as soon as it follows an array access / method call / parenthesized expression. Verified on 5.4.4:
o.oprninfos[0].name(pure dotted-variable prefix)xs[0].name(single variable + index + property)unexpect token ']'(xs[0]).nameunexpect token ')'seq.get(o.oprninfos, 0).name(property on a function return value)unexpect token ')'take_while(coll, fn)[0].nameunexpect token ']'See #606 for the user-facing case (taking an element's property from a
take_whileresult — currently no pure-expression form).Root cause
Property access
.is not a first-class parser / codegen operation today; it is folded into the "dotted variable name":ExpressionLexer.scanVariablescansfoo.bars[0].name(with a mid-chain integer-literal index) into a singleVariabletoken, lexeme ="foo.bars[0].name".AviatorJavaTypedetects the.in the lexeme and delegates toReflectorfor level-by-level resolution (full-key first, then property chain).[...](CodeGenerator.onArray/onArrayIndexStart/onArrayIndexEnd) and method call (onMethodName/onMethodInvoke) are independent runtime operations in the parser.CodeGeneratorhas no property-access primitive (nothing likeonGetProperty).So: as long as the chain prefix can be merged by the lexer into one dotted variable, it works; once the chain contains an array access / method call / parenthesized expression, the top of stack becomes an intermediate result, and the following
.prophas no "get property of the top-of-stack object" operation to lower to.parseFactor0does not handle.afterExpressionParser.arrayAccess(), so it reports a syntax error.#704already established that the lexer can only merge a mid-chain integer literal index (Reflector.fastGetProperty's bracket handling only supportsInteger.valueOf); dynamic indices / function return values cannot be merged at the lexer stage. So "make the lexer absorb more" is not a viable path.Proposed approach
Promote property access to a first-class postfix operation in the parser, unifying the primary suffix into:
Changes:
CodeGenerator, e.g.onGetProperty(Token<?> propertyName)— semantics: "pop the top-of-stack object, get its property namedpropertyName, push the result".InterpretCodeGenerator: add aGetPropertyIR, resolved against the top-of-stack value inInterpretContextusing the existing property-resolution logic.ASMCodeGenerator: emit bytecode that calls a runtime helper (reusingReflector's property resolution).ExpressionParser: in the unified suffix loop after variable / method / array access inparseFactor0, handle.identifier and emitonGetProperty.Reflector's existing property resolution andNoSuchPropertysemantics for consistency.USE_USER_ENV_AS_TOP_ENV_DIRECTLY.Impact / risk
CodeGeneratorinterface + ASM / interpreter backends + the parser suffix chain — a medium-to-large change.a[i].b, multi-dimensionala[0][1].b, function return valuef(x).b, parenthesized(x).b, and combinations with the existing dotted-variable chain.primary(.prop | [idx] | (args))*combination in one shot, removing the current "only pure dotted-variable prefix works" inconsistency.Related to #606.