diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 47101ea9fe..2e84ff6f07 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -33,7 +33,7 @@ parameters: - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' identifier: phpstanApi.instanceofType - count: 3 + count: 2 path: src/Analyser/MutatingScope.php - @@ -48,12 +48,6 @@ parameters: count: 2 path: src/Analyser/NodeScopeResolver.php - - - message: '#^Doing instanceof PHPStan\\Type\\Constant\\ConstantStringType is error\-prone and deprecated\. Use Type\:\:getConstantStrings\(\) instead\.$#' - identifier: phpstanApi.instanceofType - count: 1 - path: src/Analyser/NodeScopeResolver.php - - message: '#^Parameter \#2 \$node of method PHPStan\\BetterReflection\\SourceLocator\\Ast\\Strategy\\NodeToReflection\:\:__invoke\(\) expects PhpParser\\Node\\Expr\\ArrowFunction\|PhpParser\\Node\\Expr\\Closure\|PhpParser\\Node\\Expr\\FuncCall\|PhpParser\\Node\\Stmt\\Class_\|PhpParser\\Node\\Stmt\\Const_\|PhpParser\\Node\\Stmt\\Enum_\|PhpParser\\Node\\Stmt\\Function_\|PhpParser\\Node\\Stmt\\Interface_\|PhpParser\\Node\\Stmt\\Trait_, PhpParser\\Node\\Stmt\\ClassLike given\.$#' identifier: argument.type diff --git a/src/Analyser/MutatingScope.php b/src/Analyser/MutatingScope.php index 395626e1ce..eb7661682e 100644 --- a/src/Analyser/MutatingScope.php +++ b/src/Analyser/MutatingScope.php @@ -4229,16 +4229,29 @@ public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType, $scope = $this; if ($expr instanceof Expr\ArrayDimFetch && $expr->dim !== null) { - $dimType = $scope->getType($expr->dim)->toArrayKey(); - if ($dimType instanceof ConstantIntegerType || $dimType instanceof ConstantStringType) { + $dimTypes = $scope->getType($expr->dim)->getConstantArrayKeys(); + + if ($dimTypes !== []) { $exprVarType = $scope->getType($expr->var); if (!$exprVarType instanceof MixedType && !$exprVarType->isArray()->no()) { + $hasOffsets = []; + $containsInteger = false; + foreach ($dimTypes as $dimType) { + $hasOffsets[] = new HasOffsetValueType($dimType, $type); + + if (!($dimType instanceof ConstantIntegerType)) { + continue; + } + + $containsInteger = true; + } + $types = [ new ArrayType(new MixedType(), new MixedType()), new ObjectType(ArrayAccess::class), new NullType(), ]; - if ($dimType instanceof ConstantIntegerType) { + if ($containsInteger) { $types[] = new StringType(); } @@ -4246,12 +4259,13 @@ public function specifyExpressionType(Expr $expr, Type $type, Type $nativeType, $expr->var, TypeCombinator::intersect( TypeCombinator::intersect($exprVarType, TypeCombinator::union(...$types)), - new HasOffsetValueType($dimType, $type), + ...$hasOffsets, ), $scope->getNativeType($expr->var), $certainty, ); } + } } diff --git a/src/Analyser/NodeScopeResolver.php b/src/Analyser/NodeScopeResolver.php index 8ff656bb57..4aa732374d 100644 --- a/src/Analyser/NodeScopeResolver.php +++ b/src/Analyser/NodeScopeResolver.php @@ -171,7 +171,6 @@ use PHPStan\Type\Constant\ConstantArrayTypeBuilder; use PHPStan\Type\Constant\ConstantBooleanType; use PHPStan\Type\Constant\ConstantIntegerType; -use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ErrorType; use PHPStan\Type\FileTypeMapper; use PHPStan\Type\GeneralizePrecision; @@ -6053,15 +6052,19 @@ private function produceArrayDimFetchAssignValueToWrite(array $dimFetchStack, ar && $scope->hasExpressionType($arrayDimFetch)->yes() ) { $hasOffsetType = null; - if ($offsetType instanceof ConstantStringType || $offsetType instanceof ConstantIntegerType) { - $hasOffsetType = new HasOffsetValueType($offsetType, $valueToWrite); + $offsetConstantArrayKeys = $offsetType->getConstantArrayKeys(); + if ($offsetConstantArrayKeys !== []) { + $hasOffsetType = []; + foreach ($offsetConstantArrayKeys as $constantArrayKey) { + $hasOffsetType[] = new HasOffsetValueType($constantArrayKey, $valueToWrite); + } } $valueToWrite = $offsetValueType->setExistingOffsetValueType($offsetType, $valueToWrite); if ($hasOffsetType !== null) { $valueToWrite = TypeCombinator::intersect( $valueToWrite, - $hasOffsetType, + ...$hasOffsetType, ); } elseif ($valueToWrite->isArray()->yes()) { $valueToWrite = TypeCombinator::intersect( diff --git a/src/Type/Accessory/AccessoryArrayListType.php b/src/Type/Accessory/AccessoryArrayListType.php index 6ed6baeced..1df2ee93b5 100644 --- a/src/Type/Accessory/AccessoryArrayListType.php +++ b/src/Type/Accessory/AccessoryArrayListType.php @@ -70,6 +70,11 @@ public function getConstantArrays(): array return []; } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/Accessory/AccessoryLiteralStringType.php b/src/Type/Accessory/AccessoryLiteralStringType.php index d8a02005d6..9cdb7147f4 100644 --- a/src/Type/Accessory/AccessoryLiteralStringType.php +++ b/src/Type/Accessory/AccessoryLiteralStringType.php @@ -64,6 +64,11 @@ public function getObjectClassReflections(): array return []; } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/Accessory/AccessoryLowercaseStringType.php b/src/Type/Accessory/AccessoryLowercaseStringType.php index 3e1383bdb4..23a2bbf707 100644 --- a/src/Type/Accessory/AccessoryLowercaseStringType.php +++ b/src/Type/Accessory/AccessoryLowercaseStringType.php @@ -64,6 +64,11 @@ public function getObjectClassReflections(): array return []; } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/Accessory/AccessoryNonEmptyStringType.php b/src/Type/Accessory/AccessoryNonEmptyStringType.php index a8b573809a..007cf42a6d 100644 --- a/src/Type/Accessory/AccessoryNonEmptyStringType.php +++ b/src/Type/Accessory/AccessoryNonEmptyStringType.php @@ -65,6 +65,11 @@ public function getObjectClassReflections(): array return []; } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/Accessory/AccessoryNonFalsyStringType.php b/src/Type/Accessory/AccessoryNonFalsyStringType.php index f32d613154..9e8b2cbbcd 100644 --- a/src/Type/Accessory/AccessoryNonFalsyStringType.php +++ b/src/Type/Accessory/AccessoryNonFalsyStringType.php @@ -67,6 +67,11 @@ public function getObjectClassReflections(): array return []; } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/Accessory/AccessoryNumericStringType.php b/src/Type/Accessory/AccessoryNumericStringType.php index c10dd28082..161cc6ba80 100644 --- a/src/Type/Accessory/AccessoryNumericStringType.php +++ b/src/Type/Accessory/AccessoryNumericStringType.php @@ -64,6 +64,11 @@ public function getObjectClassReflections(): array return []; } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/Accessory/AccessoryUppercaseStringType.php b/src/Type/Accessory/AccessoryUppercaseStringType.php index 5688e62df7..1f49bb7b85 100644 --- a/src/Type/Accessory/AccessoryUppercaseStringType.php +++ b/src/Type/Accessory/AccessoryUppercaseStringType.php @@ -64,6 +64,11 @@ public function getObjectClassReflections(): array return []; } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/Accessory/HasOffsetType.php b/src/Type/Accessory/HasOffsetType.php index da6c176655..2c3bbaec83 100644 --- a/src/Type/Accessory/HasOffsetType.php +++ b/src/Type/Accessory/HasOffsetType.php @@ -75,6 +75,11 @@ public function getObjectClassReflections(): array return []; } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/Accessory/HasOffsetValueType.php b/src/Type/Accessory/HasOffsetValueType.php index 5e7c834942..097786e0cc 100644 --- a/src/Type/Accessory/HasOffsetValueType.php +++ b/src/Type/Accessory/HasOffsetValueType.php @@ -78,6 +78,11 @@ public function getObjectClassReflections(): array return []; } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/Accessory/HasPropertyType.php b/src/Type/Accessory/HasPropertyType.php index 71b6b42759..e6e462cf5f 100644 --- a/src/Type/Accessory/HasPropertyType.php +++ b/src/Type/Accessory/HasPropertyType.php @@ -51,6 +51,11 @@ public function getObjectClassReflections(): array return []; } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/Accessory/NonEmptyArrayType.php b/src/Type/Accessory/NonEmptyArrayType.php index d360d85172..501f0c1fa5 100644 --- a/src/Type/Accessory/NonEmptyArrayType.php +++ b/src/Type/Accessory/NonEmptyArrayType.php @@ -69,6 +69,11 @@ public function getConstantArrays(): array return []; } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/Accessory/OversizedArrayType.php b/src/Type/Accessory/OversizedArrayType.php index 77fdec48fb..7d6a581cdf 100644 --- a/src/Type/Accessory/OversizedArrayType.php +++ b/src/Type/Accessory/OversizedArrayType.php @@ -67,6 +67,11 @@ public function getConstantArrays(): array return []; } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/BooleanType.php b/src/Type/BooleanType.php index bbde2f2aa8..32148d33a2 100644 --- a/src/Type/BooleanType.php +++ b/src/Type/BooleanType.php @@ -46,6 +46,11 @@ public function getConstantStrings(): array return []; } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantScalarTypes(): array { return [new ConstantBooleanType(true), new ConstantBooleanType(false)]; diff --git a/src/Type/CallableType.php b/src/Type/CallableType.php index 568c847711..dec5e06940 100644 --- a/src/Type/CallableType.php +++ b/src/Type/CallableType.php @@ -124,6 +124,11 @@ public function getObjectClassReflections(): array return []; } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/ClosureType.php b/src/Type/ClosureType.php index 292557f12a..84680c9f9c 100644 --- a/src/Type/ClosureType.php +++ b/src/Type/ClosureType.php @@ -358,6 +358,11 @@ public function getConstant(string $constantName): ClassConstantReflection return $this->objectType->getConstant($constantName); } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/FloatType.php b/src/Type/FloatType.php index 92ebb92cf1..2640e04ea9 100644 --- a/src/Type/FloatType.php +++ b/src/Type/FloatType.php @@ -57,6 +57,11 @@ public function getObjectClassReflections(): array return []; } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/IntegerRangeType.php b/src/Type/IntegerRangeType.php index 1c956015dd..26cfcbcaaf 100644 --- a/src/Type/IntegerRangeType.php +++ b/src/Type/IntegerRangeType.php @@ -693,6 +693,11 @@ public function exponentiate(Type $exponent): Type return parent::exponentiate($exponent); } + public function getConstantArrayKeys(): array + { + return $this->getFiniteTypes(); + } + /** * @return list */ diff --git a/src/Type/IntegerType.php b/src/Type/IntegerType.php index 0dd713bee8..07bef4cd96 100644 --- a/src/Type/IntegerType.php +++ b/src/Type/IntegerType.php @@ -47,6 +47,11 @@ public function describe(VerbosityLevel $level): string return 'int'; } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/IntersectionType.php b/src/Type/IntersectionType.php index 2d305b6883..3052bd01d6 100644 --- a/src/Type/IntersectionType.php +++ b/src/Type/IntersectionType.php @@ -176,6 +176,18 @@ public function getConstantArrays(): array return $constantArrays; } + public function getConstantArrayKeys(): array + { + $arrayKeys = []; + foreach ($this->types as $type) { + foreach ($type->getConstantArrayKeys() as $string) { + $arrayKeys[] = $string; + } + } + + return $arrayKeys; + } + public function getConstantStrings(): array { $strings = []; diff --git a/src/Type/IterableType.php b/src/Type/IterableType.php index 2242c477a5..ed0a756f1b 100644 --- a/src/Type/IterableType.php +++ b/src/Type/IterableType.php @@ -71,6 +71,11 @@ public function getObjectClassReflections(): array return []; } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/MixedType.php b/src/Type/MixedType.php index 1245743947..4dd0047a5c 100644 --- a/src/Type/MixedType.php +++ b/src/Type/MixedType.php @@ -88,6 +88,11 @@ public function getConstantArrays(): array return []; } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/NeverType.php b/src/Type/NeverType.php index 659368d6da..237b50b16c 100644 --- a/src/Type/NeverType.php +++ b/src/Type/NeverType.php @@ -64,6 +64,11 @@ public function getObjectClassReflections(): array return []; } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/NonexistentParentClassType.php b/src/Type/NonexistentParentClassType.php index 0b91e093e6..e10d2f132f 100644 --- a/src/Type/NonexistentParentClassType.php +++ b/src/Type/NonexistentParentClassType.php @@ -112,6 +112,11 @@ public function getConstant(string $constantName): ClassConstantReflection throw new ShouldNotHappenException(); } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/NullType.php b/src/Type/NullType.php index ef2408e713..26e7c73323 100644 --- a/src/Type/NullType.php +++ b/src/Type/NullType.php @@ -51,6 +51,11 @@ public function getObjectClassReflections(): array return []; } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/ObjectType.php b/src/Type/ObjectType.php index 78e4473410..5ae794c3eb 100644 --- a/src/Type/ObjectType.php +++ b/src/Type/ObjectType.php @@ -917,6 +917,11 @@ public function getTemplateType(string $ancestorClassName, string $templateTypeN return $type; } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/ResourceType.php b/src/Type/ResourceType.php index 3e83e3d819..c2208fdddd 100644 --- a/src/Type/ResourceType.php +++ b/src/Type/ResourceType.php @@ -45,6 +45,11 @@ public function describe(VerbosityLevel $level): string return 'resource'; } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/StaticType.php b/src/Type/StaticType.php index 2d5a7c3ad8..1037427553 100644 --- a/src/Type/StaticType.php +++ b/src/Type/StaticType.php @@ -123,6 +123,11 @@ public function getConstantArrays(): array return $this->getStaticObjectType()->getConstantArrays(); } + public function getConstantArrayKeys(): array + { + return $this->getStaticObjectType()->getConstantArrayKeys(); + } + public function getConstantStrings(): array { return $this->getStaticObjectType()->getConstantStrings(); diff --git a/src/Type/StrictMixedType.php b/src/Type/StrictMixedType.php index 0ff25dc124..f42fef31d8 100644 --- a/src/Type/StrictMixedType.php +++ b/src/Type/StrictMixedType.php @@ -46,6 +46,11 @@ public function getObjectClassReflections(): array return []; } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/StringType.php b/src/Type/StringType.php index 44fa96ab15..861ee7b0e2 100644 --- a/src/Type/StringType.php +++ b/src/Type/StringType.php @@ -47,6 +47,11 @@ public function describe(VerbosityLevel $level): string return 'string'; } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/Traits/ConstantScalarTypeTrait.php b/src/Type/Traits/ConstantScalarTypeTrait.php index f458512622..101fc72cf9 100644 --- a/src/Type/Traits/ConstantScalarTypeTrait.php +++ b/src/Type/Traits/ConstantScalarTypeTrait.php @@ -9,6 +9,8 @@ use PHPStan\Type\BooleanType; use PHPStan\Type\CompoundType; use PHPStan\Type\Constant\ConstantBooleanType; +use PHPStan\Type\Constant\ConstantIntegerType; +use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ConstantScalarType; use PHPStan\Type\IsSuperTypeOfResult; use PHPStan\Type\LooseComparisonHelper; @@ -120,6 +122,22 @@ public function getConstantScalarValues(): array return [$this->getValue()]; } + public function getConstantArrayKeys(): array + { + $arrayKey = $this->toArrayKey(); + + if ( + /** @phpstan-ignore phpstanApi.instanceofType */ + !$arrayKey instanceof ConstantIntegerType + /** @phpstan-ignore phpstanApi.instanceofType */ + && !$arrayKey instanceof ConstantStringType + ) { + throw new ShouldNotHappenException(); + } + + return [$arrayKey]; + } + public function getFiniteTypes(): array { return [$this]; diff --git a/src/Type/Traits/LateResolvableTypeTrait.php b/src/Type/Traits/LateResolvableTypeTrait.php index 0e1dee9904..74d2b175a5 100644 --- a/src/Type/Traits/LateResolvableTypeTrait.php +++ b/src/Type/Traits/LateResolvableTypeTrait.php @@ -44,6 +44,11 @@ public function getConstantArrays(): array return $this->resolve()->getConstantArrays(); } + public function getConstantArrayKeys(): array + { + return $this->resolve()->getConstantArrayKeys(); + } + public function getConstantStrings(): array { return $this->resolve()->getConstantStrings(); diff --git a/src/Type/Traits/NonObjectTypeTrait.php b/src/Type/Traits/NonObjectTypeTrait.php index d16b86c9b1..e61613abf2 100644 --- a/src/Type/Traits/NonObjectTypeTrait.php +++ b/src/Type/Traits/NonObjectTypeTrait.php @@ -81,6 +81,11 @@ public function getConstant(string $constantName): ClassConstantReflection throw new ShouldNotHappenException(); } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/Traits/ObjectTypeTrait.php b/src/Type/Traits/ObjectTypeTrait.php index c600f2d74a..980472ad77 100644 --- a/src/Type/Traits/ObjectTypeTrait.php +++ b/src/Type/Traits/ObjectTypeTrait.php @@ -114,6 +114,11 @@ public function getConstant(string $constantName): ClassConstantReflection return new DummyClassConstantReflection($constantName); } + public function getConstantArrayKeys(): array + { + return []; + } + public function getConstantStrings(): array { return []; diff --git a/src/Type/Type.php b/src/Type/Type.php index b9c88f51e6..0242954d2f 100644 --- a/src/Type/Type.php +++ b/src/Type/Type.php @@ -14,6 +14,7 @@ use PHPStan\Reflection\Type\UnresolvedPropertyPrototypeReflection; use PHPStan\TrinaryLogic; use PHPStan\Type\Constant\ConstantArrayType; +use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\Enum\EnumCaseObjectType; use PHPStan\Type\Generic\TemplateTypeMap; @@ -64,6 +65,9 @@ public function getConstantArrays(): array; /** @return list */ public function getConstantStrings(): array; + /** @return list */ + public function getConstantArrayKeys(): array; + public function accepts(Type $type, bool $strictTypes): AcceptsResult; public function isSuperTypeOf(Type $type): IsSuperTypeOfResult; diff --git a/src/Type/UnionType.php b/src/Type/UnionType.php index f459041746..fc90143c7f 100644 --- a/src/Type/UnionType.php +++ b/src/Type/UnionType.php @@ -182,6 +182,11 @@ public function getConstantArrays(): array ); } + public function getConstantArrayKeys(): array + { + return $this->notBenevolentPickFromTypes(static fn (Type $type) => $type->getConstantArrayKeys()); + } + public function getConstantStrings(): array { return $this->pickFromTypes( diff --git a/tests/PHPStan/Analyser/nsrt/array-const-array-keys.php b/tests/PHPStan/Analyser/nsrt/array-const-array-keys.php new file mode 100644 index 0000000000..30d106d07d --- /dev/null +++ b/tests/PHPStan/Analyser/nsrt/array-const-array-keys.php @@ -0,0 +1,17 @@ + $oneTwoThree + * @param 2|'aA' $constUnion + */ +function doFoo(array $arr, $oneTwoThree, $constUnion) { + if ($arr[$oneTwoThree]) { + assertType("non-empty-array&hasOffsetValue(1, mixed~(0|0.0|''|'0'|array{}|false|null))&hasOffsetValue(2, mixed~(0|0.0|''|'0'|array{}|false|null))&hasOffsetValue(3, mixed~(0|0.0|''|'0'|array{}|false|null))", $arr); + } + + if ($arr[$constUnion]) { + assertType("non-empty-array&hasOffsetValue('aA', mixed~(0|0.0|''|'0'|array{}|false|null))&hasOffsetValue(1, mixed)&hasOffsetValue(2, mixed~(0|0.0|''|'0'|array{}|false|null))&hasOffsetValue(3, mixed)", $arr); + } +} diff --git a/tests/PHPStan/Rules/Methods/data/bug-12927.php b/tests/PHPStan/Rules/Methods/data/bug-12927.php index 0331446aec..adc59dd169 100644 --- a/tests/PHPStan/Rules/Methods/data/bug-12927.php +++ b/tests/PHPStan/Rules/Methods/data/bug-12927.php @@ -60,4 +60,19 @@ public function sayFooBar(array $list): void } assertType('array', $list[$k]); } + + /** + * @param list> $list + * @param 'ab'|'cd' $constUnion + */ + public function sayUnionFoo2(array $list, $constUnion): void + { + foreach($list as $k => $v) { + $list[$k][$constUnion] = 'world'; + assertType("non-empty-list&hasOffsetValue('ab', 'world')&hasOffsetValue('cd', 'world')>", $list); + assertType("non-empty-array&hasOffsetValue('ab', 'world')&hasOffsetValue('cd', 'world')", $list[$k]); + } + assertType("list&hasOffsetValue('ab', 'world')&hasOffsetValue('cd', 'world')>", $list); + } + }