Repository navigation
Conversation
gnutix
force-pushed
the
static-property-string-unions
branch
2 times, most recently
from
July 5, 2026 07:49
ed541a4 to
d23e01f
Compare
gnutix
force-pushed
the
static-property-string-unions
branch
3 times, most recently
from
July 5, 2026 11:40
fbdd0e9 to
dfcb186
Compare
gnutix
force-pushed
the
static-property-string-unions
branch
from
October 12, 2026 16:32
dfcb186 to
1514045
Compare
gnutix
force-pushed
the
static-property-string-unions
branch
from
October 12, 2026 18:33
1514045 to
915568f
Compare
…class-string unions Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
gnutix
force-pushed
the
static-property-string-unions
branch
from
October 12, 2026 20:27
915568f to
58bba30
Compare
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.
Follow-up to phpstan-src#5976 ("Check static method calls through constant class-name strings and class-string unions"), same root-cause family.
AccessStaticPropertiesCheck(the$classString::$proppath) contained a legacy string bail-out that predates theType::isClassString()/Type::getClassStringObjectType()API: as soon as the class expression resolved to a string type it returned silently (if ($classType->isString()->yes()) { return []; }). As a result, static property access through a variable holding a constant class-name string, aclass-string<C>, or a union thereof reported nothing — even when the property did not exist.This is actually worse than the methods case that #5976 fixed: the methods code at least handled a single
GenericClassStringType, whereas here even a singleclass-string<C>was skipped entirely. SosingleGenericClassString()in the new test is a brand-new report.Fix
Mirrors #5976, adapted to properties. Before calling
RuleLevelHelper::findTypeToCheck(), the check routes through the object type the class expression describes (new TypeExpr($exprType->getClassStringObjectType())) whenever that names at least one class that is not a trait. There is no separateisClassString()->yes()guard: for any type that is not entirely a class-string (objects, unions with non-class-string members, plainstring),getClassStringObjectType()yields no class name, so the conversion is skipped by the same no-class-name check that keeps plainclass-stringand traits on the old path (a guard would be an equivalent mutant for Infection). This makes constant-string / class-string unions behave identically to object-typed expressions with respect to rule levels: union-member filtering whencheckUnionTypesis off, whole-union check when it is on, plus benevolent-union and unknown-class handling for free (StringType::canAccessProperties()isNo, the same bypass mechanism ascanCallMethods()in the template).hasStaticProperty()guard is needed (unlike Check static method calls through constant class-name strings and class-string unions #5976'shasMethod()guard):property_exists()with a string first argument narrows theproperty_exists(...)call itself, and the rule rebuilds that call from the original class expression, soproperty_exists()-guarded access keeps working (covered bypropertyExistsNarrowing()).class-string,class-string<object>,class-string<T of object>) are not rerouted and stay silent, as before.RuleLevelHelperwould report the trait as an unknown class even when the property exists, so$trait = T::class; $trait::$propstays silent as before.__getequivalent for statics), so unlike Check static method calls through constant class-name strings and class-string unions #5976 there are no magic-member cases and noreportMagicMethodsgating.The access now goes through the whole existing pipeline, for reads and writes alike: undefined property (including in
isset(),??,empty(),++and??=), static access to an instance property, private/protected visibility, andprivate(set)/protected(set)on writes.Tests
static-property-string-unions.php(read rule) mirrors the template's matrix: single constant string; union of constant strings with one member missing the property; all-valid union;get_class()union;class-string<Foo>|class-string<Baz>union; singleclass-string<Baz>(the new report); plainclass-string(silent); static access to an instance property through a constant string;property_exists()narrowing (silent); trait name (silent);isset()/??/empty()on a missing property; private/protected read. Level semantics are pinned with the same flag-combination approach as #5976 — unions are reported only whencheckUnionTypesis on, single class-strings always.static-property-string-unions-assign.php(assign rule) covers writes,++and??=on a missing property, a private write, a union write, a valid write and a write through a trait name (silent);static-property-string-unions-asymmetric.php(PHP 8.5) coversprivate(set)/protected(set)writes through a constant string.Ecosystem impact
No integration project gains a new error from this PR.
Alternatives
If you prefer, either part can go the other way, in line with whatever is decided on #5976:
checkUnionTypesinstead of routing through the object type; with the current choice, class-string unions follow the object-union rules (reported at level 7+ only).T::$propreports for a class-string naming a trait (e.g.staticProperty.notFoundfor a missing property) instead of staying silent.🤖 Generated with Claude Code, model Fable 5.