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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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