Skip to content

Support property access after array index / method call / parenthesized expression (unified postfix chain) #706

Description

@killme2008

Background

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:

primary ( '[' index ']' | '(' args ')' | '.' identifier )*

Changes:

  1. 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".
  2. 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).
  3. ExpressionParser: in the unified suffix loop after variable / method / array access in parseFactor0, handle . identifier and emit onGetProperty.
  4. 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.

Related to #606.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions