1313use PHPStan \Reflection \MissingMethodFromReflectionException ;
1414use PHPStan \TrinaryLogic ;
1515use PHPStan \Type \MixedType ;
16+ use PHPStan \Type \NeverType ;
1617use PHPStan \Type \Type ;
1718use PHPStan \Type \TypehintHelper ;
1819use function sprintf ;
@@ -25,7 +26,9 @@ final class PhpPropertyReflection implements ExtendedPropertyReflection
2526
2627 private ?Type $ finalNativeType = null ;
2728
28- private ?Type $ type = null ;
29+ private ?Type $ readableType = null ;
30+
31+ private ?Type $ writableType = null ;
2932
3033 /**
3134 * @param list<AttributeReflection> $attributes
@@ -34,7 +37,8 @@ public function __construct(
3437 private ClassReflection $ declaringClass ,
3538 private ?ClassReflection $ declaringTrait ,
3639 private ReflectionUnionType |ReflectionNamedType |ReflectionIntersectionType |null $ nativeType ,
37- private ?Type $ phpDocType ,
40+ private ?Type $ readablePhpDocType ,
41+ private ?Type $ writablePhpDocType ,
3842 private ReflectionProperty $ reflection ,
3943 private ?ExtendedMethodReflection $ getHook ,
4044 private ?ExtendedMethodReflection $ setHook ,
@@ -105,9 +109,9 @@ public function isReadOnlyByPhpDoc(): bool
105109
106110 public function getReadableType (): Type
107111 {
108- return $ this ->type ??= TypehintHelper::decideTypeFromReflection (
112+ return $ this ->readableType ??= TypehintHelper::decideTypeFromReflection (
109113 $ this ->nativeType ,
110- $ this ->phpDocType ,
114+ $ this ->readablePhpDocType ,
111115 $ this ->declaringClass ,
112116 );
113117 }
@@ -122,13 +126,36 @@ public function getWritableType(): Type
122126 }
123127 }
124128
125- return $ this ->getReadableType ();
129+ if ($ this ->writableType !== null ) {
130+ return $ this ->writableType ;
131+ }
132+
133+ if ($ this ->writablePhpDocType === null || $ this ->writablePhpDocType instanceof NeverType) {
134+ return $ this ->writableType = TypehintHelper::decideTypeFromReflection (
135+ $ this ->nativeType ,
136+ $ this ->readablePhpDocType ,
137+ $ this ->declaringClass ,
138+ );
139+ }
140+
141+ if (
142+ $ this ->readablePhpDocType !== null
143+ && !$ this ->readablePhpDocType ->equals ($ this ->writablePhpDocType )
144+ ) {
145+ return $ this ->writableType = $ this ->writablePhpDocType ;
146+ }
147+
148+ return $ this ->writableType = TypehintHelper::decideTypeFromReflection (
149+ $ this ->nativeType ,
150+ $ this ->writablePhpDocType ,
151+ $ this ->declaringClass ,
152+ );
126153 }
127154
128155 public function canChangeTypeAfterAssignment (): bool
129156 {
130157 if ($ this ->isStatic ()) {
131- return true ;
158+ return $ this -> getReadableType ()-> equals ( $ this -> getWritableType ()) ;
132159 }
133160
134161 if ($ this ->isVirtual ()->yes ()) {
@@ -143,7 +170,7 @@ public function canChangeTypeAfterAssignment(): bool
143170 return false ;
144171 }
145172
146- return true ;
173+ return $ this -> getReadableType ()-> equals ( $ this -> getWritableType ()) ;
147174 }
148175
149176 public function isPromoted (): bool
@@ -153,13 +180,13 @@ public function isPromoted(): bool
153180
154181 public function hasPhpDocType (): bool
155182 {
156- return $ this ->phpDocType !== null ;
183+ return $ this ->readablePhpDocType !== null ;
157184 }
158185
159186 public function getPhpDocType (): Type
160187 {
161- if ($ this ->phpDocType !== null ) {
162- return $ this ->phpDocType ;
188+ if ($ this ->readablePhpDocType !== null ) {
189+ return $ this ->readablePhpDocType ;
163190 }
164191
165192 return new MixedType ();
0 commit comments