44
55use PhpParser \Node \Expr ;
66use PhpParser \Node \Expr \PropertyFetch ;
7+ use PhpParser \Node \Identifier ;
8+ use PhpParser \Node \Scalar \String_ ;
79use PhpParser \Node \Stmt ;
810use PHPStan \Analyser \ExpressionContext ;
911use PHPStan \Analyser \ExpressionResult ;
1012use PHPStan \Analyser \ExpressionResultStorage ;
1113use PHPStan \Analyser \ExprHandler ;
14+ use PHPStan \Analyser \ExprHandler \Helper \NullsafeShortCircuitingHelper ;
1215use PHPStan \Analyser \InternalThrowPoint ;
1316use PHPStan \Analyser \MutatingScope ;
1417use PHPStan \Analyser \NodeScopeResolver ;
1518use PHPStan \DependencyInjection \AutowiredService ;
1619use PHPStan \Php \PhpVersion ;
20+ use PHPStan \Rules \Properties \PropertyReflectionFinder ;
21+ use PHPStan \Type \ErrorType ;
22+ use PHPStan \Type \MixedType ;
23+ use PHPStan \Type \Type ;
24+ use PHPStan \Type \TypeCombinator ;
25+ use function array_map ;
1726use function array_merge ;
27+ use function count ;
1828
1929/**
2030 * @implements ExprHandler<PropertyFetch>
@@ -25,6 +35,7 @@ final class PropertyFetchHandler implements ExprHandler
2535
2636 public function __construct (
2737 private PhpVersion $ phpVersion ,
38+ private PropertyReflectionFinder $ propertyReflectionFinder ,
2839 )
2940 {
3041 }
@@ -77,4 +88,66 @@ public function processExpr(NodeScopeResolver $nodeScopeResolver, Stmt $stmt, Ex
7788 );
7889 }
7990
91+ /**
92+ * @param PropertyFetch $expr
93+ */
94+ public function resolveType (MutatingScope $ scope , Expr $ expr ): Type
95+ {
96+ if ($ expr ->name instanceof Identifier) {
97+ if ($ scope ->nativeTypesPromoted ) {
98+ $ propertyReflection = $ this ->propertyReflectionFinder ->findPropertyReflectionFromNode ($ expr , $ scope );
99+ if ($ propertyReflection === null ) {
100+ return new ErrorType ();
101+ }
102+
103+ if (!$ propertyReflection ->hasNativeType ()) {
104+ return new MixedType ();
105+ }
106+
107+ $ nativeType = $ propertyReflection ->getNativeType ();
108+
109+ return NullsafeShortCircuitingHelper::getType ($ scope , $ expr ->var , $ nativeType );
110+ }
111+
112+ $ returnType = $ this ->propertyFetchType (
113+ $ scope ,
114+ $ scope ->getType ($ expr ->var ),
115+ $ expr ->name ->name ,
116+ $ expr ,
117+ );
118+ if ($ returnType === null ) {
119+ $ returnType = new ErrorType ();
120+ }
121+
122+ return NullsafeShortCircuitingHelper::getType ($ scope , $ expr ->var , $ returnType );
123+ }
124+
125+ $ nameType = $ scope ->getType ($ expr ->name );
126+ if (count ($ nameType ->getConstantStrings ()) > 0 ) {
127+ return TypeCombinator::union (
128+ ...array_map (static fn ($ constantString ) => $ constantString ->getValue () === '' ? new ErrorType () : $ scope
129+ ->filterByTruthyValue (new Expr \BinaryOp \Identical ($ expr ->name , new String_ ($ constantString ->getValue ())))
130+ ->getType (
131+ new PropertyFetch ($ expr ->var , new Identifier ($ constantString ->getValue ())),
132+ ), $ nameType ->getConstantStrings ()),
133+ );
134+ }
135+
136+ return new MixedType ();
137+ }
138+
139+ private function propertyFetchType (MutatingScope $ scope , Type $ fetchedOnType , string $ propertyName , PropertyFetch $ propertyFetch ): ?Type
140+ {
141+ $ propertyReflection = $ scope ->getInstancePropertyReflection ($ fetchedOnType , $ propertyName );
142+ if ($ propertyReflection === null ) {
143+ return null ;
144+ }
145+
146+ if ($ scope ->isInExpressionAssign ($ propertyFetch )) {
147+ return $ propertyReflection ->getWritableType ();
148+ }
149+
150+ return $ propertyReflection ->getReadableType ();
151+ }
152+
80153}
0 commit comments