Skip to content

fix(types): reject late-static class names in property defaults - #1696

Open
nahime0 wants to merge 44 commits into
mainfrom
fix/1539-late-static-property-default
Open

nahime0 wants to merge 44 commits into
mainfrom
fix/1539-late-static-property-default

Conversation

@nahime0

@nahime0 nahime0 commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

Summary

Reject late-static property defaults while preserving lexical self/parent binding and PHP's declaration-versus-materialization boundary. Ordinary missing-parent constants produce a lazy catchable access Error; trait-imported class-name defaults preserve their distinct use Error. Reflection retains deferred default presence per slot, including native metadata accessed through dynamic eval.

Fix the confirmed CI regressions: inline ReflectionClass metadata guards now borrow their receiver, and ReflectionParameter retains the original self::/parent:: constant spelling.

Closes #1539

Validation

  • Rebased onto main 9e7cad3.
  • 44 focused tests pass: 24 native codegen cases (including three previously failing CI cases), eight EIR/optimizer cases, six diagnostic tests, four native metadata ABI tests and two eval cache format guards.
  • PHP 8.5.10 cross-checks, all-five-target emission, warning-free cargo build, assembly-comment checks and git diff --check pass.
  • Complete builtin documentation regeneration, catalog/site audit and target-architecture boundary audit pass, with no generated public builtin changes.
  • docs/php/classes.md reconciled with the corrected behavior.
  • Complete current-head CI is pending. Keep draft until full CI is green.

@github-actions github-actions Bot added area:types Touches type checking, inference, or compatibility. size:xs Very small pull request. type:fix Corrects broken or incompatible behavior. labels Sep 30, 2026
@nahime0
nahime0 marked this pull request as ready for review September 30, 2026 18:58
Comment thread src/types/checker/schema/classes/properties.rs Outdated
Comment thread src/types/checker/schema/classes/properties.rs Outdated
Comment thread src/types/checker/schema/classes/constants.rs Outdated
Comment thread src/types/checker/schema/classes/constants.rs Outdated
@nahime0

nahime0 commented Oct 1, 2026

Copy link
Copy Markdown
Member Author

Confidence Score: 3/5

[Medium risk] Rejects late-static static::class in stored property defaults while class schemas are built.

Promoted constructor defaults still miss the check and fail in the backend, and the static:: constant diagnostic does not match PHP.

Summary

Property defaults now go through the class-constant lexical walker. static::class is rejected with PHP's compile-time diagnostic, including inside arrays, while self::class, parent::class, and named Foo::class expressions are left unchanged. The new error tests cover instance properties, static properties, a one-level array, and early-bound acceptance at check time.

  • validate_lexical_property_default reuses rewrite_expr and discards the rewritten tree
  • apply_properties runs that check for every property that already stores a default
  • tests/error_tests/classes_traits.rs locks the static::class diagnostic and the early-bound cases
Diagram
flowchart LR
  prop["property default"] --> check["validate_lexical_property_default"]
  check --> walk["rewrite_expr"]
  walk --> reject["static::class error"]
  walk --> keep["keep original expression"]
  keep --> schema["apply_properties"]
Loading

Findings

  • [P1] src/types/checker/schema/classes/properties.rs:36 — Promoted constructor defaults skip the new check
  • [P2] src/types/checker/schema/classes/properties.rs:35 — Unused trait property defaults are not validated
  • [P2] src/types/checker/schema/classes/constants.rs:42 — static:: property defaults use a non-PHP diagnostic
  • [P2] src/types/checker/schema/classes/constants.rs:40 — parent:: property defaults do not match PHP

Last reviewed commit: 5bcbb5a

@github-actions github-actions Bot added area:runtime Touches runtime helpers, GC, ownership, or bridge runtimes. size:s Small pull request. and removed size:xs Very small pull request. labels Oct 1, 2026
@nahime0
nahime0 force-pushed the fix/1539-late-static-property-default branch from fc18c54 to 62c5d03 Compare October 2, 2026 13:48
@github-actions github-actions Bot removed the area:runtime Touches runtime helpers, GC, ownership, or bridge runtimes. label Oct 2, 2026
Comment thread src/types/checker/schema/classes/constants.rs Outdated
Comment thread src/types/checker/schema/classes/properties.rs Outdated
@nahime0

nahime0 commented Oct 5, 2026

Copy link
Copy Markdown
Member Author

Confidence Score: 2/5

[High risk] Rejects static::class in property defaults, then checks trait and promoted defaults without binding self:: or parent::.

Early-bound defaults still do not compile or print the class name, and a trait parent:: default is rejected at class build time instead of when the value is used.

Summary

Property defaults, including nested arrays, now go through the class-constant lexical walker. static::class is rejected with PHP's compile-time diagnostic, and other static:: receivers use "static::" is not allowed in compile-time constants. The walker resolves self:: and parent:: only to validate them, then discards the rewritten tree, so stored defaults stay lexical. Trait declarations are scanned before flattening, and promoted constructor parameters are scanned with the class properties.

  • validate_property_default_in_scope reuses rewrite_expr and drops the result
  • validate_trait_property_defaults rejects unused-trait static::class without binding parent
  • validate_promoted_defaults covers constructor-promoted initializers
  • New diagnostics in tests/error_tests/classes_traits.rs and tests/error_tests/property_default_scope.rs are checker-only

Earlier review on 5bcbb5a: promoted static::class skipping the check is fixed, unused-trait static::class is now rejected, and the static:: constant diagnostic matches PHP. The parent:: thread is still open: direct class properties now use PHP's missing-parent wording, but trait consumers do not.

Diagram
flowchart LR
  decl["property or promoted default"] --> walk["rewrite_expr"]
  walk --> drop["discard rewrite"]
  drop --> keep["keep self or parent"]
  keep --> objectNew["object_new literal default"]
  keep --> callSite["new call site"]
  traitUse["trait copied into class"] --> parentCheck["missing parent compile error"]
Loading

Findings

  • [P1] src/types/checker/schema/classes/constants.rs:49 — Early-bound defaults are validated and then discarded
  • [P1] src/types/checker/schema/classes/properties.rs:39 — Trait parent defaults are rejected before they are used

Last reviewed commit: 62c5d03

@nahime0
nahime0 marked this pull request as draft October 5, 2026 15:24
@nahime0
nahime0 force-pushed the fix/1539-late-static-property-default branch 2 times, most recently from ab8fd46 to 8fe7795 Compare October 5, 2026 16:30
@github-actions github-actions Bot added area:eir Touches EIR definitions, lowering, validation, or passes. area:optimizer Touches AST or EIR optimization passes. scope:multi-area Touches more compiler areas than the automatic area-label cap. size:l Large pull request. and removed size:s Small pull request. labels Oct 5, 2026
Comment thread src/types/checker/schema/classes/constants.rs Outdated
Comment thread src/types/checker/schema/classes/properties.rs Outdated
@nahime0

nahime0 commented Oct 6, 2026

Copy link
Copy Markdown
Member Author

Confidence Score: 2/5

[High risk] Binds self::class and parent::class in property defaults and defers an unbound trait parent until class initialization.

Direct parent::CONST defaults are rejected at compile time with the parent::class diagnostic, and clearing the unresolved tree makes ReflectionProperty::getDefaultValue() return null.

Summary

Property defaults, including nested arrays and promoted constructor parameters, are rewritten to early-bound class names before initialization. static::class and other static:: receivers are compile errors. A trait parent::class copied onto a class with no parent stays a legal declaration and raises a catchable Error at construction, static-property access, and the covered reflection entry points. Valid self:: and parent:: defaults print the declaring or consuming class.

  • normalize_property_default_in_scope stores the rewritten default instead of discarding it
  • Trait imports record deferred_property_default_error and the by-name allocator runs that thunk before allocation
  • Native regressions cover binding, declaration-only consumers, explicit promoted arguments, and initialization boundaries

Earlier review on 5bcbb5a: promoted static::class skipping the check is fixed (validate_promoted_defaults); unused-trait static::class is fixed (validate_trait_property_defaults); the static:: constant diagnostic is fixed. The parent::class wording and trait deferral are fixed for parent::class; parent::CONST is a new mismatch.
Earlier review on 62c5d03: discarded early-bound defaults are fixed (stored rewrites; test_property_default_review_lexical_class_names prints Defaults\Leaf|Defaults\Leaf|Defaults\Base). Trait parent::class rejected before use is fixed (declaration-only programs typecheck and throw only at initialization).

Findings

  • [P1] src/types/checker/schema/classes/constants.rs:52 — Missing parent constants use the class-name diagnostic
  • [P1] src/types/checker/schema/classes/properties.rs:55 — Dropped defaults make ReflectionProperty return null
Diagram
flowchart LR
  decl["property or promoted default"] --> bind["normalize_property_default_in_scope"]
  bind --> literal["string literal self or parent"]
  bind --> reject["compile error"]
  traitUse["trait parent on class with no parent"] --> flag["deferred_property_default_error"]
  flag --> init["new, static access, by-name alloc"]
  flag --> dropDefault["stored default becomes None"]
Loading

Last reviewed commit: c782151

@nahime0
nahime0 force-pushed the fix/1539-late-static-property-default branch from c782151 to 1fd5d84 Compare October 6, 2026 13:27

nahime0 commented Oct 6, 2026

Copy link
Copy Markdown
Member Author

Review follow-up published at 1fd5d84, aligned with main 9e7cad3. Both current P1 findings are fixed and regression-tested, including native reflection inside dynamic eval. The three source failures in the previous CI run are reproduced and corrected without weakening their fixtures. 44 focused checks and the full builtin documentation audit pass locally. The new complete matrix is pending, so this PR remains draft.

@github-actions github-actions Bot added area:magician Touches eval, include execution, or elephc-magician. and removed area:optimizer Touches AST or EIR optimization passes. labels Oct 6, 2026
@nahime0
nahime0 marked this pull request as ready for review October 7, 2026 09:07
Comment thread src/codegen/lower_inst/objects/reflection/parameter_property_emit.rs Outdated
Comment thread src/types/checker/schema/defaults.rs Outdated
@nahime0

nahime0 commented Oct 7, 2026

Copy link
Copy Markdown
Member Author

Confidence Score: 2/5

High risk Deferred missing-parent defaults are preserved for properties and calls, but parameter reflection and trait reflection metadata still disagree with PHP.

ReflectionParameter must report a deferred missing-parent default and throw Error from getDefaultValue, and reflecting a class that uses a trait with parent::class must compile.

Summary

Property and method defaults now keep lexical self::/parent:: binding, reject static:: at compile time, and turn an unresolved parent:: into a stored throw new Error(...) instead of dropping the default. Direct parent::class stays a compile error; ordinary parent::CONST and trait-imported parent::class stay legal until materialization. ReflectionProperty recognizes that throw, but ReflectionParameter does not, and the separate trait-method snapshot still contains the raw parent:: receiver.

  • Class, interface, and trait-consumer signatures store a deferred Error for unresolved parent:: defaults.
  • ReflectionProperty::hasDefaultValue() stays true and getDefaultValue() throws that slot's Error.
  • Focused codegen and diagnostic tests cover property initialization, lexical class names, and the Cannot access versus Cannot use split.
  • Parameter reflection and trait reflection metadata are not covered by those tests.
Diagram
flowchart LR
    src["parent default"] --> split{"receiver"}
    split -->|"static::"| compile["compile error"]
    split -->|"direct parent::class"| useErr["compile error: Cannot use"]
    split -->|"parent::CONST or trait parent::class"| defer["throw new Error stored on class signature"]
    defer --> call["omitted argument throws Error"]
    defer --> prop["ReflectionProperty DeferredError"]
    defer --> param["ReflectionParameter default_value None"]
    trait["trait snapshot keeps raw parent::class"] --> meta["ReflectionClass emits __traits"]
    meta --> abort["codegen: parent receiver in trait"]
Loading

Findings

  • [P1] src/codegen/lower_inst/objects/reflection/parameter_property_emit.rs:410 — ReflectionParameter drops deferred missing-parent defaults
  • [P1] src/types/checker/schema/defaults.rs:190 — Trait parent::class defaults abort reflection codegen

Earlier findings

  • [P1] Promoted constructor defaults skip the new check — fixed. validate_promoted_defaults runs from apply_properties, and test_property_default_scope_promoted_late_static rejects promoted static::class.
  • [P2] Unused trait property defaults are not validated — fixed. validate_trait_property_defaults checks the trait declaration, including unused traits (test_property_default_scope_unused_trait_late_static).
  • [P2] static:: property defaults use a non-PHP diagnostic — fixed. rewrite_constant_receiver reports "static::" is not allowed in compile-time constants (test_property_default_scope_late_static_constant_diagnostic).
  • [P2] parent:: property defaults do not match PHP — fixed. Direct parent::class is a compile-time Cannot use error (test_property_default_scope_parent_without_parent); a trait consumer compiles and throws that Error only at initialization.
  • [P1] Early-bound defaults are validated and then discarded — fixed. The rewrite is stored, and test_property_default_review_lexical_class_names prints the bound class names.
  • [P1] Trait parent defaults are rejected before they are used — fixed. A declaration-only consumer runs, and omitting a trait method argument throws Cannot use "parent"... at the call.
  • [P1] Missing parent constants use the class-name diagnostic — fixed. parent::A on a method and a promoted constructor compiles; an omitted argument throws Error: Cannot access "parent" when current class scope has no parent, and an explicit argument returns the passed value.
  • [P1] Dropped defaults make ReflectionProperty return null — fixed. The throw stays on the slot, reflection_property_slot_default_value maps it to DeferredError, and test_property_default_review_reflection_preserves_deferred_defaults covers hasDefaultValue / getDefaultValue.

Last reviewed commit: 1fd5d84

@github-actions github-actions Bot added area:codegen Touches target-aware assembly or backend lowering. and removed area:eir Touches EIR definitions, lowering, validation, or passes. labels Oct 18, 2026
Comment thread src/codegen/lower_inst/objects/reflection/property_defaults_fold.rs Outdated
@nahime0

nahime0 commented Oct 18, 2026

Copy link
Copy Markdown
Member Author

@greptile review

Comment thread src/codegen/const_default_values.rs
@nahime0

nahime0 commented Oct 18, 2026

Copy link
Copy Markdown
Member Author

@greptile review

@nahime0

nahime0 commented Oct 18, 2026

Copy link
Copy Markdown
Member Author

@greptile review

@nahime0
nahime0 marked this pull request as ready for review October 18, 2026 16:24
@nahime0
nahime0 marked this pull request as draft October 18, 2026 19:40
@nahime0

nahime0 commented Oct 18, 2026

Copy link
Copy Markdown
Member Author

@greptile review

@nahime0

nahime0 commented Oct 18, 2026

Copy link
Copy Markdown
Member Author

@greptile review

Comment thread src/optimize/exception_flow.rs Outdated
Comment thread src/codegen/lower_inst/builtins/eval/default_source.rs
Comment thread src/codegen/lower_inst/builtins/eval/default_source.rs Outdated
Comment thread src/codegen/block_emit.rs
Comment thread src/optimize/exception_flow.rs Outdated
@nahime0

nahime0 commented Oct 18, 2026

Copy link
Copy Markdown
Member Author

@greptile review

@nahime0

nahime0 commented Oct 19, 2026

Copy link
Copy Markdown
Member Author

@greptile review

@nahime0

nahime0 commented Oct 19, 2026

Copy link
Copy Markdown
Member Author

@greptileai Please review current head 5601227 after integration with main and the deferred-default compound-order fix. Grok review is clean on this exact head. Focused strict-heap IR-on/off, metadata-aware DCE and all-five-target checks pass. Draft remains until fresh review and complete CI pass.

Comment thread src/codegen/lower_inst/builtins/eval/default_source.rs
Comment thread src/ir_lower/stmt/static_property_writes.rs
@nahime0

nahime0 commented Oct 19, 2026

Copy link
Copy Markdown
Member Author

@greptile-apps please review the current head bdab1d8. The integer-minimum expression encoding is fixed, with baseline-failing native/eval regression tests. The computed string-index claim was independently traced through parser-generated PhpLocal ownership and is covered by heap-clean throw regressions in both IR modes. Grok review is clean on this exact head.

@nahime0
nahime0 marked this pull request as ready for review October 19, 2026 18:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:codegen Touches target-aware assembly or backend lowering. area:magician Touches eval, include execution, or elephc-magician. area:types Touches type checking, inference, or compatibility. scope:multi-area Touches more compiler areas than the automatic area-label cap. size:xl Very large pull request that needs deliberate review planning. type:fix Corrects broken or incompatible behavior.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reject static::class as a property default at check time

1 participant