Skip to content

Commit e73bf01

Browse files
committed
Merge branch 'PHP-8.0'
2 parents ccf7c51 + d6264b0 commit e73bf01

28 files changed

+264
-116
lines changed

build/gen_stub.php

Lines changed: 185 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,30 @@
1212

1313
error_reporting(E_ALL);
1414

15-
function processDirectory(string $dir, Context $context) {
15+
/**
16+
* @return FileInfo[]
17+
*/
18+
function processDirectory(string $dir, Context $context): array {
19+
$fileInfos = [];
20+
1621
$it = new RecursiveIteratorIterator(
1722
new RecursiveDirectoryIterator($dir),
1823
RecursiveIteratorIterator::LEAVES_ONLY
1924
);
2025
foreach ($it as $file) {
2126
$pathName = $file->getPathName();
2227
if (preg_match('/\.stub\.php$/', $pathName)) {
23-
processStubFile($pathName, $context);
28+
$fileInfo = processStubFile($pathName, $context);
29+
if ($fileInfo) {
30+
$fileInfos[] = $fileInfo;
31+
}
2432
}
2533
}
34+
35+
return $fileInfos;
2636
}
2737

28-
function processStubFile(string $stubFile, Context $context) {
38+
function processStubFile(string $stubFile, Context $context): ?FileInfo {
2939
try {
3040
if (!file_exists($stubFile)) {
3141
throw new Exception("File $stubFile does not exist");
@@ -37,15 +47,15 @@ function processStubFile(string $stubFile, Context $context) {
3747
$stubCode = file_get_contents($stubFile);
3848
$stubHash = computeStubHash($stubCode);
3949
$oldStubHash = extractStubHash($arginfoFile);
40-
if ($stubHash === $oldStubHash && $context->forceRegeneration === false) {
50+
if ($stubHash === $oldStubHash && !$context->forceParse) {
4151
/* Stub file did not change, do not regenerate. */
42-
return;
52+
return null;
4353
}
4454

4555
initPhpParser();
4656
$fileInfo = parseStubFile($stubCode);
4757
$arginfoCode = generateArgInfoCode($fileInfo, $stubHash);
48-
if (file_put_contents($arginfoFile, $arginfoCode)) {
58+
if (($context->forceRegeneration || $stubHash !== $oldStubHash) && file_put_contents($arginfoFile, $arginfoCode)) {
4959
echo "Saved $arginfoFile\n";
5060
}
5161

@@ -54,20 +64,12 @@ function processStubFile(string $stubFile, Context $context) {
5464
$funcInfo->discardInfoForOldPhpVersions();
5565
}
5666
$arginfoCode = generateArgInfoCode($fileInfo, $stubHash);
57-
if (file_put_contents($legacyFile, $arginfoCode)) {
67+
if (($context->forceRegeneration || $stubHash !== $oldStubHash) && file_put_contents($legacyFile, $arginfoCode)) {
5868
echo "Saved $legacyFile\n";
5969
}
6070
}
6171

62-
// Collect parameter name statistics.
63-
foreach ($fileInfo->getAllFuncInfos() as $funcInfo) {
64-
foreach ($funcInfo->args as $argInfo) {
65-
if (!isset($context->parameterStats[$argInfo->name])) {
66-
$context->parameterStats[$argInfo->name] = 0;
67-
}
68-
$context->parameterStats[$argInfo->name]++;
69-
}
70-
}
72+
return $fileInfo;
7173
} catch (Exception $e) {
7274
echo "In $stubFile:\n{$e->getMessage()}\n";
7375
exit(1);
@@ -92,10 +94,10 @@ function extractStubHash(string $arginfoFile): ?string {
9294
}
9395

9496
class Context {
97+
/** @var bool */
98+
public $forceParse = false;
9599
/** @var bool */
96100
public $forceRegeneration = false;
97-
/** @var array */
98-
public $parameterStats = [];
99101
}
100102

101103
class SimpleType {
@@ -358,6 +360,8 @@ public function getDeclaration(): string;
358360
public function getArgInfoName(): string;
359361
public function __toString(): string;
360362
public function isMagicMethod(): bool;
363+
public function isMethod(): bool;
364+
public function isConstructor(): bool;
361365
}
362366

363367
class FunctionName implements FunctionOrMethodName {
@@ -402,6 +406,14 @@ public function __toString(): string {
402406
public function isMagicMethod(): bool {
403407
return false;
404408
}
409+
410+
public function isMethod(): bool {
411+
return false;
412+
}
413+
414+
public function isConstructor(): bool {
415+
return false;
416+
}
405417
}
406418

407419
class MethodName implements FunctionOrMethodName {
@@ -434,6 +446,14 @@ public function __toString(): string {
434446
public function isMagicMethod(): bool {
435447
return strpos($this->methodName, '__') === 0;
436448
}
449+
450+
public function isMethod(): bool {
451+
return true;
452+
}
453+
454+
public function isConstructor(): bool {
455+
return $this->methodName === "__construct";
456+
}
437457
}
438458

439459
class ReturnInfo {
@@ -457,13 +477,17 @@ class FuncInfo {
457477
/** @var FunctionOrMethodName */
458478
public $name;
459479
/** @var int */
480+
public $classFlags;
481+
/** @var int */
460482
public $flags;
461483
/** @var string|null */
462484
public $aliasType;
463485
/** @var FunctionName|null */
464486
public $alias;
465487
/** @var bool */
466488
public $isDeprecated;
489+
/** @var bool */
490+
public $verify;
467491
/** @var ArgInfo[] */
468492
public $args;
469493
/** @var ReturnInfo */
@@ -475,26 +499,45 @@ class FuncInfo {
475499

476500
public function __construct(
477501
FunctionOrMethodName $name,
502+
int $classFlags,
478503
int $flags,
479504
?string $aliasType,
480505
?FunctionOrMethodName $alias,
481506
bool $isDeprecated,
507+
bool $verify,
482508
array $args,
483509
ReturnInfo $return,
484510
int $numRequiredArgs,
485511
?string $cond
486512
) {
487513
$this->name = $name;
514+
$this->classFlags = $classFlags;
488515
$this->flags = $flags;
489516
$this->aliasType = $aliasType;
490517
$this->alias = $alias;
491518
$this->isDeprecated = $isDeprecated;
519+
$this->verify = $verify;
492520
$this->args = $args;
493521
$this->return = $return;
494522
$this->numRequiredArgs = $numRequiredArgs;
495523
$this->cond = $cond;
496524
}
497525

526+
public function isMethod(): bool
527+
{
528+
return $this->name->isMethod();
529+
}
530+
531+
public function isFinalMethod(): bool
532+
{
533+
return ($this->flags & Class_::MODIFIER_FINAL) || ($this->classFlags & Class_::MODIFIER_FINAL);
534+
}
535+
536+
public function isInstanceMethod(): bool
537+
{
538+
return !($this->flags & Class_::MODIFIER_STATIC) && $this->isMethod() && !$this->name->isConstructor();
539+
}
540+
498541
public function equalsApartFromName(FuncInfo $other): bool {
499542
if (count($this->args) !== count($other->args)) {
500543
return false;
@@ -732,6 +775,7 @@ function parseDocComment(DocComment $comment): array {
732775
function parseFunctionLike(
733776
PrettyPrinterAbstract $prettyPrinter,
734777
FunctionOrMethodName $name,
778+
int $classFlags,
735779
int $flags,
736780
Node\FunctionLike $func,
737781
?string $cond
@@ -741,6 +785,7 @@ function parseFunctionLike(
741785
$aliasType = null;
742786
$alias = null;
743787
$isDeprecated = false;
788+
$verify = true;
744789
$haveDocReturnType = false;
745790
$docParamTypes = [];
746791

@@ -763,6 +808,8 @@ function parseFunctionLike(
763808
}
764809
} else if ($tag->name === 'deprecated') {
765810
$isDeprecated = true;
811+
} else if ($tag->name === 'no-verify') {
812+
$verify = false;
766813
} else if ($tag->name === 'return') {
767814
$haveDocReturnType = true;
768815
} else if ($tag->name === 'param') {
@@ -843,10 +890,12 @@ function parseFunctionLike(
843890

844891
return new FuncInfo(
845892
$name,
893+
$classFlags,
846894
$flags,
847895
$aliasType,
848896
$alias,
849897
$isDeprecated,
898+
$verify,
850899
$args,
851900
$return,
852901
$numRequiredArgs,
@@ -917,6 +966,7 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
917966
$prettyPrinter,
918967
new FunctionName($stmt->namespacedName),
919968
0,
969+
0,
920970
$stmt,
921971
$cond
922972
);
@@ -936,6 +986,11 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
936986
throw new Exception("Not implemented {$classStmt->getType()}");
937987
}
938988

989+
$classFlags = 0;
990+
if ($stmt instanceof Class_) {
991+
$classFlags = $stmt->flags;
992+
}
993+
939994
$flags = $classStmt->flags;
940995
if ($stmt instanceof Stmt\Interface_) {
941996
$flags |= Class_::MODIFIER_ABSTRACT;
@@ -948,6 +1003,7 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
9481003
$methodInfos[] = parseFunctionLike(
9491004
$prettyPrinter,
9501005
new MethodName($className, $classStmt->name->toString()),
1006+
$classFlags,
9511007
$flags,
9521008
$classStmt,
9531009
$cond
@@ -1262,29 +1318,132 @@ function initPhpParser() {
12621318
}
12631319

12641320
$optind = null;
1265-
$options = getopt("fh", ["force-regeneration", "parameter-stats", "help"], $optind);
1321+
$options = getopt("fh", ["force-regeneration", "parameter-stats", "help", "verify"], $optind);
12661322

12671323
$context = new Context;
12681324
$printParameterStats = isset($options["parameter-stats"]);
1269-
$context->forceRegeneration =
1270-
isset($options["f"]) || isset($options["force-regeneration"]) || $printParameterStats;
1325+
$verify = isset($options["verify"]);
1326+
$context->forceRegeneration = isset($options["f"]) || isset($options["force-regeneration"]);
1327+
$context->forceParse = $context->forceRegeneration || $printParameterStats || $verify;
12711328

12721329
if (isset($options["h"]) || isset($options["help"])) {
1273-
die("\nusage: gen-stub.php [ -f | --force-regeneration ] [ --parameter-stats ] [ -h | --help ] [ name.stub.php | directory ]\n\n");
1330+
die("\nusage: gen-stub.php [ -f | --force-regeneration ] [ --parameter-stats ] [ --verify ] [ -h | --help ] [ name.stub.php | directory ]\n\n");
12741331
}
12751332

1333+
$fileInfos = [];
12761334
$location = $argv[$optind] ?? ".";
12771335
if (is_file($location)) {
12781336
// Generate single file.
1279-
processStubFile($location, $context);
1337+
$fileInfo = processStubFile($location, $context);
1338+
if ($fileInfo) {
1339+
$fileInfos[] = $fileInfo;
1340+
}
12801341
} else if (is_dir($location)) {
1281-
processDirectory($location, $context);
1342+
$fileInfos = processDirectory($location, $context);
12821343
} else {
12831344
echo "$location is neither a file nor a directory.\n";
12841345
exit(1);
12851346
}
12861347

12871348
if ($printParameterStats) {
1288-
arsort($context->parameterStats);
1289-
echo json_encode($context->parameterStats, JSON_PRETTY_PRINT), "\n";
1349+
$parameterStats = [];
1350+
1351+
foreach ($fileInfos as $fileInfo) {
1352+
foreach ($fileInfo->getAllFuncInfos() as $funcInfo) {
1353+
foreach ($funcInfo->args as $argInfo) {
1354+
if (!isset($context->parameterStats[$argInfo->name])) {
1355+
$parameterStats[$argInfo->name] = 0;
1356+
}
1357+
$parameterStats[$argInfo->name]++;
1358+
}
1359+
}
1360+
}
1361+
1362+
arsort($parameterStats);
1363+
echo json_encode($parameterStats, JSON_PRETTY_PRINT), "\n";
1364+
}
1365+
1366+
if ($verify) {
1367+
$errors = [];
1368+
$funcMap = [];
1369+
$aliases = [];
1370+
1371+
foreach ($fileInfos as $fileInfo) {
1372+
foreach ($fileInfo->getAllFuncInfos() as $funcInfo) {
1373+
/** @var FuncInfo $funcInfo */
1374+
$funcMap[$funcInfo->name->__toString()] = $funcInfo;
1375+
1376+
if ($funcInfo->aliasType === "alias") {
1377+
$aliases[] = $funcInfo;
1378+
}
1379+
}
1380+
}
1381+
1382+
foreach ($aliases as $aliasFunc) {
1383+
if (!isset($funcMap[$aliasFunc->alias->__toString()])) {
1384+
$errors[] = "Aliased function {$aliasFunc->alias}() cannot be found";
1385+
continue;
1386+
}
1387+
1388+
if (!$aliasFunc->verify) {
1389+
continue;
1390+
}
1391+
1392+
$aliasedFunc = $funcMap[$aliasFunc->alias->__toString()];
1393+
$aliasedArgs = $aliasedFunc->args;
1394+
$aliasArgs = $aliasFunc->args;
1395+
1396+
if ($aliasFunc->isInstanceMethod() !== $aliasedFunc->isInstanceMethod()) {
1397+
if ($aliasFunc->isInstanceMethod()) {
1398+
$aliasedArgs = array_slice($aliasedArgs, 1);
1399+
}
1400+
1401+
if ($aliasedFunc->isInstanceMethod()) {
1402+
$aliasArgs = array_slice($aliasArgs, 1);
1403+
}
1404+
}
1405+
1406+
array_map(
1407+
function(?ArgInfo $aliasArg, ?ArgInfo $aliasedArg) use ($aliasFunc, $aliasedFunc, &$errors) {
1408+
if ($aliasArg === null) {
1409+
assert($aliasedArg !== null);
1410+
$errors[] = "{$aliasFunc->name}(): Argument \$$aliasedArg->name of aliased function {$aliasedFunc->name}() is missing";
1411+
return null;
1412+
}
1413+
1414+
if ($aliasedArg === null) {
1415+
assert($aliasArg !== null);
1416+
$errors[] = "{$aliasedFunc->name}(): Argument \$$aliasArg->name of alias function {$aliasFunc->name}() is missing";
1417+
return null;
1418+
}
1419+
1420+
if ($aliasArg->name !== $aliasedArg->name) {
1421+
$errors[] = "{$aliasFunc->name}(): Argument \$$aliasArg->name and argument \$$aliasedArg->name of aliased function {$aliasedFunc->name}() must have the same name";
1422+
return null;
1423+
}
1424+
1425+
if ($aliasArg->type != $aliasedArg->type) {
1426+
$errors[] = "{$aliasFunc->name}(): Argument \$$aliasArg->name and argument \$$aliasedArg->name of aliased function {$aliasedFunc->name}() must have the same type";
1427+
}
1428+
1429+
if ($aliasArg->defaultValue !== $aliasedArg->defaultValue) {
1430+
$errors[] = "{$aliasFunc->name}(): Argument \$$aliasArg->name and argument \$$aliasedArg->name of aliased function {$aliasedFunc->name}() must have the same default value";
1431+
}
1432+
},
1433+
$aliasArgs, $aliasedArgs
1434+
);
1435+
1436+
if ((!$aliasedFunc->isMethod() || $aliasedFunc->isFinalMethod()) &&
1437+
(!$aliasFunc->isMethod() || $aliasFunc->isFinalMethod()) &&
1438+
$aliasFunc->return != $aliasedFunc->return
1439+
) {
1440+
$errors[] = "{$aliasFunc->name}() and {$aliasedFunc->name}() must have the same return type";
1441+
}
1442+
}
1443+
1444+
echo implode("\n", $errors);
1445+
if (!empty($errors)) {
1446+
echo "\n";
1447+
exit(1);
1448+
}
12901449
}

0 commit comments

Comments
 (0)