Conversation
Move arrayElementType deletion from step-48 to step-49 (its caller is only replaced in step-49; step-48 cannot delete a method whose one caller still lives). Switch step-48's docblock parser from an in-house string parser to phpstan/phpdoc-parser and record the Tighten it adds. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Docblocks are parsed text; the Parser tier is where parsing lives. The Resolution layer already depends on Parser, so both call sites (ExpressionResolver, ResolvedSymbolPresenter) keep working through the existing layer edge. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Already present transitively (via phpstan). DocblockTypeAnnotator will parse docblock types through it; promote to a direct dep so the version constraint is visible in composer.json. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Turn a resolved phpstan/phpdoc-parser TypeNode into a Domain\Type. Every identifier the annotator hands us is already either a PrimitiveType name or a fully qualified class name, so no name resolution runs here. Unsupported shapes (callables, conditionals, array shapes) yield null so callers keep the native type unchanged. Confine PHPStan\PhpDocParser\* to TypeFactory. The annotator will be added to the allowlist when it exists. Expose PrimitiveType::$name for the merge routine that follows. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
One reconciliation point so no downstream code has to know a type's origin. Native declarations win on the outer shape; a docblock only fills in the value type when the native says `array` or `iterable`, or names the same class without generics of its own. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
One visitor reads @var, @return, @PARAM (plus psalm/phpstan spellings) of every ClassMethod, Property, ClassConst, and Function_. It parses the text through phpstan/phpdoc-parser, walks the resulting TypeNode tree, and rewrites every class-name IdentifierTypeNode into its fully qualified form through the same PhpParser\NameContext the sibling NameResolver populates. The resolved TypeNodes land as the resolvedDocblockTypes attribute keyed by tag (`return`, `var`, `param:<name>`), which the FunctionInfo and ClassInfo factories will read next. Adds a phpstan disallowedNamespaces rule confining PHPStan\PhpDocParser\* to the annotator and TypeFactory, and updates the ledger: the name-resolution row gains DocblockTypeAnnotator; docblock-types swaps TypeFactory for DocblockTypeAnnotator among its holders. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Attach the annotator alongside NameResolver, sharing its NameContext. Every parsed tree now carries resolvedDocblockTypes on every ClassMethod, Property, ClassConst, and Function_ that has a docblock. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Read the resolvedDocblockTypes attribute set by DocblockTypeAnnotator and reconcile each part through TypeFactory::merge. A new TypeFactory::fromDocblockAttribute helper keeps the attribute's shape (a map from tag key to phpdoc-parser TypeNode) hidden inside the Domain layer, so callers reason in Type only. ParameterInfo::fromNode accepts an optional pre-computed docblock Type so a caller building parameter list at the function-info site drives the merge without touching phpdoc-parser directly. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Every AST path — method, plain property, promoted constructor property, class constant — now reads its declaring node's resolvedDocblockTypes attribute and reconciles the native declaration with the docblock through TypeFactory::merge. Downstream MethodInfo, PropertyInfo, and ConstantInfo objects hold one Type with no awareness of origin. Recapture the class-like-lookup golden: PSR ServerRequestInterface's untyped-in-native parameters (`$default`, `$value`, `$data`, and the `getParsedBody` return) now surface the mixed/null|array|object types their docblocks declare. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Prove the Done clause end-to-end: parse UserRepository through the production TreeAnnotator (NameResolver + DocblockTypeAnnotator), pass the node to DefaultClassInfoFactory, and assert the resulting MethodInfo::returnType->valueType() is a ClassName of the fully qualified Fixtures\Domain\User. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #516 +/- ##
============================================
- Coverage 99.45% 99.23% -0.22%
- Complexity 1897 1966 +69
============================================
Files 134 135 +1
Lines 4954 5121 +167
============================================
+ Hits 4927 5082 +155
- Misses 27 39 +12 ☔ View full report in Codecov by Harness. |
Firehed
marked this pull request as ready for review
September 9, 2026 19:38
Firehed
marked this pull request as draft
September 9, 2026 19:40
The visitor is one class check plus one identifier check; every other TypeNode is descended into by the library traverser. A new TypeNode subclass in phpdoc-parser gains identifier resolution automatically.
Readers pass a typed php-parser Node and TypeFactory owns the mixed-shape attribute boundary. No caller carries a mixed value through its signature.
Discovers every concrete PHPStan\PhpDocParser\Ast\Type\* implementing TypeNode and asserts each is listed as SUPPORTED or UNSUPPORTED. A new subclass in a library release fails the test so a human decides which bucket it belongs to, rather than silently becoming null in TypeFactory::fromDocblockType.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Build-manifest step-48. Docblock types now flow through the same place code
names are resolved. Every consumer of
MethodInfo,PropertyInfo,ConstantInfo,FunctionInfo, orParameterInforeads oneTypethatalready reconciles the native declaration with any docblock refinement — no
consumer knows or cares where each part came from.
The docblock grammar is not parsed in-house.
phpstan/phpdoc-parserispromoted to a direct dependency,
Parser\DocblockTypeAnnotatorrunsalongside
NameResolverand stores a resolvedTypeNodeper tag on thedeclaring node, and
TypeFactory::fromDocblockType+TypeFactory::mergeturn that into the merged
Domain\Type.Done clauses
docblock typesrow listsDocblockTypeAnnotatorandResolvedSymbolPresenteras holders and keepsExpressionResolverpending step-49 (
tests/Architecture/OneRoutePerFactTest.php).php-parser name resolutionrow gainsDocblockTypeAnnotatoras a holder (same test).@return list<User>method'sreturnType->valueType()is the fullyqualified
User(
DefaultClassInfoFactoryTest::testMethodReturnListOfUserResolvesValueTypeToFullyQualifiedUser).TypeConstructionRulestill passes (composer test).FunctionSurfaceParityTestgolden unchanged (composer test).Also amends step-48 and step-49 rows so
arrayElementType's deletion landswith its sole caller in step-49 (small correction to the manifest wording;
first commit on the branch).
The
ClassLikeLookupParityTestgolden gains merged types on PSRServerRequestInterfacemethods where the native declaration was untyped —recaptured in the class-factory commit; small diff, visible improvement.