feat: Phase 7 type resolution — return-aware loop inference & PHP class-property iterables#341
Conversation
…ontext object, pendingCallResults - Move extractReturnTypeName + helpers from call-processor.ts to type-extractors/shared.ts (breaks circular import risk: call-processor → type-env → type-extractors → call-processor) - Add SymbolTable.lookupFuzzyCallable(name) — lazy callable-only index, O(1) per call, invalidated on add(); avoids per-call .filter() on lookupFuzzy results - Add ReturnTypeLookup interface (conservative: undefined when 0 or 2+ callables match) - Add ForLoopExtractorContext interface — replaces 4 positional params with context object; update all 10 language extractor implementations (go, ts, py, jvm×2, cs, rs, rb, php, c-cpp) - Add PendingAssignment discriminated union (kind: 'copy' | 'callResult'); update PendingAssignmentExtractor in all 9 language extractors that implement it - Wire buildTypeEnv: build ReturnTypeLookup from optional symbolTable; split pendingAssignments into pendingCopies + pendingCallResults; add Tier 2b call-result propagation loop - Update call-processor.test.ts to import extractReturnTypeName from shared.ts
…oop extractors (7 languages) Extends for-loop type extraction in all 7 typed-iteration languages to resolve element types when the iterable is a direct function call. **New capability**: `for (var u : getUsers())` in Java, `for u in get_users()` in Python, `for user in getUsers()` in TypeScript, etc. now resolve `u`/`user` to the callee's return element type via lookupRawReturnType + extractElementTypeFromString. Changes per language: - types.ts: extend ReturnTypeLookup with lookupRawReturnType (raw return string for container-type extraction); update ForLoopExtractorContext with returnTypeLookup field - type-env.ts: implement lookupRawReturnType on the concrete ReturnTypeLookup built in buildTypeEnv (same guards as lookupReturnType, no extractReturnTypeName) - go.ts: call_expression branch in range_clause — identifier func or selector_expression method; existing isChannelType guards updated - typescript.ts: identifier fn branch inside call_expression handler - python.ts: identifier fn branch inside call handler - jvm.ts (Java): method_invocation without object field in enhanced_for_statement - jvm.ts (Kotlin): simple_identifier callee branch in call_expression node - csharp.ts: identifier fn branch in invocation_expression handler - rust.ts: identifier func branch in call_expression handler (alongside existing field_expression/method-call path) All branches follow the same conservative pattern: lookupRawReturnType(callee) → extractElementTypeFromString → bind loop var
…var class property scan Adds Strategy C to PHP's extractForLoopBinding for the pattern: foreach (\$this->property as \$item) when Strategy A (resolveIterableElementType) and Strategy B (scopeEnv lookup) both fail to find the element type. Strategy C: when the iterable is a member_access_expression with object '$this', walk up the AST to the enclosing class_declaration, scan its declaration_list for a property_declaration whose variable_name matches the property, and extract the element type from: 1. PHPDoc @var annotation on a preceding comment sibling (/** @var User[] */) 2. PHP 7.4+ native type field (e.g. UserRepo \$repo — skips generic 'array') This eliminates the @param workaround that was previously required in the php-foreach-member-access fixture (which used @param User[] \$users on the method to populate the method's scopeEnv with a \$users binding). New helpers in php.ts: - PHPDOC_VAR_RE: regex for @var extraction - extractClassPropertyElementType: reads @var or native type from a property_declaration - findClassPropertyElementType: scans class body for a named property Tests added (type-env.test.ts): - PHP: resolves from @var User[] without @param workaround - PHP: conservative — no binding for unknown property - PHP: multi-class file — both classes resolve independently Fixture updated (php-foreach-member-access/App.php): - Removed the @param User[] \$users workaround from processMembers() - Test now validates the natural class-property-based resolution path
Records that 7A (call_expression iterables, 7 languages), 7B (PHP $this->property via @var scan), and 7C (ReturnTypeLookup + context object) are all shipped. Adds implementation notes and strikethroughs on resolved language-specific gaps.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…n AGENTS.md and CLAUDE.md
CI Report✅ All checks passed Pipeline Status
Test Results
✅ All 2749 tests passed 20 test(s) skipped — expand for detailsIntegration:
Unit:
Code CoverageCombined (Unit + Integration)
Coverage breakdown by test suiteUnit Tests
Integration Tests
📋 View full run · Generated by CI |
…gration tests for 7 languages Add integration test coverage for Phase 7.3's call_expression iterable resolution across all 7 languages (Go, TypeScript, Python, Java, Kotlin, PHP, Rust). Each test creates a fixture with competing User/Repo classes that both define save(), then verifies for-loop iteration over a function call's return value resolves to the correct class. PHP was missing function_call_expression support in its for-loop extractor. Three changes fix this: - php.ts extractForLoopBinding: handle function_call_expression and member_call_expression iterables via returnTypeLookup - php.ts normalizePhpReturnType: preserve array notation (User[]) in SymbolTable so lookupRawReturnType returns useful container types - parse-worker.ts + parsing-processor.ts: upgrade uninformative AST return types (array, iterable) with PHPDoc @return annotations 35 new integration tests (5 per language), 2525 total tests passing.
|
@claude ultra-think while identifying per-language gaps for all of our supported languages (except swift). Perfom this analysis on this pull request and verify those findings. Act as a senior compiler font-end engineer and expert with static analysis tools and review this change from that perpective and architectural fit. Also check out the previous comments. You must also cross-reference it with our |
|
Claude finished @magyargergo's task in 7m 4s —— View job Phase 7 Type Resolution — Deep Review
|
| Gap | Severity | Notes |
|---|---|---|
Method-chained call iterable: for (const u of repo.getUsers()) |
Medium | fn.type === 'member_expression' path treats repo as the container, not calls returnTypeLookup on getUsers. Only bare function calls (getUsers()) use returnTypeLookup (typescript.ts:376–380). |
for (const u of this.getUsers()) |
Medium | fn.type === 'member_expression' with obj.type === 'this_expression' — not matched by obj.type === 'identifier' guard (typescript.ts:368) |
for...of object destructuring: for (const { id } of users) |
Low | Correctly skipped with a comment; minor missed opportunity |
No callResult from extractPendingAssignment |
Low | const users = getUsers() only resolves via call-processor.ts path |
Go
| Gap | Severity | Notes |
|---|---|---|
Channel type for call-expression iterables: for v := range getChan() |
Medium | extractElementTypeFromString('chan User') → undefined. The identifier-iterable path has isChannelType() handling but the call-expression path doesn't (go.ts:308–326). |
Multi-return non-error cases: a, b := getUser() where second var isn't err/ok/_ |
Low | scanConstructorBinding only binds first var when second is error sentinel (go.ts:154–168) |
Python
| Gap | Severity | Notes |
|---|---|---|
Method call iterable: for u in self.get_users() |
Medium | fn.type === 'attribute' path sets iterableName = 'self', tries scopeEnv lookup for self as a container — not useful. returnTypeLookup is not consulted for attribute-method calls (python.ts:249–254). |
for u in repo.get_users() |
Medium | Same issue — treated as repo container lookup, not return-type lookup on get_users |
| Generator function return type | Low | Generator[User, None, None] not parsed by extractElementTypeFromString |
PHP
| Gap | Severity | Notes |
|---|---|---|
self::$staticProperty / static::$property iterables |
Medium | Strategy C only handles $this->property via member_access_expression. Static property access (scope_resolution_expression) not covered (php.ts:499–512). |
normalizePhpType less conservative than other languages |
Medium | See architecture note above |
| No pattern binding extractor | Low | PHP has no extractPatternBinding |
Java
| Gap | Severity | Notes |
|---|---|---|
Java 21 switch expression patterns: obj switch { case User u -> ... } |
Low | type_pattern is handled in extractJavaPatternBinding (jvm.ts:222–233). Switch expressions may use different AST structure than switch statements. |
Receiver-qualified method call: for (var u : repo.getUsers()) |
Low | Handled! method_invocation with obj field → iterableName = obj.text → container lookup. If repo type is in scopeEnv, should resolve. |
Kotlin
| Gap | Severity | Notes |
|---|---|---|
| Similar to Java — generally solid | Low | The for-each path is shared via jvm.ts |
C#
| Gap | Severity | Notes |
|---|---|---|
Switch expression patterns: obj switch { User u => ... } |
Low | Switch statement declaration_pattern is handled. Switch expressions may differ in AST. |
foreach (var u in this.GetUsers()) |
Medium | invocation_expression with member_access_expression where expression is this — obj.type would be this_expression, not matched by obj.type === 'identifier' guard (csharp.ts:240–243) |
Rust
| Gap | Severity | Notes |
|---|---|---|
Nested Option/Result: if let Some(Ok(x)) = nested |
Medium | Only one unwrap level (Some/Ok/Err); nested wrappers not resolved (rust.ts:241–247) |
Struct field destructuring: if let User { name, email } = user |
Medium | extractCapturedPatternBindings handles captured_pattern but not struct field extraction — field types are Phase 8 |
For-loop over impl Iterator: for u in get_users() where return is impl Iterator<Item=User> |
Low | extractElementTypeFromString('impl Iterator<Item=User>') → undefined. The impl Trait syntax is not parsed. |
Ruby ⚠️ Not updated for Phase 7.3
| Gap | Severity | Notes |
|---|---|---|
| No call-expression iterable support at all | High | extractForLoopBinding doesn't destructure returnTypeLookup from context and has no call-expression path (ruby.ts:345). for user in get_users() is not resolved even if get_users is in the symbol table. |
| Block parameter inference: `users.each { | user | }` |
for user in method() on call node |
Medium | Only handles method name extraction for method chains like users.each, not standalone function calls |
C / C++ ⚠️ Not updated for Phase 7.3
| Gap | Severity | Notes |
|---|---|---|
| No standalone function call iterable support | High | extractForLoopBinding doesn't use returnTypeLookup. for (auto& user : getUsers()) where rightNode.type === 'call_expression' and function.type === 'identifier' → not handled (c-cpp.ts:323–337). |
Only container.method() call handled |
Medium | Only field_expression-based calls (like users.begin()). Direct calls like getUsers() are silently dropped. |
Cross-reference: type-resolution-roadmap.md
The roadmap is accurate. All Phase 7 items correctly show ✓ shipped status. The per-language gaps section correctly omits Ruby and C/C++ call-expression support (they're not listed). Two additions would complete the picture:
- Under Go: add "channel type for call-expression iterables" as a remaining gap
- Under Ruby: add "for...in call-expression iterables — excluded from Phase 7.3, no returnTypeLookup support"
- Under All languages: the
pendingCallResultsTier 2b path should be noted as "infrastructure shipped, not yet wired in extractors (Phase 9 prerequisite)"
Does type-resolution-system.md need updating? Yes.
The following sections are stale and need to be updated to reflect Phase 7:
1. Architecture → shared.ts entry (line 72): Add buildReturnTypeLookup / ReturnTypeLookup to the description.
2. Language Feature Matrix (line 369–379): The "For-loop element types" row should be annotated with two capability tiers:
- Identifier iterables: all languages with for-loop support
- Call-expression iterables (Phase 7.3): Go, TS, Python, Rust, Java, Kotlin, C# — not Ruby, C/C++
3. "Current Strengths" section (line 386): Add mention of Phase 7 — "call-expression iterables in for-loops" and "PHP class-property iterable resolution."
4. "Current Limitations" section (line 399): These are now resolved and should be struck or updated:
- "no broad expression-level return-type propagation inside TypeEnv" — partially resolved via Phase 7.3 for-loop paths
- The
$this->propertyPHP iterable limitation is resolved by Phase 7.4
5. Resolution Tiers → Tier 0b (line 209): Describe the new call-expression iterable path as a 4th strategy in resolveIterableElementType (it's actually a pre-check, not a 4th strategy, but worth documenting).
6. SymbolTable Interaction / Deferred Binding Verification sections (lines 346–363): The ReturnTypeLookup is now also used at TypeEnv build time (for-loop extractors), not just in call-processor.ts. The statement "what does not yet exist is feeding that information back into TypeEnv broadly enough to power loop inference" is now outdated — loop inference is exactly what Phase 7.3 delivers.
Code Quality
Minor nits:
-
php.ts:127—findClassPropertyElementTypefallback on last named child:?? classNode.namedChild(classNode.namedChildCount - 1);
This is a smell — if a
class_declarationever has a non-body last child (e.g., attribute), it would produce wrong results. ThechildForFieldName('body')path should be sufficient fortree-sitter-php. Consider removing the fallback or restricting it todeclaration_listtype. -
go.ts:359–373— Channel-type check for single-var range: the call-expression path (wheniterableNameis undefined) defaults to non-channel treatment conservatively. A comment explaining this assumption would be helpful. -
type-env.ts:475— TheforLoopCtxobject could be hoisted or reused to avoid per-node allocation, though this is micro-optimization territory. -
All extractors returning only
copyfromextractPendingAssignment— thePendingAssignmenttype signature permitscallResultbut it's never emitted. TypeScript's type system won't catch this gap. A runtime assertion or comment in each extractor like// TODO Phase 9: add callResult case for call_expression RHSwould help track this.
Summary
| Area | Assessment |
|---|---|
ReturnTypeLookup interface design |
✅ Clean, well-separated |
ForLoopExtractorContext migration |
✅ Good API design |
| Phase 7.3 (7 languages) | ✅ Solid, well-tested |
| Phase 7.4 PHP Strategy C | ✅ Elegant AST walk approach |
extractReturnTypeName deduplication |
✅ 137 lines cleaned up |
pendingCallResults Tier 2b |
|
| Ruby Phase 7.3 exclusion | |
| C/C++ Phase 7.3 exclusion | |
PHP normalizePhpType in call path |
|
type-resolution-system.md |
❌ Needs update to reflect Phase 7 additions |
type-resolution-roadmap.md |
✅ Accurate (minor additions suggested) |
The core Phase 7 delivery is well-engineered and the test coverage across all 7 Phase 7.3 languages is comprehensive. The main items to address before merge (in priority order):
- Clarify/resolve the
pendingCallResultsdead-code situation (documentation accuracy or light wire-up) - Update
type-resolution-system.mdto reflect Phase 7 - Consider extending Phase 7.3 to Ruby and C/C++ (or document the exclusion explicitly in the roadmap)
| Branch
… + dormant infrastructure docs - Replace normalizePhpType with extractElementTypeFromString in PHP call-expression foreach paths, aligning with all 6 other language extractors and preventing incorrect binding of bare non-container types like User - Add NOTE comments clarifying pendingCallResults Tier 2b is infrastructure-ready but no extractor populates it yet - Expand Go channel-type comments explaining why non-channel assumption is safe
|
@claude ultra-think while identifying per-language gaps for all of our supported languages (except swift). Perfom this analysis on this pull request and verify those findings. Act as a senior compiler font-end engineer and expert with static analysis tools and review this change from that perpective and architectural fit. Also check out the previous comments. You must also cross-reference it with our type-resolution-roadmap.md and see if we need to update type-resolution-system.md after this change. |
|
Claude finished @magyargergo's task in 4m 49s —— View job Phase 7 Type Resolution — Deep Review (Verification Pass)Tasks
I read every relevant source file (
|
| Gap | Severity | Verified? | Notes |
|---|---|---|---|
for (const u of this.getUsers()) |
Medium | ✅ Confirmed | fn.type === 'member_expression' with obj.type === 'this_expression' — neither identifier nor nested member_expression guard matches (typescript.ts:368,371) |
for (const u of repo.getUsers()) where repo is known |
Low | ✅ Works | obj.type === 'identifier' → iterableName = 'repo' → scopeEnv lookup on repo's type succeeds if repo is typed |
for (const u of this.repos.getUsers()) |
Low | ✅ Confirmed gap | obj.type === 'member_expression' extracts the inner property name (repos) → resolves as container, not via return type |
Go
| Gap | Severity | Verified? | Notes |
|---|---|---|---|
Channel-typed return: for v := range getChan() |
Medium | ✅ Confirmed | Code comment at go.ts:358–365 explicitly acknowledges this: "For call-expression iterables (iterableName undefined), conservative: treat as non-channel." The single-var case returns early when iterableName is undefined. This is a documented, intentional trade-off. |
Python
| Gap | Severity | Verified? | Notes |
|---|---|---|---|
for u in self.get_users() |
Medium | ✅ Confirmed | fn.type === 'attribute' with obj.type === 'identifier' → iterableName = obj.text = 'self' → tries to resolve 'self' as a container in scopeEnv (always fails since self isn't typed as a container) |
for u in repo.get_users() |
Medium | ✅ Confirmed | Same: iterableName = 'repo' → container lookup. Works only if repo is already in scopeEnv with a container-typed value |
Generator[User, None, None] return types |
Low | Likely | extractElementTypeFromString('Generator[User, None, None]') — outer [ parses as Swift array sugar branch, hits content check for User, None, None which fails \w+ test. Returns undefined. |
PHP
| Gap | Severity | Verified? | Notes |
|---|---|---|---|
static::$property / self::$property iterables |
Medium | ✅ Confirmed | Strategy C only guards iterableNode?.type === 'member_access_expression' with obj.text === '$this'. scope_resolution_expression nodes are never checked (php.ts:499–512). |
foreach ($this->getUsers() as $user) |
✅ Works | N/A | member_call_expression is handled — lookups returnTypeLookup.lookupRawReturnType(methodName.text) and calls extractElementTypeFromString. |
Java
| Gap | Severity | Verified? | Notes |
|---|---|---|---|
for (var u : repo.getUsers()) where repo is in scope |
✅ Works | N/A | method_invocation with obj.type === 'identifier' → iterableName = obj.text = 'repo' → falls through to resolveIterableElementType |
Direct call for (var u : getUsers()) |
✅ Works | N/A | !obj && name branch → lookupRawReturnType(name.text) → extractElementTypeFromString |
The JVM extractor is the most complete of the 7 Phase 7.3 languages.
Kotlin
| Gap | Severity | Verified? | Notes |
|---|---|---|---|
| Shares JVM for-loop path with Java | Confirmed | N/A | The extractKotlinForLoopBinding delegates upward through jvm.ts. Same coverage as Java. |
C#
| Gap | Severity | Verified? | Notes |
|---|---|---|---|
foreach (var u in this.GetUsers()) |
Medium | ✅ Confirmed | invocation_expression with fn.type === 'member_access_expression' and obj.type === 'this_expression' — fails the obj.type === 'identifier' guard at csharp.ts:242. |
foreach (var u in GetUsers()) |
✅ Works | N/A | fn.type === 'identifier' → lookupRawReturnType → extractElementTypeFromString |
Rust
| Gap | Severity | Verified? | Notes |
|---|---|---|---|
impl Iterator<Item=User> return types |
Low | ✅ Confirmed | extractElementTypeFromString('impl Iterator<Item=User>') — the impl keyword prefix means no opening < or [ at index 0; openAngle finds the < in Iterator< but the base check fails the \w+ test on impl Iterator. Returns undefined. |
Nested Option<Result<User, E>> |
Low | Confirmed | extractReturnTypeName unwraps one level (WRAPPER_GENERICS for Option/Result) but not nested wrappers |
Ruby ⚠️ Not updated for Phase 7.3
| Gap | Severity | Verified? | Notes |
|---|---|---|---|
for user in get_users() — no call-expression path |
High | ✅ Confirmed | extractForLoopBinding at ruby.ts:345 does NOT destructure returnTypeLookup from the context. For call nodes, it extracts method.text as iterableName and runs resolveIterableElementType('get_users', ...) — scopeEnv lookup for get_users returns nothing, YARD param lookup for get_users returns nothing. Silently produces no binding. |
| Block parameter inference `users.each { | user | }` | High |
C / C++ ⚠️ Not updated for Phase 7.3
| Gap | Severity | Verified? | Notes |
|---|---|---|---|
for (auto& user : getUsers()) — direct call |
High | ✅ Confirmed | extractForLoopBinding at c-cpp.ts:269 does NOT destructure returnTypeLookup. In the call_expression branch (c-cpp.ts:323–331), only field_expression-typed callees (i.e., container.method()) are handled. When fieldExpr.type === 'identifier' (bare function call like getUsers()), the branch falls through with iterableName undefined. |
Roadmap Accuracy (type-resolution-roadmap.md)
Two statements are inaccurate and contradict the code comments:
Line 86:
"Phase 7.2 also added
pendingCallResultspropagation:var x = f()assignments now propagate the inferred return type offinto the local scope env."
Line 100:
"var x = f() assignments propagate the element / receiver type for subsequent use in the same scope"
Both are false. The pendingCallResults processing loop is structurally complete but no extractor emits { kind: 'callResult' }. The code's own comments (added in this PR) explicitly call this out as dormant. The roadmap is contradicting the implementation.
Additionally, the per-language gap section doesn't document that Ruby and C/C++ were excluded from Phase 7.3, even though they support for-loops. A reader would need to infer this from the absence of those languages in the "all 7 typed-iteration languages" wording.
Does type-resolution-system.md Need Updating? Yes — 3 stale sections
1. shared.ts description in the Architecture table (line 72) — stale:
| `shared.ts` | Language-agnostic helpers such as `extractSimpleTypeName`, ...
Missing: extractElementTypeFromString, resolveIterableElementType, buildReturnTypeLookup/ReturnTypeLookup — these are now major residents of shared.ts.
2. "Deferred Binding Verification" paragraph (line 363) — stale:
"What does not yet exist is feeding that information back into
TypeEnvbroadly enough to power loop inference, general assignment propagation, and wider expression typing."
This sentence is now outdated. Phase 7.3 is exactly "feeding that information back into TypeEnv to power loop inference" — ReturnTypeLookup is threaded into every for-loop extractor via ForLoopExtractorContext. This is a significant correctness issue in the documentation because it describes Phase 7 work as future work.
3. "Current Limitations" (line 409) — partially stale:
"no broad expression-level return-type propagation inside
TypeEnv"
This is now partially resolved: for-loop iterables in 7 languages now use return-type propagation inside TypeEnv. It should be updated to distinguish "for-loop call-expression iterables (done, 7 languages)" from "general assignment return-type binding (pending, Phase 9)".
The Language Feature Matrix at line 375 should also add a sub-row or note distinguishing "identifier iterables" vs "call-expression iterables" since Ruby/C++ only support the former.
Code Quality: Additional Nits Not Covered Previously
-
go.ts:358–365— channel gap is documented, decision is defensible. The comment is accurate and the conservative trade-off is right. For call-expression iterables (getChan()returningchan User),extractElementTypeFromStringwouldn't handlechan Useranyway since it has no bracket syntax. Even ifisChannelTypewere called, it would need to inspect the raw return type string — a separate enhancement. -
jvm.ts:167—!obj && namefor direct calls is elegantly correct. In Java's tree-sitter grammar,getUsers()produces amethod_invocationwhereobjectfield is absent andnamefield holds the method name. The guard is exactly right. -
csharp.ts:244—fn.type === 'identifier'forGetUsers()works correctly. C#'sinvocation_expressionfor a bare call producesidentifieras the first child. Butthis.GetUsers()producesmember_access_expressionas first child withexpression=this_expression— the gap is confirmed at line 242. -
All
extractPendingAssignmentimplementations return onlycopykind. This is consistent. A// TODO Phase 9: callResult casecomment in even one extractor would make the intention clearer to contributors. Currently onlytype-env.tsmentions Phase 9.
Summary
| Finding | Previous Review | Verification |
|---|---|---|
pendingCallResults dormant + now has clarifying comments |
Identified (no comments yet) | ✅ Comments added — gap closed |
PHP normalizePhpType asymmetry |
❌ PHP uses extractElementTypeFromString — same as all other languages |
|
| Ruby call-expression gap | Identified | ✅ Confirmed at ruby.ts:345 |
| C/C++ call-expression gap | Identified | ✅ Confirmed at c-cpp.ts:269 |
TypeScript this.getUsers() gap |
Identified | ✅ Confirmed at typescript.ts:368 |
C# this.GetUsers() gap |
Identified | ✅ Confirmed at csharp.ts:242 |
| Go channel-from-call gap | Identified | ✅ Confirmed + code comment documents this trade-off |
Python self.get_users() gap |
Identified | ✅ Confirmed at python.ts:250 |
type-resolution-system.md stale |
Identified | ✅ Confirmed — 3 specific stale sections |
Roadmap pendingCallResults inaccuracy |
Not flagged | 🆕 Lines 86 and 100 are factually wrong |
findClassPropertyElementType fallback type smell |
Identified | ✅ Confirmed, low risk, easy guard |
Verdict: The Phase 7 core delivery is high quality — the architecture is sound, the test coverage is comprehensive, and the implementation is conservative in the right places. The two action items before merge are: (1) correct the two inaccurate roadmap sentences about pendingCallResults, and (2) update the stale paragraph in type-resolution-system.md at line 363. Extending Phase 7.3 to Ruby and C/C++ is desirable but can be a follow-up.
Branch |
Response to Verification Pass ReviewRe: PHP The verification pass quotes lines 452–465 as evidence that PHP already uses - if (rawReturn) callExprElementType = normalizePhpType(rawReturn);
+ if (rawReturn) callExprElementType = extractElementTypeFromString(rawReturn);The behavioral difference matters: Re: The review suggests Fixes applied in this push:
All 2835 tests pass. The PHP integration tests specifically confirm the call-expression paths work correctly with Acknowledged for follow-up (not in this PR):
|
…HP fallback guard - Roadmap lines 86/100: correct pendingCallResults from "active" to "dormant infrastructure (Phase 9)" - type-resolution-system.md line 363: update to reflect Phase 7.3 loop inference is delivered - type-resolution-system.md line 409: clarify for-loop call-expression resolution (done) vs general assignment propagation (pending) - php.ts:127: add declaration_list type guard on fallback to prevent silent wrong results
…ss-property iterables (abhigyanpatwari#341) * feat(type-resolution): Phase 7.1+7.2 foundation — ReturnTypeLookup, context object, pendingCallResults - Move extractReturnTypeName + helpers from call-processor.ts to type-extractors/shared.ts (breaks circular import risk: call-processor → type-env → type-extractors → call-processor) - Add SymbolTable.lookupFuzzyCallable(name) — lazy callable-only index, O(1) per call, invalidated on add(); avoids per-call .filter() on lookupFuzzy results - Add ReturnTypeLookup interface (conservative: undefined when 0 or 2+ callables match) - Add ForLoopExtractorContext interface — replaces 4 positional params with context object; update all 10 language extractor implementations (go, ts, py, jvm×2, cs, rs, rb, php, c-cpp) - Add PendingAssignment discriminated union (kind: 'copy' | 'callResult'); update PendingAssignmentExtractor in all 9 language extractors that implement it - Wire buildTypeEnv: build ReturnTypeLookup from optional symbolTable; split pendingAssignments into pendingCopies + pendingCallResults; add Tier 2b call-result propagation loop - Update call-processor.test.ts to import extractReturnTypeName from shared.ts * feat(type-resolution): Phase 7.3 — call_expression iterables in for-loop extractors (7 languages) Extends for-loop type extraction in all 7 typed-iteration languages to resolve element types when the iterable is a direct function call. **New capability**: `for (var u : getUsers())` in Java, `for u in get_users()` in Python, `for user in getUsers()` in TypeScript, etc. now resolve `u`/`user` to the callee's return element type via lookupRawReturnType + extractElementTypeFromString. Changes per language: - types.ts: extend ReturnTypeLookup with lookupRawReturnType (raw return string for container-type extraction); update ForLoopExtractorContext with returnTypeLookup field - type-env.ts: implement lookupRawReturnType on the concrete ReturnTypeLookup built in buildTypeEnv (same guards as lookupReturnType, no extractReturnTypeName) - go.ts: call_expression branch in range_clause — identifier func or selector_expression method; existing isChannelType guards updated - typescript.ts: identifier fn branch inside call_expression handler - python.ts: identifier fn branch inside call handler - jvm.ts (Java): method_invocation without object field in enhanced_for_statement - jvm.ts (Kotlin): simple_identifier callee branch in call_expression node - csharp.ts: identifier fn branch in invocation_expression handler - rust.ts: identifier func branch in call_expression handler (alongside existing field_expression/method-call path) All branches follow the same conservative pattern: lookupRawReturnType(callee) → extractElementTypeFromString → bind loop var * feat(type-resolution): Phase 7.4 — PHP \$this->property iterable via @var class property scan Adds Strategy C to PHP's extractForLoopBinding for the pattern: foreach (\$this->property as \$item) when Strategy A (resolveIterableElementType) and Strategy B (scopeEnv lookup) both fail to find the element type. Strategy C: when the iterable is a member_access_expression with object '$this', walk up the AST to the enclosing class_declaration, scan its declaration_list for a property_declaration whose variable_name matches the property, and extract the element type from: 1. PHPDoc @var annotation on a preceding comment sibling (/** @var User[] */) 2. PHP 7.4+ native type field (e.g. UserRepo \$repo — skips generic 'array') This eliminates the @param workaround that was previously required in the php-foreach-member-access fixture (which used @param User[] \$users on the method to populate the method's scopeEnv with a \$users binding). New helpers in php.ts: - PHPDOC_VAR_RE: regex for @var extraction - extractClassPropertyElementType: reads @var or native type from a property_declaration - findClassPropertyElementType: scans class body for a named property Tests added (type-env.test.ts): - PHP: resolves from @var User[] without @param workaround - PHP: conservative — no binding for unknown property - PHP: multi-class file — both classes resolve independently Fixture updated (php-foreach-member-access/App.php): - Removed the @param User[] \$users workaround from processMembers() - Test now validates the natural class-property-based resolution path * docs: mark Phase 7 complete in type-resolution-roadmap.md Records that 7A (call_expression iterables, 7 languages), 7B (PHP $this->property via @var scan), and 7C (ReturnTypeLookup + context object) are all shipped. Adds implementation notes and strikethroughs on resolved language-specific gaps. * fix(docs): update project references to feat-phase7-type-resolution in AGENTS.md and CLAUDE.md * feat(type-resolution): Phase 7.5 — PHP call_expression foreach + integration tests for 7 languages Add integration test coverage for Phase 7.3's call_expression iterable resolution across all 7 languages (Go, TypeScript, Python, Java, Kotlin, PHP, Rust). Each test creates a fixture with competing User/Repo classes that both define save(), then verifies for-loop iteration over a function call's return value resolves to the correct class. PHP was missing function_call_expression support in its for-loop extractor. Three changes fix this: - php.ts extractForLoopBinding: handle function_call_expression and member_call_expression iterables via returnTypeLookup - php.ts normalizePhpReturnType: preserve array notation (User[]) in SymbolTable so lookupRawReturnType returns useful container types - parse-worker.ts + parsing-processor.ts: upgrade uninformative AST return types (array, iterable) with PHPDoc @return annotations 35 new integration tests (5 per language), 2525 total tests passing. * fix(type-resolution): address PR abhigyanpatwari#341 review findings — PHP asymmetry + dormant infrastructure docs - Replace normalizePhpType with extractElementTypeFromString in PHP call-expression foreach paths, aligning with all 6 other language extractors and preventing incorrect binding of bare non-container types like User - Add NOTE comments clarifying pendingCallResults Tier 2b is infrastructure-ready but no extractor populates it yet - Expand Go channel-type comments explaining why non-channel assumption is safe * fix(type-resolution): address verification review — docs accuracy + PHP fallback guard - Roadmap lines 86/100: correct pendingCallResults from "active" to "dormant infrastructure (Phase 9)" - type-resolution-system.md line 363: update to reflect Phase 7.3 loop inference is delivered - type-resolution-system.md line 409: clarify for-loop call-expression resolution (done) vs general assignment propagation (pending) - php.ts:127: add declaration_list type guard on fallback to prevent silent wrong results

Summary
ReturnTypeLookupinterface andbuildReturnTypeLookupfactory, replacing the bare(node, env)for-loop extractor signature with aForLoopExtractorContextobject. AddspendingCallResultsinfrastructure (discriminated union type, processing loop) for futurevar x = f()return-type propagation. The loop is structurally complete but no extractor populates it yet —const users = getUsers()currently resolves via the existingscanConstructorBindingpath incall-processor.ts.for user in getUsers(),foreach (var u in GetUsers()), etc.) by unwrappinglookupRawReturnTypevia the sharedextractElementTypeFromStringhelper.$this->property, walks up the AST to the enclosingclass_declaration, scans thedeclaration_listfor a matchingproperty_declaration, and extracts the element type from the@varPHPDoc annotation or PHP 7.4+ native type. Removes the@paramworkaround that was previously required in the fixture.type-resolution-roadmap.mdupdated with implementation notes and resolved-gap strikethroughs for all 7A/7B/7C items.Test plan
npx vitest run test/unit/type-env.test.ts— 288 tests pass (includes new Phase 7.3/7.4 cases)npx vitest run test/unit/call-processor.test.ts— 78 tests passnpx vitest run test/integration/resolvers/go.test.ts— 70 tests passnpx vitest run test/integration/resolvers/php.test.ts— passeslbug-pool/local-backend*/ cursor skill install)🤖 Generated with Claude Code