5
5
use PhpParser \Node ;
6
6
use PHPStan \Analyser \Scope ;
7
7
use PHPStan \Node \ClassPropertyNode ;
8
+ use PHPStan \Php \PhpVersion ;
8
9
use PHPStan \Reflection \ClassReflection ;
9
10
use PHPStan \Reflection \Php \PhpPropertyReflection ;
10
11
use PHPStan \Rules \Rule ;
@@ -21,6 +22,7 @@ final class OverridingPropertyRule implements Rule
21
22
{
22
23
23
24
public function __construct (
25
+ private PhpVersion $ phpVersion ,
24
26
private bool $ checkPhpDocMethodSignatures ,
25
27
private bool $ reportMaybes ,
26
28
)
@@ -129,15 +131,45 @@ public function processNode(Node $node, Scope $scope): array
129
131
))->identifier ('property.missingNativeType ' )->nonIgnorable ()->build ();
130
132
} else {
131
133
if (!$ prototype ->getNativeType ()->equals ($ nativeType )) {
132
- $ typeErrors [] = RuleErrorBuilder::message (sprintf (
133
- 'Type %s of property %s::$%s is not the same as type %s of overridden property %s::$%s. ' ,
134
- $ nativeType ->describe (VerbosityLevel::typeOnly ()),
135
- $ classReflection ->getDisplayName (),
136
- $ node ->getName (),
137
- $ prototype ->getNativeType ()->describe (VerbosityLevel::typeOnly ()),
138
- $ prototype ->getDeclaringClass ()->getDisplayName (),
139
- $ node ->getName (),
140
- ))->identifier ('property.nativeType ' )->nonIgnorable ()->build ();
134
+ if (
135
+ $ this ->phpVersion ->supportsPropertyHooks ()
136
+ && ($ prototype ->isVirtual ()->yes () || $ prototype ->isAbstract ()->yes ())
137
+ && (!$ prototype ->isReadable () || !$ prototype ->isWritable ())
138
+ ) {
139
+ if (!$ prototype ->isReadable ()) {
140
+ if (!$ nativeType ->isSuperTypeOf ($ prototype ->getNativeType ())->yes ()) {
141
+ $ typeErrors [] = RuleErrorBuilder::message (sprintf (
142
+ 'Type %s of property %s::$%s is not contravariant with type %s of overridden property %s::$%s. ' ,
143
+ $ nativeType ->describe (VerbosityLevel::typeOnly ()),
144
+ $ classReflection ->getDisplayName (),
145
+ $ node ->getName (),
146
+ $ prototype ->getNativeType ()->describe (VerbosityLevel::typeOnly ()),
147
+ $ prototype ->getDeclaringClass ()->getDisplayName (),
148
+ $ node ->getName (),
149
+ ))->identifier ('property.nativeType ' )->nonIgnorable ()->build ();
150
+ }
151
+ } elseif (!$ prototype ->getNativeType ()->isSuperTypeOf ($ nativeType )->yes ()) {
152
+ $ typeErrors [] = RuleErrorBuilder::message (sprintf (
153
+ 'Type %s of property %s::$%s is not covariant with type %s of overridden property %s::$%s. ' ,
154
+ $ nativeType ->describe (VerbosityLevel::typeOnly ()),
155
+ $ classReflection ->getDisplayName (),
156
+ $ node ->getName (),
157
+ $ prototype ->getNativeType ()->describe (VerbosityLevel::typeOnly ()),
158
+ $ prototype ->getDeclaringClass ()->getDisplayName (),
159
+ $ node ->getName (),
160
+ ))->identifier ('property.nativeType ' )->nonIgnorable ()->build ();
161
+ }
162
+ } else {
163
+ $ typeErrors [] = RuleErrorBuilder::message (sprintf (
164
+ 'Type %s of property %s::$%s is not the same as type %s of overridden property %s::$%s. ' ,
165
+ $ nativeType ->describe (VerbosityLevel::typeOnly ()),
166
+ $ classReflection ->getDisplayName (),
167
+ $ node ->getName (),
168
+ $ prototype ->getNativeType ()->describe (VerbosityLevel::typeOnly ()),
169
+ $ prototype ->getDeclaringClass ()->getDisplayName (),
170
+ $ node ->getName (),
171
+ ))->identifier ('property.nativeType ' )->nonIgnorable ()->build ();
172
+ }
141
173
}
142
174
}
143
175
} elseif ($ nativeType !== null ) {
@@ -167,6 +199,45 @@ public function processNode(Node $node, Scope $scope): array
167
199
}
168
200
169
201
$ verbosity = VerbosityLevel::getRecommendedLevelByType ($ prototype ->getReadableType (), $ propertyReflection ->getReadableType ());
202
+
203
+ if (
204
+ $ this ->phpVersion ->supportsPropertyHooks ()
205
+ && ($ prototype ->isVirtual ()->yes () || $ prototype ->isAbstract ()->yes ())
206
+ && (!$ prototype ->isReadable () || !$ prototype ->isWritable ())
207
+ ) {
208
+ if (!$ prototype ->isReadable ()) {
209
+ if (!$ propertyReflection ->getReadableType ()->isSuperTypeOf ($ prototype ->getReadableType ())->yes ()) {
210
+ $ errors [] = RuleErrorBuilder::message (sprintf (
211
+ 'PHPDoc type %s of property %s::$%s is not contravariant with PHPDoc type %s of overridden property %s::$%s. ' ,
212
+ $ propertyReflection ->getReadableType ()->describe ($ verbosity ),
213
+ $ classReflection ->getDisplayName (),
214
+ $ node ->getName (),
215
+ $ prototype ->getReadableType ()->describe ($ verbosity ),
216
+ $ prototype ->getDeclaringClass ()->getDisplayName (),
217
+ $ node ->getName (),
218
+ ))->identifier ('property.phpDocType ' )->tip (sprintf (
219
+ "You can fix 3rd party PHPDoc types with stub files: \n %s " ,
220
+ '<fg=cyan>https://phpstan.org/user-guide/stub-files</> ' ,
221
+ ))->build ();
222
+ }
223
+ } elseif (!$ prototype ->getReadableType ()->isSuperTypeOf ($ propertyReflection ->getReadableType ())->yes ()) {
224
+ $ errors [] = RuleErrorBuilder::message (sprintf (
225
+ 'PHPDoc type %s of property %s::$%s is not covariant with PHPDoc type %s of overridden property %s::$%s. ' ,
226
+ $ propertyReflection ->getReadableType ()->describe ($ verbosity ),
227
+ $ classReflection ->getDisplayName (),
228
+ $ node ->getName (),
229
+ $ prototype ->getReadableType ()->describe ($ verbosity ),
230
+ $ prototype ->getDeclaringClass ()->getDisplayName (),
231
+ $ node ->getName (),
232
+ ))->identifier ('property.phpDocType ' )->tip (sprintf (
233
+ "You can fix 3rd party PHPDoc types with stub files: \n %s " ,
234
+ '<fg=cyan>https://phpstan.org/user-guide/stub-files</> ' ,
235
+ ))->build ();
236
+ }
237
+
238
+ return $ errors ;
239
+ }
240
+
170
241
$ isSuperType = $ prototype ->getReadableType ()->isSuperTypeOf ($ propertyReflection ->getReadableType ());
171
242
$ canBeTurnedOffError = RuleErrorBuilder::message (sprintf (
172
243
'PHPDoc type %s of property %s::$%s is not the same as PHPDoc type %s of overridden property %s::$%s. ' ,
0 commit comments