Skip to content

Commit 83f1fa3

Browse files
authored
Minor Release 0.12.x (#120)
* Implement static type evaluators for AST expressions * Add comprehensive unit tests for StaticTypeEvaluator and related components * Enhance ContractVisitor and FunctionContractInjector to support current file context for improved type evaluation * Refactor internal command classes to add @internal annotations and enhance configuration for statically proven checks * Fix phpstan errors * improve aritmentic evaluation handling for statically provable types * fix autoloading deadlocks in compile time optimization for objects * Enhance FunctionContractInjector to tag preceding assignments and improve return type evaluation in various control structures * Refactor error handling in ContractVisitor to streamline type parsing logic * Update cache version prefix to v0.12.0 * Implement generator evaluation and elimination checks * fix code styling * Implement type deduction and reference assignment checks in ContractVisitor, ScopeManager, and TypeDeducer; add comprehensive tests for type propagation and validation in foreach and reference assignments. * Implement property dimension assignment checks and validation; add tests for member assignments and type enforcement * Enhance MemberAssignChecker with caching improvements and reset functionality; update Config to reset MemberAssignChecker cache * Add PropertyRefTargetFixture and SameFilePropertyRefFixture with tests for reference assignment behavior * Enhance type inference and scope management by adding inferred variable type handling in ScopeManager and ContractVisitor; implement tests for inferred variable type registration and resolution. * Refactor ContractVisitor to improve type handling and reduce code duplication; add methods for parameter type registration and member chain type deduction. * Refactor FunctionContractInjector and TemplateManager for improved logic; enhance CloneGenericInstanceTest with new test cases for generic template enforcement and return injection checks. * Fix formatting in FunctionContractInjector by adding spaces and new lines for improved readability * Enhance StreamWrapper by adding registration and unregistration around source transformation for improved resource management * fix code styling * Enhance DocblockExtractor to merge bounds and defaults for template tags; add test for handling split variance and bounds in PhpstanAndPsalmTagPriorityTest. * Enhance FunctionContractInjector and TypePHPPrinter for improved handling of single-line empty methods; update StreamWrapper to preserve line numbers and refactor tests for better coverage of edge cases. * Refactor StreamWrapper to simplify line number preservation logic and enhance test coverage for constructor and method line number accuracy * Refactor FunctionContractInjector and TypePHPPrinter for improved handling of empty method bodies; update StreamWrapper to enhance line number preservation and add comprehensive tests for multi-line parameter scenarios. * Add reset method to GenericValidator and enhance int-mask tests for enum handling * Fix phpstan errors and wndows specific failing test * Remove roave/security-advisories from require-dev in composer.json * Improve test coverage
1 parent 0e59adc commit 83f1fa3

73 files changed

Lines changed: 7560 additions & 424 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.gitignore‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ index.php
77
benchmark.php
88
.php-cs-fixer.cache
99
/app
10-
/coverage-report
10+
/coverage-report
11+
/coverage

‎composer.json‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030
"nikic/php-parser": "^5.3"
3131
},
3232
"require-dev": {
33-
"roave/security-advisories": "dev-latest",
3433
"laravel/pint": "^1.10",
3534
"pestphp/pest": "^2.0 || ^3.0 || ^4.0",
3635
"phpstan/phpstan": "^2.1",

‎src/Internal/Ast/ContractVisitor.php‎

Lines changed: 541 additions & 183 deletions
Large diffs are not rendered by default.
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypePHP\Internal\Ast\Elimination;
6+
7+
use PhpParser\Node\Expr\ArrowFunction;
8+
use PhpParser\Node\Expr\Closure;
9+
use PhpParser\Node\Stmt\ClassMethod;
10+
use PhpParser\Node\Stmt\Function_;
11+
use PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode;
12+
use PHPStan\PhpDocParser\Ast\Type\GenericTypeNode;
13+
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
14+
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
15+
16+
/**
17+
* Enforces LSP safety rules and runtime state invariants to determine
18+
* whether a check can be safely eliminated during AST transformation.
19+
*
20+
* @internal
21+
*/
22+
final class EliminationSafetyPolicy
23+
{
24+
/**
25+
* O(1) hash map of generic containers that carry no object state or WeakMap template bindings.
26+
*
27+
* @var array<string, true>
28+
*/
29+
private const PERMISSIBLE_NON_OBJECT_GENERICS = [
30+
'int' => true,
31+
'integer' => true,
32+
'int-mask' => true,
33+
'int-mask-of' => true,
34+
'value-of' => true,
35+
'list' => true,
36+
'non-empty-list' => true,
37+
'array' => true,
38+
'non-empty-array' => true,
39+
'iterable' => true,
40+
];
41+
42+
/**
43+
* O(1) hash map of permissible generator return type containers.
44+
*
45+
* @var array<string, true>
46+
*/
47+
private const PERMISSIBLE_GENERATOR_GENERICS = [
48+
'generator' => true,
49+
'iterable' => true,
50+
'traversable' => true,
51+
'iterator' => true,
52+
];
53+
54+
/**
55+
* Determines whether a return check can be safely eliminated under LSP rules.
56+
*
57+
* Flow of logic:
58+
* 1. Returns false if no return type is declared.
59+
* 2. Rejects generic object classes (e.g. Collection<Dog>) to ensure runtime
60+
* WeakMap template bindings are preserved. Permits array containers (list<T>, array<K, V>)
61+
* and scalar generics (int<min, max>, int-mask) which carry no object state.
62+
* 3. Authorizes standalone functions, closures, and arrow functions (zero inheritance risk).
63+
* 4. For class methods:
64+
* - Authorizes private methods (cannot override or be overridden).
65+
* - Authorizes classes with no inheritance (`hasRealInheritance === false`).
66+
* 5. Defers all other methods to runtime to prevent bypassing unanalyzed parent contracts.
67+
*
68+
* @param array{name?: ?string, hasInheritance?: bool, hasRealInheritance?: bool, hasPropertyWithDoc?: bool, isReadonly?: bool, isFinal?: bool, hasTemplates?: bool}|null $classContext
69+
*/
70+
public static function isReturnEliminationSafe(
71+
ClassMethod|Function_|Closure|ArrowFunction $functionNode,
72+
?array $classContext,
73+
?TypeNode $returnTypeNode
74+
): bool {
75+
if ($returnTypeNode === null) {
76+
return false;
77+
}
78+
79+
if ($returnTypeNode instanceof GenericTypeNode) {
80+
$baseName = strtolower($returnTypeNode->type->name);
81+
if (! isset(self::PERMISSIBLE_NON_OBJECT_GENERICS[$baseName])) {
82+
return false;
83+
}
84+
}
85+
86+
if ($classContext === null || $functionNode instanceof Function_ || $functionNode instanceof Closure || $functionNode instanceof ArrowFunction) {
87+
return true;
88+
}
89+
90+
if ($functionNode->isPrivate()) {
91+
return true;
92+
}
93+
94+
$hasRealInheritance = $classContext['hasRealInheritance'] ?? true;
95+
if (! $hasRealInheritance) {
96+
return true;
97+
}
98+
99+
return false;
100+
}
101+
102+
/**
103+
* Determines whether generator yield, send, and return checks can be safely eliminated under LSP rules.
104+
*
105+
* @param array{name?: ?string, hasInheritance?: bool, hasRealInheritance?: bool, hasPropertyWithDoc?: bool, isReadonly?: bool, isFinal?: bool, hasTemplates?: bool}|null $classContext
106+
*/
107+
public static function isGeneratorEliminationSafe(
108+
ClassMethod|Function_|Closure|ArrowFunction $functionNode,
109+
?array $classContext,
110+
?TypeNode $returnTypeNode
111+
): bool {
112+
if ($returnTypeNode === null) {
113+
return false;
114+
}
115+
116+
if ($returnTypeNode instanceof GenericTypeNode) {
117+
$baseName = strtolower(ltrim($returnTypeNode->type->name, '\\'));
118+
if (! isset(self::PERMISSIBLE_GENERATOR_GENERICS[$baseName])) {
119+
return false;
120+
}
121+
} elseif ($returnTypeNode instanceof IdentifierTypeNode) {
122+
$baseName = strtolower(ltrim($returnTypeNode->name, '\\'));
123+
if (! isset(self::PERMISSIBLE_GENERATOR_GENERICS[$baseName])) {
124+
return false;
125+
}
126+
} elseif (! ($returnTypeNode instanceof ArrayTypeNode)) {
127+
return false;
128+
}
129+
130+
if ($classContext === null || $functionNode instanceof Function_ || $functionNode instanceof Closure || $functionNode instanceof ArrowFunction) {
131+
return true;
132+
}
133+
134+
if ($classContext['hasTemplates'] ?? false) {
135+
return false;
136+
}
137+
138+
if ($functionNode->isPrivate()) {
139+
return true;
140+
}
141+
142+
$hasRealInheritance = $classContext['hasRealInheritance'] ?? true;
143+
if (! $hasRealInheritance) {
144+
return true;
145+
}
146+
147+
return false;
148+
}
149+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace TypePHP\Internal\Ast\Elimination\Evaluator;
6+
7+
use PhpParser\Node\Expr;
8+
use PhpParser\Node\Expr\BinaryOp;
9+
use PhpParser\Node\Expr\UnaryMinus;
10+
use PhpParser\Node\Expr\UnaryPlus;
11+
use PhpParser\Node\Scalar\DNumber;
12+
use PhpParser\Node\Scalar\LNumber;
13+
14+
/**
15+
* Statically folds deterministic binary and unary arithmetic expressions at compile time,
16+
* handling IEEE 754 floating-point calculations with epsilon tolerance.
17+
*
18+
* @internal
19+
*/
20+
final class ArithmeticEvaluator
21+
{
22+
/**
23+
* Standard IEEE 754 comparison tolerance matching runtime ConstValidator.
24+
*/
25+
public const FLOAT_EPSILON = 1e-9;
26+
27+
/**
28+
* Recursively folds an arithmetic expression into a deterministic int or float.
29+
*
30+
* Flow of logic:
31+
* 1. Resolves integer and float scalar literal values.
32+
* 2. Evaluates unary signs (+ and -) on folded sub-expressions.
33+
* 3. Recursively evaluates left and right operands of binary arithmetic operations.
34+
* 4. Guards against division and modulo by zero, aborting on invalid arithmetic.
35+
* 5. Computes addition, subtraction, multiplication, division, modulo, and exponentiation.
36+
* 6. Verifies that the resulting number is finite (not NAN or INF).
37+
*/
38+
public static function evaluate(Expr $expr): int|float|null
39+
{
40+
if ($expr instanceof LNumber) {
41+
return $expr->value;
42+
}
43+
44+
if ($expr instanceof DNumber) {
45+
return $expr->value;
46+
}
47+
48+
if ($expr instanceof UnaryMinus) {
49+
$inner = self::evaluate($expr->expr);
50+
51+
return $inner !== null ? -$inner : null;
52+
}
53+
54+
if ($expr instanceof UnaryPlus) {
55+
return self::evaluate($expr->expr);
56+
}
57+
58+
if ($expr instanceof BinaryOp) {
59+
return self::evaluateBinaryOp($expr);
60+
}
61+
62+
return null;
63+
}
64+
65+
/**
66+
* Computes the result of a binary arithmetic operation on folded operands.
67+
*/
68+
private static function evaluateBinaryOp(BinaryOp $expr): int|float|null
69+
{
70+
$left = self::evaluate($expr->left);
71+
$right = self::evaluate($expr->right);
72+
73+
if ($left === null || $right === null) {
74+
return null;
75+
}
76+
77+
$result = match ($expr::class) {
78+
BinaryOp\Plus::class => $left + $right,
79+
BinaryOp\Minus::class => $left - $right,
80+
BinaryOp\Mul::class => $left * $right,
81+
BinaryOp\Div::class => self::divide($left, $right),
82+
BinaryOp\Mod::class => self::modulo($left, $right),
83+
BinaryOp\Pow::class => $left ** $right,
84+
default => null,
85+
};
86+
87+
if ($result === null || ! is_finite((float) $result)) {
88+
return null;
89+
}
90+
91+
return $result;
92+
}
93+
94+
/**
95+
* Safely executes division, guarding against division by zero.
96+
*/
97+
private static function divide(int|float $left, int|float $right): int|float|null
98+
{
99+
if (abs((float) $right) < self::FLOAT_EPSILON) {
100+
return null;
101+
}
102+
103+
return $left / $right;
104+
}
105+
106+
/**
107+
* Safely executes integer modulo arithmetic, guarding against modulo by zero.
108+
*/
109+
private static function modulo(int|float $left, int|float $right): int|null
110+
{
111+
$intRight = (int) $right;
112+
if ($intRight === 0) {
113+
return null;
114+
}
115+
116+
return (int) $left % $intRight;
117+
}
118+
}

0 commit comments

Comments
 (0)