-
Notifications
You must be signed in to change notification settings - Fork 574
Report array&callable pass as wrong param
#5573
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d4dcc5c
7507cc4
20dccde
8bbf9bb
ca76429
3afa0ac
3b83616
9759075
507d88a
14d9af0
631138c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| <?php | ||
|
|
||
| namespace Bug14549Bis; | ||
|
|
||
| class Foo | ||
| { | ||
|
|
||
| /** @param array<int> $param */ | ||
| public function callArrayInt(array $param): void | ||
| { | ||
| } | ||
|
|
||
| /** @param array{string, string} $param */ | ||
| public function callConstantArrayStringString(array $param): void | ||
| { | ||
| } | ||
|
|
||
| /** @param array{object|string, string} $param */ | ||
| public function callConstantArrayObjectOrStringString(array $param): void | ||
| { | ||
| } | ||
|
|
||
| /** @param array{object|string, string, string} $param */ | ||
| public function callConstantArrayObjectOrStringStringString(array $param): void | ||
| { | ||
| } | ||
|
|
||
| /** | ||
| * @param callable-array $task | ||
| */ | ||
| public function doCallWithCallableArray(array $task): void | ||
| { | ||
| $this->callArrayInt($task); | ||
| $this->callConstantArrayStringString($task); | ||
| $this->callConstantArrayObjectOrStringString($task); | ||
| $this->callConstantArrayObjectOrStringStringString($task); | ||
| } | ||
|
|
||
| /** | ||
| * @param callable&array $task | ||
| */ | ||
| public function doCallWithCallableAndArray(array $task): void | ||
| { | ||
| $this->callArrayInt($task); | ||
| $this->callConstantArrayStringString($task); | ||
| $this->callConstantArrayObjectOrStringString($task); | ||
| $this->callConstantArrayObjectOrStringStringString($task); | ||
| } | ||
|
|
||
| /** @param array<string> $param */ | ||
| public function callArrayString(array $param): void | ||
| { | ||
| } | ||
|
|
||
| public function doCallWithHasOffsetValue(array $arr): void | ||
| { | ||
| if (isset($arr[1]) && $arr[1] === 1) { | ||
| $this->callArrayString($arr); | ||
| $this->callArrayInt($arr); | ||
| } | ||
| } | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -109,6 +109,132 @@ public function testAccepts(Type $type, Type $otherType, TrinaryLogic $expectedR | |
| ); | ||
| } | ||
|
|
||
| /** | ||
| * @return Iterator<int, array{Type, Type, TrinaryLogic}> | ||
| */ | ||
| public static function dataIsAcceptedBy(): Iterator | ||
| { | ||
| // array&callable isAcceptedBy array - success | ||
| yield [ | ||
| new IntersectionType([new ArrayType(new MixedType(), new MixedType()), new CallableType()]), | ||
| new ArrayType(new MixedType(), new MixedType()), | ||
| TrinaryLogic::createYes(), | ||
| ]; | ||
|
|
||
| // array&callable isAcceptedBy array<int> - failure | ||
| yield [ | ||
| new IntersectionType([new ArrayType(new MixedType(), new MixedType()), new CallableType()]), | ||
| new ArrayType(new MixedType(), new IntegerType()), | ||
| TrinaryLogic::createNo(), | ||
| ]; | ||
|
|
||
| // array&callable isAcceptedBy constantArray{stdClass, string} - maybe | ||
| yield [ | ||
| new IntersectionType([new ArrayType(new MixedType(), new MixedType()), new CallableType()]), | ||
| new ConstantArrayType( | ||
| [new ConstantIntegerType(0), new ConstantIntegerType(1)], | ||
| [new UnionType([new ObjectType('stdClass'), new StringType()]), new StringType()], | ||
| ), | ||
| TrinaryLogic::createMaybe(), | ||
| ]; | ||
|
|
||
| // array&callable isAcceptedBy constantArray{string, string} - maybe | ||
| yield [ | ||
| new IntersectionType([new ArrayType(new MixedType(), new MixedType()), new CallableType()]), | ||
| new ConstantArrayType( | ||
| [new ConstantIntegerType(0), new ConstantIntegerType(1)], | ||
| [new StringType(), new StringType()], | ||
| ), | ||
| TrinaryLogic::createMaybe(), | ||
| ]; | ||
|
|
||
| // array&hasOffsetValue isAcceptedBy array - success | ||
| yield [ | ||
| new IntersectionType([ | ||
| new ArrayType(new MixedType(), new MixedType()), | ||
| new NonEmptyArrayType(), | ||
| new HasOffsetValueType(new ConstantIntegerType(3), new IntegerType()), | ||
| ]), | ||
| new ArrayType(new MixedType(), new MixedType()), | ||
| TrinaryLogic::createYes(), | ||
| ]; | ||
|
Comment on lines
+151
to
+160
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we could have another test like "array&hasOffsetValue" is accepted by a union of e.g. "array|array"
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added the test case staabm requested: |
||
|
|
||
| // array&hasOffsetValue isAcceptedBy array - failure | ||
| yield [ | ||
| new IntersectionType([ | ||
| new ArrayType(new MixedType(), new MixedType()), | ||
| new NonEmptyArrayType(), | ||
| new HasOffsetValueType(new ConstantIntegerType(3), new IntegerType()), | ||
| ]), | ||
| new ArrayType(new MixedType(), new StringType()), | ||
| TrinaryLogic::createNo(), | ||
| ]; | ||
|
|
||
| // array&hasOffsetValue isAcceptedBy array<int> - success (matching value type) | ||
| yield [ | ||
| new IntersectionType([ | ||
| new ArrayType(new MixedType(), new MixedType()), | ||
| new NonEmptyArrayType(), | ||
| new HasOffsetValueType(new ConstantIntegerType(3), new IntegerType()), | ||
| ]), | ||
| new ArrayType(new MixedType(), new IntegerType()), | ||
| TrinaryLogic::createYes(), | ||
| ]; | ||
|
|
||
| // array&hasOffsetValue isAcceptedBy constantArray{int, int} - success | ||
| yield [ | ||
| new IntersectionType([ | ||
| new ArrayType(new MixedType(), new MixedType()), | ||
| new NonEmptyArrayType(), | ||
| new HasOffsetValueType(new ConstantIntegerType(0), new IntegerType()), | ||
| ]), | ||
| new ConstantArrayType( | ||
| [new ConstantIntegerType(0), new ConstantIntegerType(1)], | ||
| [new IntegerType(), new IntegerType()], | ||
| ), | ||
| TrinaryLogic::createMaybe(), | ||
| ]; | ||
|
|
||
| // array&hasOffsetValue isAcceptedBy constantArray{string, string} - failure | ||
| yield [ | ||
| new IntersectionType([ | ||
| new ArrayType(new MixedType(), new MixedType()), | ||
| new NonEmptyArrayType(), | ||
| new HasOffsetValueType(new ConstantIntegerType(0), new IntegerType()), | ||
| ]), | ||
| new ConstantArrayType( | ||
| [new ConstantIntegerType(0), new ConstantIntegerType(1)], | ||
| [new StringType(), new StringType()], | ||
| ), | ||
| TrinaryLogic::createNo(), | ||
| ]; | ||
|
|
||
| // array&hasOffsetValue(3, int) isAcceptedBy array<int>|array<string> - yes (array<int> accepts it) | ||
| yield [ | ||
| new IntersectionType([ | ||
| new ArrayType(new MixedType(), new MixedType()), | ||
| new NonEmptyArrayType(), | ||
| new HasOffsetValueType(new ConstantIntegerType(3), new IntegerType()), | ||
| ]), | ||
| new UnionType([ | ||
| new ArrayType(new MixedType(), new IntegerType()), | ||
| new ArrayType(new MixedType(), new StringType()), | ||
| ]), | ||
| TrinaryLogic::createYes(), | ||
| ]; | ||
| } | ||
|
|
||
| #[DataProvider('dataIsAcceptedBy')] | ||
| public function testIsAcceptedBy(Type $type, Type $acceptingType, TrinaryLogic $expectedResult): void | ||
| { | ||
| $actualResult = $acceptingType->accepts($type, true)->result; | ||
| $this->assertSame( | ||
| $expectedResult->describe(), | ||
| $actualResult->describe(), | ||
| sprintf('%s -> isAcceptedBy(%s)', $type->describe(VerbosityLevel::precise()), $acceptingType->describe(VerbosityLevel::precise())), | ||
| ); | ||
| } | ||
|
|
||
| public static function dataIsCallable(): array | ||
| { | ||
| return [ | ||
|
|
@@ -362,6 +488,53 @@ public static function dataIsSubTypeOf(): Iterator | |
| ]), | ||
| TrinaryLogic::createYes(), | ||
| ]; | ||
|
|
||
| // array&callable isSubTypeOf array - success | ||
| yield [ | ||
| new IntersectionType([new ArrayType(new MixedType(), new MixedType()), new CallableType()]), | ||
| new ArrayType(new MixedType(), new MixedType()), | ||
| TrinaryLogic::createYes(), | ||
| ]; | ||
|
|
||
| // array&callable isSubTypeOf array<int> - failure | ||
| yield [ | ||
| new IntersectionType([new ArrayType(new MixedType(), new MixedType()), new CallableType()]), | ||
| new ArrayType(new MixedType(), new IntegerType()), | ||
| TrinaryLogic::createNo(), | ||
| ]; | ||
|
|
||
| // array&hasOffsetValue isSubTypeOf array - success | ||
| yield [ | ||
| new IntersectionType([ | ||
| new ArrayType(new MixedType(), new MixedType()), | ||
| new NonEmptyArrayType(), | ||
| new HasOffsetValueType(new ConstantIntegerType(3), new IntegerType()), | ||
| ]), | ||
| new ArrayType(new MixedType(), new MixedType()), | ||
| TrinaryLogic::createYes(), | ||
| ]; | ||
|
|
||
| // array&hasOffsetValue isSubTypeOf array<int> - maybe | ||
| yield [ | ||
| new IntersectionType([ | ||
| new ArrayType(new MixedType(), new MixedType()), | ||
| new NonEmptyArrayType(), | ||
| new HasOffsetValueType(new ConstantIntegerType(3), new IntegerType()), | ||
| ]), | ||
| new ArrayType(new MixedType(), new IntegerType()), | ||
| TrinaryLogic::createMaybe(), | ||
| ]; | ||
|
|
||
| // array&hasOffsetValue isSubTypeOf array<string> - failure | ||
| yield [ | ||
| new IntersectionType([ | ||
| new ArrayType(new MixedType(), new MixedType()), | ||
| new NonEmptyArrayType(), | ||
| new HasOffsetValueType(new ConstantIntegerType(3), new IntegerType()), | ||
| ]), | ||
| new ArrayType(new MixedType(), new StringType()), | ||
| TrinaryLogic::createNo(), | ||
| ]; | ||
| } | ||
|
|
||
| #[DataProvider('dataIsSubTypeOf')] | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a quick comment to explain which situations is this about
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a 4-line comment above the
isSuperTypeOfguard inIntersectionType::isAcceptedBy()explaining that it catches false positives fromlazyMaxMinshort-circuiting whenMixedType::isAcceptedBy()returns Yes for intersection types likearray<mixed>&callableorarray<mixed>&hasOffsetValueagainst specific array types. All 12,013 tests pass andmake phpstanreports no errors.