Skip to content

Commit

Permalink
PHP 8.2 | Classes/NewTypedProperties: detect DNF types
Browse files Browse the repository at this point in the history
> Type System Improvements
>
> It is now possible to combine intersection and union types. The type needs to be written in DNF.

This updates the sniff to detect DNF types.

Includes unit tests.

Refs:
* https://www.php.net/manual/en/migration82.new-features.php#migration82.new-features.core.type-system
* https://wiki.php.net/rfc/dnf_types
* php/php-src 8725
* php/php-src@f905590
  • Loading branch information
jrfnl committed May 24, 2024
1 parent e294237 commit 4123a52
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 3 deletions.
18 changes: 15 additions & 3 deletions PHPCompatibility/Sniffs/Classes/NewTypedPropertiesSniff.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
* - Since PHP 8.1, intersection types are supported for class/interface names.
* - Since PHP 8.2, `false` and `null` can be used as stand-alone types.
* - Since PHP 8.2, the `true` sub-type is available.
* - Since PHP 8.2, disjunctive normal form types are available.
*
* PHP version 7.4+
*
Expand All @@ -38,6 +39,7 @@
* @link https://wiki.php.net/rfc/pure-intersection-types
* @link https://wiki.php.net/rfc/null-false-standalone-types
* @link https://wiki.php.net/rfc/true-type
* @link https://wiki.php.net/rfc/dnf_types
*
* @since 9.2.0
*/
Expand Down Expand Up @@ -240,9 +242,10 @@ protected function checkType(File $phpcsFile, $typeToken, $typeInfo)
[$origType]
);
} else {
$types = \preg_split('`[|&]`', $type, -1, \PREG_SPLIT_NO_EMPTY);
$isUnionType = (\strpos($type, '|') !== false);
$isIntersectionType = (\strpos($type, '&') !== false);
$types = \preg_split('`[|&()]`', $type, -1, \PREG_SPLIT_NO_EMPTY);
$isUnionType = (\strpos($type, '|') !== false && \strpos($type, '(') === false);
$isIntersectionType = (\strpos($type, '&') !== false && \strpos($type, '(') === false);
$isDNFType = \strpos($type, '(') !== false;

if (ScannedCode::shouldRunOnOrBelow('7.4') === true && $isUnionType === true) {
$phpcsFile->addError(
Expand All @@ -262,6 +265,15 @@ protected function checkType(File $phpcsFile, $typeToken, $typeInfo)
);
}

if (ScannedCode::shouldRunOnOrBelow('8.1') === true && $isDNFType === true) {
$phpcsFile->addError(
'Disjunctive Normal Form types are not present in PHP version 8.1 or earlier. Found: %s',
$typeToken,
'DNFTypeFound',
[$origType]
);
}

foreach ($types as $type) {
if (isset($this->newTypes[$type])) {
$itemInfo = [
Expand Down
15 changes: 15 additions & 0 deletions PHPCompatibility/Tests/Classes/NewTypedPropertiesUnitTest.inc
Original file line number Diff line number Diff line change
Expand Up @@ -169,3 +169,18 @@ class EdgeCase {
class ShouldBeDetected {
public int $property;
}

// PHP 8.2 DNF types.
class DNFTypes {
public (Foo&Bar)|null $dnf1;
public readonly (A&B)|(C&D) $dnf2;

// Intentional fatal error - &/| reversed, but that's not the concern of the sniff.
public B&(D|W) $illegalDnf;

// Intentional fatal error - segments are not unique, but that's not the concern of the sniff.
public (A&B)|(B&A) $duplicateNotAllowed;

// Intentional fatal error - segments which are strict subsets of others are disallowed, but that's not the concern of the sniff.
public (A&self)|A $nonSubsetNotAllowedPlusSelf;
}
45 changes: 45 additions & 0 deletions PHPCompatibility/Tests/Classes/NewTypedPropertiesUnitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ public static function dataNewTypedProperties()
[161],
[165],
[170],
[175],
[176],
[179],
[182],
[185],
];
}

Expand Down Expand Up @@ -231,6 +236,7 @@ public static function dataNewTypedPropertyTypes()
['false', '7.4', 99, '8.0'],
['mixed', '7.4', 116, '8.0', false],
['true', '8.1', 147, '8.2'],
['null', '7.4', 175, '8.2'],
];
}

Expand Down Expand Up @@ -338,6 +344,7 @@ public static function dataInvalidNonUnionNullFalseType()
return [
[93, 'null'],
[96, 'false'],
[175, 'null'],
];
}

Expand Down Expand Up @@ -413,6 +420,44 @@ public static function dataNewIntersectionTypes()
}


/**
* Verify that an error is thrown for DNF types.
*
* @dataProvider dataNewDNFTypes
*
* @param string $type The declared type.
* @param int $line The line number where the error is expected.
*
* @return void
*/
public function testNewDNFTypes($type, $line)
{
$file = $this->sniffFile(__FILE__, '8.1');
$this->assertError($file, $line, "Disjunctive Normal Form types are not present in PHP version 8.1 or earlier. Found: $type");

$file = $this->sniffFile(__FILE__, '8.2');
$this->assertNoViolation($file, $line);
}

/**
* Data provider.
*
* @see testNewDNFTypes()
*
* @return array
*/
public static function dataNewDNFTypes()
{
return [
['(Foo&Bar)|null', 175],
['(A&B)|(C&D)', 176],
['B&(D|W)', 179],
['(A&B)|(B&A)', 182],
['(A&self)|A', 185],
];
}


/*
* `testNoViolationsInFileOnValidVersion` test omitted as this sniff will also throw warnings/errors
* about invalid typed properties.
Expand Down

0 comments on commit 4123a52

Please sign in to comment.