Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,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 +2170,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 +2272,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
66 changes: 41 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 @@ -2406,18 +2407,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 @@ -2713,9 +2715,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 @@ -2780,12 +2785,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 @@ -2798,13 +2803,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 @@ -2870,17 +2875,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 Expand Up @@ -2938,6 +2953,7 @@ protected function processAdditional()
T_NAME_RELATIVE => T_NAME_RELATIVE,
T_NAME_QUALIFIED => T_NAME_QUALIFIED,
T_TYPE_UNION => T_TYPE_UNION,
T_TYPE_INTERSECTION => T_TYPE_INTERSECTION,
T_BITWISE_OR => T_BITWISE_OR,
T_BITWISE_AND => T_BITWISE_AND,
T_ARRAY => T_ARRAY,
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 @@ -280,3 +280,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 @@ -736,6 +736,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