Skip to content

Commit

Permalink
Changelog for squizlabs#3581
Browse files Browse the repository at this point in the history
  • Loading branch information
gsherwood committed May 17, 2022
2 parents 1fff686 + c803671 commit e4abe66
Show file tree
Hide file tree
Showing 13 changed files with 678 additions and 47 deletions.
9 changes: 9 additions & 0 deletions package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ http://pear.php.net/dtd/package-2.0.xsd">
- Added support for the PHP 8.1 readonly token
-- Tokenzing of the readonly keyword has been backfilled for PHP versions less than 8.1
-- Thanks to Jaroslav Hanslík for the patch
- Added support for PHP 8.1 intersection types
-- Includes a new T_TYPE_INTERSECTION token to represent the ampersand character inside intersection types
-- Thanks to Jaroslav Hanslík for the patch
- File::getMethodParameters now supports the new PHP 8.1 readonly token
-- When constructor property promotion is used, a new property_readonly array index is included in the return value
--- This is a boolean value indicating if the property is readonly
Expand Down Expand Up @@ -199,6 +202,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
<file baseinstalldir="" name="StableCommentWhitespaceTest.php" role="test" />
<file baseinstalldir="" name="StableCommentWhitespaceWinTest.inc" role="test" />
<file baseinstalldir="" name="StableCommentWhitespaceWinTest.php" role="test" />
<file baseinstalldir="" name="TypeIntersectionTest.inc" role="test" />
<file baseinstalldir="" name="TypeIntersectionTest.php" role="test" />
<file baseinstalldir="" name="UndoNamespacedNameSingleTokenTest.inc" role="test" />
<file baseinstalldir="" name="UndoNamespacedNameSingleTokenTest.php" role="test" />
</dir>
Expand Down Expand Up @@ -2168,6 +2173,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
<install as="CodeSniffer/Core/Tokenizer/StableCommentWhitespaceTest.inc" name="tests/Core/Tokenizer/StableCommentWhitespaceTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/StableCommentWhitespaceWinTest.php" name="tests/Core/Tokenizer/StableCommentWhitespaceWinTest.php" />
<install as="CodeSniffer/Core/Tokenizer/StableCommentWhitespaceWinTest.inc" name="tests/Core/Tokenizer/StableCommentWhitespaceWinTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/TypeIntersectionTest.php" name="tests/Core/Tokenizer/TypeIntersectionTest.php" />
<install as="CodeSniffer/Core/Tokenizer/TypeIntersectionTest.inc" name="tests/Core/Tokenizer/TypeIntersectionTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/UndoNamespacedNameSingleTokenTest.php" name="tests/Core/Tokenizer/UndoNamespacedNameSingleTokenTest.php" />
<install as="CodeSniffer/Core/Tokenizer/UndoNamespacedNameSingleTokenTest.inc" name="tests/Core/Tokenizer/UndoNamespacedNameSingleTokenTest.inc" />
<install as="CodeSniffer/Standards/AllSniffs.php" name="tests/Standards/AllSniffs.php" />
Expand Down Expand Up @@ -2268,6 +2275,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
<install as="CodeSniffer/Core/Tokenizer/StableCommentWhitespaceTest.inc" name="tests/Core/Tokenizer/StableCommentWhitespaceTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/StableCommentWhitespaceWinTest.php" name="tests/Core/Tokenizer/StableCommentWhitespaceWinTest.php" />
<install as="CodeSniffer/Core/Tokenizer/StableCommentWhitespaceWinTest.inc" name="tests/Core/Tokenizer/StableCommentWhitespaceWinTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/TypeIntersectionTest.php" name="tests/Core/Tokenizer/TypeIntersectionTest.php" />
<install as="CodeSniffer/Core/Tokenizer/TypeIntersectionTest.inc" name="tests/Core/Tokenizer/TypeIntersectionTest.inc" />
<install as="CodeSniffer/Core/Tokenizer/UndoNamespacedNameSingleTokenTest.php" name="tests/Core/Tokenizer/UndoNamespacedNameSingleTokenTest.php" />
<install as="CodeSniffer/Core/Tokenizer/UndoNamespacedNameSingleTokenTest.inc" name="tests/Core/Tokenizer/UndoNamespacedNameSingleTokenTest.inc" />
<install as="CodeSniffer/Standards/AllSniffs.php" name="tests/Standards/AllSniffs.php" />
Expand Down
41 changes: 22 additions & 19 deletions src/Files/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -1469,6 +1469,7 @@ public function getMethodParameters($stackPtr)
case T_NAMESPACE:
case T_NS_SEPARATOR:
case T_TYPE_UNION:
case T_TYPE_INTERSECTION:
case T_FALSE:
case T_NULL:
// Part of a type hint or default value.
Expand Down Expand Up @@ -1685,16 +1686,17 @@ public function getMethodProperties($stackPtr)
}

$valid = [
T_STRING => T_STRING,
T_CALLABLE => T_CALLABLE,
T_SELF => T_SELF,
T_PARENT => T_PARENT,
T_STATIC => T_STATIC,
T_FALSE => T_FALSE,
T_NULL => T_NULL,
T_NAMESPACE => T_NAMESPACE,
T_NS_SEPARATOR => T_NS_SEPARATOR,
T_TYPE_UNION => T_TYPE_UNION,
T_STRING => T_STRING,
T_CALLABLE => T_CALLABLE,
T_SELF => T_SELF,
T_PARENT => T_PARENT,
T_STATIC => T_STATIC,
T_FALSE => T_FALSE,
T_NULL => T_NULL,
T_NAMESPACE => T_NAMESPACE,
T_NS_SEPARATOR => T_NS_SEPARATOR,
T_TYPE_UNION => T_TYPE_UNION,
T_TYPE_INTERSECTION => T_TYPE_INTERSECTION,
];

for ($i = $this->tokens[$stackPtr]['parenthesis_closer']; $i < $this->numTokens; $i++) {
Expand Down Expand Up @@ -1886,15 +1888,16 @@ public function getMemberProperties($stackPtr)
if ($i < $stackPtr) {
// We've found a type.
$valid = [
T_STRING => T_STRING,
T_CALLABLE => T_CALLABLE,
T_SELF => T_SELF,
T_PARENT => T_PARENT,
T_FALSE => T_FALSE,
T_NULL => T_NULL,
T_NAMESPACE => T_NAMESPACE,
T_NS_SEPARATOR => T_NS_SEPARATOR,
T_TYPE_UNION => T_TYPE_UNION,
T_STRING => T_STRING,
T_CALLABLE => T_CALLABLE,
T_SELF => T_SELF,
T_PARENT => T_PARENT,
T_FALSE => T_FALSE,
T_NULL => T_NULL,
T_NAMESPACE => T_NAMESPACE,
T_NS_SEPARATOR => T_NS_SEPARATOR,
T_TYPE_UNION => T_TYPE_UNION,
T_TYPE_INTERSECTION => T_TYPE_INTERSECTION,
];

for ($i; $i < $stackPtr; $i++) {
Expand Down
12 changes: 9 additions & 3 deletions src/Standards/Generic/Sniffs/PHP/LowerCaseTypeSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@ public function process(File $phpcsFile, $stackPtr)
$error = 'PHP property type declarations must be lowercase; expected "%s" but found "%s"';
$errorCode = 'PropertyTypeFound';

if (strpos($type, '|') !== false) {
if ($props['type_token'] === T_TYPE_INTERSECTION) {
// Intersection types don't support simple types.
} else if (strpos($type, '|') !== false) {
$this->processUnionType(
$phpcsFile,
$props['type_token'],
Expand Down Expand Up @@ -132,7 +134,9 @@ public function process(File $phpcsFile, $stackPtr)
$error = 'PHP return type declarations must be lowercase; expected "%s" but found "%s"';
$errorCode = 'ReturnTypeFound';

if (strpos($returnType, '|') !== false) {
if ($props['return_type_token'] === T_TYPE_INTERSECTION) {
// Intersection types don't support simple types.
} else if (strpos($returnType, '|') !== false) {
$this->processUnionType(
$phpcsFile,
$props['return_type_token'],
Expand Down Expand Up @@ -162,7 +166,9 @@ public function process(File $phpcsFile, $stackPtr)
$error = 'PHP parameter type declarations must be lowercase; expected "%s" but found "%s"';
$errorCode = 'ParamTypeFound';

if (strpos($typeHint, '|') !== false) {
if ($param['type_hint_token'] === T_TYPE_INTERSECTION) {
// Intersection types don't support simple types.
} else if (strpos($typeHint, '|') !== false) {
$this->processUnionType(
$phpcsFile,
$param['type_hint_token'],
Expand Down
65 changes: 40 additions & 25 deletions src/Tokenizers/PHP.php
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ class PHP extends Tokenizer
T_OPEN_SHORT_ARRAY => 1,
T_CLOSE_SHORT_ARRAY => 1,
T_TYPE_UNION => 1,
T_TYPE_INTERSECTION => 1,
];

/**
Expand Down Expand Up @@ -2438,18 +2439,19 @@ protected function processAdditional()
if (isset($this->tokens[$x]) === true && $this->tokens[$x]['code'] === T_OPEN_PARENTHESIS) {
$ignore = Util\Tokens::$emptyTokens;
$ignore += [
T_ARRAY => T_ARRAY,
T_CALLABLE => T_CALLABLE,
T_COLON => T_COLON,
T_NAMESPACE => T_NAMESPACE,
T_NS_SEPARATOR => T_NS_SEPARATOR,
T_NULL => T_NULL,
T_NULLABLE => T_NULLABLE,
T_PARENT => T_PARENT,
T_SELF => T_SELF,
T_STATIC => T_STATIC,
T_STRING => T_STRING,
T_TYPE_UNION => T_TYPE_UNION,
T_ARRAY => T_ARRAY,
T_CALLABLE => T_CALLABLE,
T_COLON => T_COLON,
T_NAMESPACE => T_NAMESPACE,
T_NS_SEPARATOR => T_NS_SEPARATOR,
T_NULL => T_NULL,
T_NULLABLE => T_NULLABLE,
T_PARENT => T_PARENT,
T_SELF => T_SELF,
T_STATIC => T_STATIC,
T_STRING => T_STRING,
T_TYPE_UNION => T_TYPE_UNION,
T_TYPE_INTERSECTION => T_TYPE_INTERSECTION,
];

$closer = $this->tokens[$x]['parenthesis_closer'];
Expand Down Expand Up @@ -2745,9 +2747,12 @@ protected function processAdditional()
}//end if

continue;
} else if ($this->tokens[$i]['code'] === T_BITWISE_OR) {
} else if ($this->tokens[$i]['code'] === T_BITWISE_OR
|| $this->tokens[$i]['code'] === T_BITWISE_AND
) {
/*
Convert "|" to T_TYPE_UNION or leave as T_BITWISE_OR.
Convert "&" to T_TYPE_INTERSECTION or leave as T_BITWISE_AND.
*/

$allowed = [
Expand Down Expand Up @@ -2812,12 +2817,12 @@ protected function processAdditional()
}//end for

if ($typeTokenCount === 0 || isset($suspectedType) === false) {
// Definitely not a union type, move on.
// Definitely not a union or intersection type, move on.
continue;
}

$typeTokenCount = 0;
$unionOperators = [$i];
$typeOperators = [$i];
$confirmed = false;

for ($x = ($i - 1); $x >= 0; $x--) {
Expand All @@ -2830,13 +2835,13 @@ protected function processAdditional()
continue;
}

// Union types can't use the nullable operator, but be tolerant to parse errors.
// Union and intersection types can't use the nullable operator, but be tolerant to parse errors.
if ($typeTokenCount > 0 && $this->tokens[$x]['code'] === T_NULLABLE) {
continue;
}

if ($this->tokens[$x]['code'] === T_BITWISE_OR) {
$unionOperators[] = $x;
if ($this->tokens[$x]['code'] === T_BITWISE_OR || $this->tokens[$x]['code'] === T_BITWISE_AND) {
$typeOperators[] = $x;
continue;
}

Expand Down Expand Up @@ -2902,17 +2907,27 @@ protected function processAdditional()
}//end if

if ($confirmed === false) {
// Not a union type after all, move on.
// Not a union or intersection type after all, move on.
continue;
}

foreach ($unionOperators as $x) {
$this->tokens[$x]['code'] = T_TYPE_UNION;
$this->tokens[$x]['type'] = 'T_TYPE_UNION';
foreach ($typeOperators as $x) {
if ($this->tokens[$x]['code'] === T_BITWISE_OR) {
$this->tokens[$x]['code'] = T_TYPE_UNION;
$this->tokens[$x]['type'] = 'T_TYPE_UNION';

if (PHP_CODESNIFFER_VERBOSITY > 1) {
$line = $this->tokens[$x]['line'];
echo "\t* token $x on line $line changed from T_BITWISE_OR to T_TYPE_UNION".PHP_EOL;
if (PHP_CODESNIFFER_VERBOSITY > 1) {
$line = $this->tokens[$x]['line'];
echo "\t* token $x on line $line changed from T_BITWISE_OR to T_TYPE_UNION".PHP_EOL;
}
} else {
$this->tokens[$x]['code'] = T_TYPE_INTERSECTION;
$this->tokens[$x]['type'] = 'T_TYPE_INTERSECTION';

if (PHP_CODESNIFFER_VERBOSITY > 1) {
$line = $this->tokens[$x]['line'];
echo "\t* token $x on line $line changed from T_BITWISE_AND to T_TYPE_INTERSECTION".PHP_EOL;
}
}
}

Expand Down
1 change: 1 addition & 0 deletions src/Util/Tokens.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
define('T_MATCH_DEFAULT', 'PHPCS_T_MATCH_DEFAULT');
define('T_ATTRIBUTE_END', 'PHPCS_T_ATTRIBUTE_END');
define('T_ENUM_CASE', 'PHPCS_T_ENUM_CASE');
define('T_TYPE_INTERSECTION', 'PHPCS_T_TYPE_INTERSECTION');

// Some PHP 5.5 tokens, replicated for lower versions.
if (defined('T_FINALLY') === false) {
Expand Down
16 changes: 16 additions & 0 deletions tests/Core/File/GetMemberPropertiesTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -286,3 +286,19 @@ enum Direction implements ArrayAccess
/* testEnumMethodParamNotProperty */
public function offsetGet($val) { ... }
}

$anon = class() {
/* testPHP81IntersectionTypes */
public Foo&Bar $intersectionType;

/* testPHP81MoreIntersectionTypes */
public Foo&Bar&Baz $moreIntersectionTypes;

/* testPHP81IllegalIntersectionTypes */
// Intentional fatal error - types which are not allowed for intersection type, but that's not the concern of the method.
public int&string $illegalIntersectionType;

/* testPHP81NulltableIntersectionType */
// Intentional fatal error - nullability is not allowed with intersection type, but that's not the concern of the method.
public ?Foo&Bar $nullableIntersectionType;
};
40 changes: 40 additions & 0 deletions tests/Core/File/GetMemberPropertiesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,46 @@ public function dataGetMemberProperties()
'/* testEnumProperty */',
[],
],
[
'/* testPHP81IntersectionTypes */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'type' => 'Foo&Bar',
'nullable_type' => false,
],
],
[
'/* testPHP81MoreIntersectionTypes */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'type' => 'Foo&Bar&Baz',
'nullable_type' => false,
],
],
[
'/* testPHP81IllegalIntersectionTypes */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'type' => 'int&string',
'nullable_type' => false,
],
],
[
'/* testPHP81NulltableIntersectionType */',
[
'scope' => 'public',
'scope_specified' => true,
'is_static' => false,
'type' => '?Foo&Bar',
'nullable_type' => true,
],
],
];

}//end dataGetMemberProperties()
Expand Down
17 changes: 17 additions & 0 deletions tests/Core/File/GetMethodParametersTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,20 @@ class ParametersWithAttributes(
&...$otherParam,
) {}
}

/* testPHP8IntersectionTypes */
function intersectionTypes(Foo&Bar $obj1, Boo&Bar $obj2) {}

/* testPHP81IntersectionTypesWithSpreadOperatorAndReference */
function globalFunctionWithSpreadAndReference(Boo&Bar &$paramA, Foo&Bar ...$paramB) {}

/* testPHP81MoreIntersectionTypes */
function moreIntersectionTypes(MyClassA&\Package\MyClassB&\Package\MyClassC $var) {}

/* testPHP81IllegalIntersectionTypes */
// Intentional fatal error - simple types are not allowed with intersection types, but that's not the concern of the method.
$closure = function (string&int $numeric_string) {};

/* testPHP81NullableIntersectionTypes */
// Intentional fatal error - nullability is not allowed with intersection types, but that's not the concern of the method.
$closure = function (?Foo&Bar $object) {};
Loading

0 comments on commit e4abe66

Please sign in to comment.