Skip to content

fix(enums): validate trait constant reads and case collisions - #1827

Open
Guikingone wants to merge 7 commits into
illegalstudio:mainfrom
Guikingone:fix/1317-enum-trait-constant
Open

Guikingone wants to merge 7 commits into
illegalstudio:mainfrom
Guikingone:fix/1317-enum-trait-constant

Conversation

@Guikingone

@Guikingone Guikingone commented Oct 12, 2026 •

Copy link
Copy Markdown
Collaborator

Enum constants now enforce private/protected visibility at the read, before evaluating an inaccessible initializer. An unused parent:: initializer no longer rejects the declaration: its catchable Error is raised only when evaluation reaches that access. Public initializers retain the declaring enum scope when reading private constants, and unused constant reads keep their exceptions during optimization.

Direct and nested trait constants whose names exactly match a case are rejected. Member names remain case-sensitive. Regressions also protect lexical self::, magic-constant provenance and Reflection order provided by the enum metadata now on main.

Refs #1317. Rebased on a 8f730c5 after #1674 merged; its declaration walkers, schema/driver signatures and interface/reference-return behavior are preserved. The enum feature remains one commit above the independent TLS advisory fix #1954.

Validation on the final integrated compiler:

  • 23 focused standard-harness tests per IR mode, including the 12 PR controls and 11 related upstream enum regressions.
  • 32 fresh CLI materialization/access cases match PHP 8.5.10 with clean heaps.
  • 50 program/runtime assembly pairs across all five supported targets and both IR modes;102 fresh native macOS ARM64/Linux ARM64/Linux x86_64 executions match complete PHP output with clean heaps.
  • 20 target-specific checks exercise the four collision diagnostics. The example matches PHP with clean heaps in both IR modes.
  • Warning-free compiler build and matching bridge archives; source hygiene and Rust preambles pass.

The 20 iOS library witnesses contain real probe functions. Their evidence is assembly only; no iOS execution or full local suite is claimed. Instance/closure and Reflection controls compare output separately: a 40-byte enum-case receiver retention remains identical on the reviewed baseline and final compiler, so those controls make no heap-clean claim.

Full CI must validate the new exact head. Trail's lexical matches concern recorded old failures: the final code retains main's dedicated APIs, preserves lazy errors and throwing effects, and checks visibility before initializer inference. The accompanying integration learning supersedes the temporary old-base walker design.

@github-actions github-actions Bot added area:types Touches type checking, inference, or compatibility. size:s Small pull request. type:fix Corrects broken or incompatible behavior. labels Oct 12, 2026
@Guikingone Guikingone self-assigned this Oct 12, 2026
@Guikingone
Guikingone requested a review from nahime0 October 12, 2026 14:22
Comment thread src/types/checker/schema/enums.rs Outdated
Comment thread src/types/checker/schema/enums.rs Outdated
@nahime0

nahime0 commented Oct 13, 2026

Copy link
Copy Markdown
Member

Confidence Score: 3/5

[Medium risk] An enum can read constants imported from a used trait, and reflection lists them after the enum's own cases and constants.

Trait-constant expressions that use self:: are evaluated in the caller's scope, and a trait constant whose name matches a case is accepted.

Summary

build_enum_info now installs the flattened enum's constants, which already include constants merged in from used traits, so E::CONST resolves. Reflection order still interleaves the enum's own cases and constants by source span, then appends the trait-contributed names. The new codegen test covers a literal E::TC, a trait method reading it, and getConstants() order A,X,TC.

  • Flattened trait constants are passed through as merged_constants and stored on ClassInfo.
  • constant_order keeps the enum's own cases and constants in source order, then appends trait constants.
  • test_enum_reads_trait_constant checks the literal read, the trait method, and reflection order.
  • docs/php/classes.md documents trait constants on enums.
Diagram
flowchart LR
  traitConst[Trait constants] --> flatten[flatten_classes merge]
  enumOwn[Enum cases and constants] --> flatten
  flatten --> merged[merged_constants]
  enumOwn --> order[Span-sorted own order]
  merged --> values[ClassInfo constants]
  order --> constOrder[constant_order]
  merged --> constOrder
  values --> read["E::CONST"]
  constOrder --> refl[ReflectionClass getConstants]
Loading

Findings

  • [P1] src/types/checker/schema/enums.rs:252 — Trait constant self:: follows the caller
  • [P2] src/types/checker/schema/enums.rs:272 — Trait constant colliding with a case is accepted

Last reviewed commit: 92c86b3

@github-actions github-actions Bot added size:m Medium-sized pull request. and removed size:s Small pull request. labels Oct 14, 2026
@Guikingone

Copy link
Copy Markdown
Collaborator Author

Addressed both review threads in 489d45fd065fd3f1a40b0f14fcf8e327c2c88727.

  • Enum constant metadata now uses the existing recursive lexical class-constant rewriter with the consuming enum and no parent. Imported self::A and self::class keep that enum from global scope, functions and another class; nested traits bind independently for each consumer.
  • Trait expansion rejects case/constant name collisions before enum metadata is built. Direct, nested and grouped imports identify the directly used trait, and an own declaration uses PHP's class-constant redefinition diagnostic. Member names remain case-sensitive.

Validation: warning-free build; 14 trait and 55 enum codegen tests; 71 focused diagnostic tests. Four PHP 8.5.10 oracle fixtures with EIR on/off ran on macOS ARM64 and Linux ARM64/x86_64: 24 matching outputs, all heap-clean. Both iOS device/Simulator targets produced 24 assembled program/runtime objects with runtime-symbol closure; no iOS execution or full-local-suite claim.

The direct/Reflection comparison changes from 100:C|2:E to 2:E|2:E. Its retained counts are identical on the exact reviewed baseline and final compiler (1258 blocks / 288648 bytes), so Reflection is a functional control, with no heap-clean claim.

trail check reports four keyword conflicts against the accompanying learning's descriptions of the old defects. The code now rewrites the receivers and explicitly rejects the collisions; the regressions demonstrate those rules. The commit and plan record that disposition. Overlapping enum-metadata/late-static-default branches were observed and preserved.

@greptile-apps

greptile-apps Bot commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The PR changes constant behavior across enum reads and shared declaration checks.

The PR still needs the referenced-constant type check fixed before merging.

Fix All in Claude CodeFindings

  1. P1 Wrong constant types pass ▶
Fix with agent prompt
### Issue 1
src/types/checker/schema/class_constants.rs:119-121
The new loop trusts types from `collect_constants`, but that collector labels arrays and unresolved constant references as `Int`. For example, `const DATA = ['x']; class C { const int X = DATA; }` now passes the declared-type check even though `DATA` is an array. Before this change, the missing lookup stopped compilation.

Infer the referenced declaration's real type instead of using the collector's fallback.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

The PR makes enum constant reads enforce visibility before evaluating their values.

  • Enum constant reads keep the enum's scope and access rules.
  • Enums reject constants whose names exactly match a case.
  • Typed constant checks use the declared value and keep deferred errors lazy.
  • Internal notes record how the enum constant changes fit together.

Reviews (10) · Last reviewed commit: "fix(types): cover nested constant throws..." · Reviewed by Greptile

@Guikingone
Guikingone requested a review from nahime0 October 14, 2026 17:50
@Guikingone
Guikingone force-pushed the fix/1317-enum-trait-constant branch from 489d45f to 8bd617c Compare October 15, 2026 19:11
@Guikingone

Copy link
Copy Markdown
Collaborator Author

@nahime0 Ready to review when you have time 🚀

@nahime0 nahime0 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes requested on head 8bd617c1. Three problems, each checked against PHP 8.5.9.

1. Imported __CLASS__ / __TRAIT__ / __NAMESPACE__ constants crash the compiler

insert_enum_metadata stores every merged trait constant after resolve_lexical_enum_constant_value. That rewriter handles self:: and parent:: only, and ExprKind::MagicConstant is cloned unchanged (src/types/checker/schema/classes/constants.rs:364). Constant initializers are skipped by magic-constant lowering, and enum flattening rebinds __CLASS__ only on methods (src/types/traits.rs:308).

trait T { const N = __CLASS__; const TR = __TRAIT__; }
enum E { use T; case One; }
class C { static function read() { echo E::N, "|", E::TR, "\n"; } }
C::read();

PHP prints E|T. elephc panics before codegen with entered unreachable code: MagicConstant must be lowered before type inference (src/types/checker/inference/expr/calls_objects.rs:422). A namespaced trait panics the same way (PHP prints A|B\E|A\T).

Expected: lower magic constants while the declaring trait is still known. Keep __TRAIT__, __NAMESPACE__, __FUNCTION__ and __LINE__ on the trait, and rebind __CLASS__ to each consuming enum. Please add a regression that reads them from another class.

2. parent:: in an enum constant is a compile error even when the constant is never read

resolve_lexical_enum_constant_value always calls rewrite_expr with no parent (constants.rs:40, constants.rs:447), so any parent:: receiver fails with Class 'E' has no parent class while the enum metadata is built.

PHP compiles and runs enum E { case One; const A = parent::X; } and const A = [parent::X]. It throws a catchable Error (Cannot access "parent" when current class scope has no parent) only when the constant is read.

Expected: keep parent:: in the stored expression and raise the catchable Error on evaluation, without failing the enum declaration.

3. Private and protected trait constants imported by an enum are readable from global scope

Visibility is copied onto constant_visibilities, but infer_scoped_constant_access returns info.constants without consulting it (src/types/checker/schema/enums.rs:501, src/types/checker/inference/expr/class_refs.rs:125).

trait T { private const A = 1; public function read(): int { return self::A; } }
enum E { use T; case One; }
echo E::One->read(), "\n";
try { echo E::A, "\n"; } catch (Error $e) { echo "caught:", $e->getMessage(), "\n"; }

PHP prints 1, then caught:Cannot access private constant E::A. elephc prints 1 twice. The same lookup ignores protected constants.

Expected: outside the declaring enum, raise Cannot access private constant E::A / Cannot access protected constant E::A; reads inside the enum keep working.

@Guikingone
Guikingone force-pushed the fix/1317-enum-trait-constant branch from 8bd617c to d21c825 Compare October 20, 2026 13:59
@github-actions github-actions Bot added size:s Small pull request. and removed size:m Medium-sized pull request. labels Oct 20, 2026
Comment thread src/types/checker/schema/enums.rs Outdated
Comment thread src/types/checker/schema/enums.rs Outdated
@Guikingone
Guikingone force-pushed the fix/1317-enum-trait-constant branch from d21c825 to ccb9f79 Compare October 20, 2026 21:09
@github-actions github-actions Bot added area:optimizer Touches AST or EIR optimization passes. area:runtime Touches runtime helpers, GC, ownership, or bridge runtimes. scope:multi-area Touches more compiler areas than the automatic area-label cap. size:m Medium-sized pull request. and removed size:s Small pull request. labels Oct 20, 2026
@Guikingone Guikingone changed the title fix(types): let an enum read a constant it imports from a trait fix(enums): validate trait constant reads and case collisions Oct 20, 2026
Comment thread src/types/checker/schema/enums.rs
@Guikingone

Copy link
Copy Markdown
Collaborator Author

Thanks for the detailed pass. I re-checked all three scenarios against the current head: the intermediate commit on this branch (ccb9f7955f, which landed after your reviewed head 8bd617c1) already fixes them, so the behavior you asked for is in place. Evidence on 432a41d7c3, differential against PHP 8.5.10:

  1. Imported magic constants — trait T { const N = __CLASS__; const TR = __TRAIT__; const NS = __NAMESPACE__; } in namespace A, enum E { use T; case One; }, read from class C in namespace B: prints A\E|A\T|A exactly like PHP; no panic. The plain shape (E|T) matches too.

  2. parent:: in an enum constant — enum E { case One; const A = parent::X; const B = [parent::Y]; } compiles, prints declared, and reading either constant raises the catchable Error: Cannot access "parent" when current class scope has no parent — both the scalar and the array form.

  3. Visibility — private and protected imported trait constants are readable inside the enum (self::A from a trait method returns 1/2) and outside raise Cannot access private constant E::A / Cannot access protected constant E::B.

I added your exact shapes as AOT regressions in tests/codegen/oop/traits.rs (test_enum_imported_trait_constants_lower_magic_constants, test_enum_parent_constant_defers_to_a_catchable_error, test_enum_imported_trait_constant_visibility_is_enforced — the first one reads them from another class, as requested). oop::traits is 17/17 and the build is warning-free on 432a41d7c3.

Could you re-review at the current head? The PR head moved twice since your review (a rebase plus the collision-message test fix), and I'd like the CHANGES_REQUESTED resolved against the code that would actually merge.

@Guikingone

Copy link
Copy Markdown
Collaborator Author

Follow-up on the inline findings, all re-checked on the current head (b1de8d9bd7):\n\nFixed — declared type on a deferred parent:: constant (greptile, enums.rs:539). enum E { const int P = parent::X; } used to fail at declaration (Cannot use null as value for class constant E::P of type int) because validate_constant_declaration inferred the stored deferred Throw as void. It now skips the declared-type check for that deferred shape, so the program compiles and prints unused, and reading E::P raises the catchable Error like PHP; a non-deferred mismatch (const int P = "s") still errors. Regression test_typed_parent_constant_defers_to_a_catchable_error added.\n\nNot reproduced on this head (verified against PHP 8.5.10; the behavior is already PHP-exact):\n\n- self:: in trait constants — trait T { const A = 2; const B = self::A + 1; const N = self::class; } enum E { use T; } echo E::B, "|", E::N; prints 3|E, from global scope, a function and another class (3|E) — no Cannot use self:: outside a class context, no caller-scope resolution.\n- Trait constant colliding with a case — trait T { const A = 2; } enum E { use T; case A; } is rejected with exactly PHP's fatal: Cannot use trait T, because T::A conflicts with enum case E::A (the earlier getConstants() probe only looked accepted because that builtin is unsupported and errored first).\n- Magic constants and parent:: deferral from the review summary — the six shapes (__CLASS__/__TRAIT__/__NAMESPACE__ imported and read from another class, namespaced A\E|A\T|A, parent::X scalar and [parent::Y]) are all PHP-exact; the crash was on 8bd617c1 and is gone since ccb9f7955f.\n- Private/protected imported constants — Cannot access private constant E::A / Cannot access protected constant E::B outside the enum, self:: reads inside keep working.\n\nSuites: oop::traits 18 (four new regressions pinned to your shapes), error enums 80, build warning-free. Could you take another look at b1de8d9bd7?

Comment thread src/types/checker/schema/class_constants.rs
Reject own/direct/nested trait constants that collide with enum case names.
Keep missing parent constant accesses lazy and catchable, enforce private
and protected reads before evaluating inaccessible initializers, and
preserve declaration scope for public initializers using private values.
Model scoped reads as MAY_THROW so unused reads retain their exceptions.

Use the merged enum declaration APIs from main a8f730c without changing
its magic walkers, driver/schema argument order or reference-return paths.
Refs illegalstudio#1317; addresses the outstanding PR1827 review findings on its
existing branch, with regressions also covering lexical and magic scope.

Validation on the integrated compiler:23 focused tests per IR mode,
32 strict fresh CLI cases,50 target program/runtime assembly pairs with
102 clean native macOS/Linux executions,20 target diagnostics and the
example in both IR modes. iOS evidence is assembly only. Functional
receiver/Reflection controls are separate from strict heap assertions.

Trail reports20 lexical matches with old failure descriptions. The old
walker opt-in/signature extension is superseded by main's dedicated APIs.
Current source/artifact digests and fresh target executions validate the
rebase; iOS uses real function bodies. Parent errors remain lazy, visibility
precedes inference, and scoped reads carry throwing effects. The separate
TLS dependency removes the old roots wrapper. New immutable integration
learning records these decisions without editing earlier checkpoints.
The collision reached through a trait reports PHP's own wording — 'Cannot use trait T,
because T::A conflicts with enum case E::A' — while a direct enum constant reports the
'Eenum constant E::A conflicts with enum case' form. The test's fixtures are all
trait-imported, so it must assert the substring both share.
… trait constants

The current head already handles the three shapes from the review:
- trait magic constants lower against the declaring trait and __CLASS__ rebinds to the
  consuming enum, readable from another class across namespaces (A\E|A\T|A);
- parent:: in an enum constant keeps the declaration compiling and raises PHP's catchable
  Error on read, for the scalar and array forms;
- private/protected imported trait constants stay readable inside the enum and raise
  'Cannot access private|protected constant E::A' outside it.

These regressions pin the exact shapes against the reviewed head.
A parent:: initializer is stored as a deferred Throw that raises PHP's catchable Error only
when the constant is read, but validate_constant_declaration inferred it as void and rejected
the declaration against the declared type, so 'enum E { const int P = parent::X; }' failed to
compile even when the constant was never read (PHP compiles it and only reading fails).
Skip the declared-type check for the deferred shape; a non-deferred mismatch still errors.
PHP forbids `throw` in a constant expression, but the review bypass that defers a missing
`parent::` receiver skipped every `Throw`, so `const int X = throw new Exception();` was
accepted (typed and untyped, in classes, interfaces and traits). The deferred lexical Error is
inserted by the checker into the normalized value only, while `ClassConst::value` keeps the
source expression, so the check now scans the source and rejects it before the type check.
@Guikingone
Guikingone force-pushed the fix/1317-enum-trait-constant branch from b1de8d9 to 01bd5b6 Compare October 22, 2026 11:57
@github-actions github-actions Bot added area:eir Touches EIR definitions, lowering, validation, or passes. and removed area:runtime Touches runtime helpers, GC, ownership, or bridge runtimes. scope:multi-area Touches more compiler areas than the automatic area-label cap. labels Oct 22, 2026
…lizers

A class-constant initializer is a constant expression PHP evaluates lazily, on first read, so
`const int Z = Y;` is legal when `Y` is declared later in the file or imported with
`use const`. The declared-type check ran against `checker.constants`, which the statement
pass only fills when it reaches each declaration, so those initializers failed with
"Undefined constant" even though PHP accepts them (and even for a fully-qualified
`\A\XV` reference).

Seed the program's own top-level constants for the declared-type check, from the same
`collect_constants` scan codegen uses, and restore the map afterwards. A name declared
nowhere still fails closed, and a declared constant holding an incompatible value keeps
PHP's declared-type rejection (`Cannot use string as value for class constant ...`).
@Guikingone

Copy link
Copy Markdown
Collaborator Author

Both findings re-checked on the current head f37965995d (rebased onto 46dbde694c).

Finding 1 — invalid constants pass checks: fixed. ClassConst::value keeps the source expression; the checker's deferred lexical Error is inserted only into the normalized value. The bypass therefore scans the source and rejects a source-written throw with PHP's diagnostic before the type check. Covered: typed, untyped, nested (1 + throw), interface and trait. Test: test_error_source_throw_in_class_constant_initializer.

Finding 2 — unused typed constants fail: fixed, and the root cause was broader. enum E { const int P = parent::X; } compiles on this head, and reading E::P raises Error: Cannot access "parent" when current class scope has no parent, matching PHP 8.5.10 exactly.

While verifying the "imported typed constants" half I found the real defect: any typed constant naming a top-level constant failed with Undefined constant, even fully qualified:

const Y = 5; class C { const int Z = Y; }                       // error: Undefined constant: Y
namespace A { const XV = 5; }
namespace B { use const A\XV; class C { const int Y = XV; } }   // same

The declared-type check runs before the statement pass records top-level constants. It now seeds them from the same collect_constants scan codegen uses (and restores the map afterwards), so the declaration is accepted and the declared type is checked against the referenced constant's real value type. A name declared nowhere still fails closed, and an incompatible value keeps PHP's rejection (Cannot use string as value for class constant ...). Tests: test_typed_class_constant_names_a_declared_or_imported_constant, test_error_typed_constant_rejects_mismatched_referenced_constant.

Verification (PHP 8.5.10, both streams): enum parent:: constant (unused and read), parent constant in a class, namespaced use const, a later-declared constant, and a fully-qualified \A\XV reference all match. Suites: codegen enum 340, constant 688, class_const 73, oop::traits 18; error classes_traits 95, enum_trait_constants 4, property_default_scope 17, type_system 260; lib 3088; cargo build zero warnings; git diff --check clean.

One remaining, separate gap (pre-existing, not introduced here): a typed constant naming a missing member (const int Z = self::MISSING;, const int Z = NOPE;) is still rejected at declaration, while PHP accepts the declaration and fails on read — the untyped path already behaves like PHP. Filed as #2024.

@nahime0 could you re-review when you have a moment?

Comment on lines +119 to +121
for (name, (_, ty)) in declared_constants {
if !checker.constants.contains_key(name) {
checker.constants.insert(name.clone(), ty.clone());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Wrong constant types pass

The new loop trusts types from collect_constants, but that collector labels arrays and unresolved constant references as Int. For example, const DATA = ['x']; class C { const int X = DATA; } now passes the declared-type check even though DATA is an array. Before this change, the missing lookup stopped compilation.

Infer the referenced declaration's real type instead of using the collector's fallback.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/types/checker/schema/class_constants.rs
Line: 119-121

Comment:
**Wrong constant types pass**

The new loop trusts types from `collect_constants`, but that collector labels arrays and unresolved constant references as `Int`. For example, `const DATA = ['x']; class C { const int X = DATA; }` now passes the declared-type check even though `DATA` is an array. Before this change, the missing lookup stopped compilation.

Infer the referenced declaration's real type instead of using the collector's fallback.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex Fix in Cursor

…t types

Review of the typed-constant work found three gaps:

- contains_throw only knew the shapes contains_deferred_error walked, so a throw nested in
  @(...), an array element, a ternary, a call argument or a match arm was accepted. It now
  visits every ExprKind that can carry a sub-expression; closure bodies stay exempt because
  their throw runs when the closure is called.
- Top-level const declarations had no throw check at all. They now reject it at the same point
  as class constants, with PHP's 'Constant expression contains invalid operations'.
- Seeding a referenced constant's type trusted the prescan's Int fallback for an initializer it
  cannot fold, which rejected valid array constants and accepted an array as int. Only literal
  and array-literal types are seeded now; anything else seeds Mixed, which the declared-type
  check accepts.

PHP 8.5.10 differentials cover each shape; tests pin the nested-throw rejection, the
array-backed typed constant and the array-into-scalar rejection.
@Guikingone

Copy link
Copy Markdown
Collaborator Author

Second review round addressed on 7010a65d73.

1. Seeded types no longer trust the prescan's Int fallback. collect_constants maps any initializer it cannot fold to Int, so seeding that verbatim rejected valid array constants and let an array pass as int. Only literal types (and array literals, seeded as array<mixed>) are trusted now; everything else seeds Mixed, which the declared-type check accepts.

  • class C { const array A = LATER; } const LATER = [1, 2]; → compiles, count(C::A) is 2 (PHP: same).
  • define('F2', [1, 2]); class Z { const int G = F2; } → rejected with Cannot use array<mixed> as value for class constant Z::G of type int (PHP rejects at read, so this stays fail-closed instead of returning an array from an int constant).

2. contains_throw is now exhaustive. It matches every ExprKind with no _ arm (so a future variant fails compilation instead of silently escaping), skips closure and arrow bodies because their throw runs at call time, and visits closure parameter defaults. Top-level const declarations were not checked at all before; check_const_decl now rejects a source throw before inference, so all of

const X = @(throw new Exception());
const X = [throw new Exception()];
const X = true ? throw new Exception() : 1;
const X = 1 + (throw new Exception());
const X = boom(throw new Exception());

fail with PHP's Constant expression contains invalid operations, and a legal throw inside a closure body still compiles.

The remaining shapes that PHP rejects as non-constant expressions without a throw (const X = strlen("a");, const X = @(1 + 2);) are unaffected: validating that an initializer is a constant expression is a separate gap, tracked in #2024. The parent:: deferral and the source/normalized throw distinction are unchanged.

Verification (PHP 8.5.10, both streams): each shape above, plus the array-backed constant, the array-into-scalar rejection, the enum parent:: constant (unused and read), use const, later-declared and fully-qualified references. Suites: codegen class_const 73, constant 689, enum 340, oop::traits 18; error classes_traits 97, enum_trait_constants 4, property_default_scope 17, type_system 260; lib 3088; zero warnings; git diff --check clean.

Review: glm-5.3-flash and muse-spark-open both APPROVE 7010a65d73 after rebuilding and re-running their probes. kimi-k3 could not produce a verdict this round: four attempts across three sessions returned empty responses, including a trivial "reply with OK" ping, so the provider is likely unavailable rather than the review being skipped.

@Guikingone

Copy link
Copy Markdown
Collaborator Author

Checked the finding against 7010a65d73 — it does not reproduce, and the example stays rejected:

<?php
const DATA = ['x'];
class C { const int X = DATA; }
echo "unused\n";

elephc (7010a65d73):

error[3:21]: Cannot use array<mixed> as value for class constant C::X of type int

PHP 8.5.10 accepts the declaration (unused) and raises TypeError: Cannot assign array to class constant C::X of type int when C::X is read, so elephc is one step stricter (declaration-time instead of read-time) but does not "pass" the check.

The seeding does not trust the collector's fallback:

  • literal kinds keep the prescan type (IntLiteral, FloatLiteral, StringLiteral, BoolLiteral, Null);
  • an array literal is seeded as array<mixed>, which is why int is rejected above;
  • every other kind — including an unresolved constant reference — seeds Mixed, which the declared-type check accepts (strict_type_accepts treats Mixed as unknown), because a reference may resolve to a value declared later or imported with use const.

Mixed is deliberate rather than the collector's Int: seeding Int for an unfolder initializer is exactly what produced the false array/int rejections in the previous round. A reference whose real type is unknown cannot be checked at declaration time without resolving declarations transitively, which this PR does not attempt.

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

Labels

area:eir Touches EIR definitions, lowering, validation, or passes. area:optimizer Touches AST or EIR optimization passes. area:types Touches type checking, inference, or compatibility. size:m Medium-sized pull request. type:fix Corrects broken or incompatible behavior.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants