|
|
|
|
@ -138,6 +138,16 @@ class CompilerBase implements PropertyAccessContext |
|
|
|
|
use LoopVarOptimizer; |
|
|
|
|
use SsaPropOptimizer; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Variables (and parameters) within the currently compiled function that must |
|
|
|
|
* be emitted as a runtime variant (php::Var). A variable is recorded here when |
|
|
|
|
* it is assigned incompatible types at different points in the body — which is |
|
|
|
|
* perfectly legal in dynamically typed PHP but would otherwise force a strict |
|
|
|
|
* backend to either fatal ("Cannot re-assign") or silently miscompile. The set |
|
|
|
|
* is populated by computeVariantVars() right before the function body is parsed. |
|
|
|
|
*/ |
|
|
|
|
protected array $variantVars = []; |
|
|
|
|
|
|
|
|
|
public const string DEFAULT_PHP_VERSION = '8.5'; |
|
|
|
|
protected const string NATIVE_PROPERTY_VALUE_VAR = 'var'; |
|
|
|
|
protected const string NATIVE_PROPERTY_VALUE_DYNAMIC = 'dynamic'; |
|
|
|
|
@ -999,6 +1009,7 @@ class CompilerBase implements PropertyAccessContext |
|
|
|
|
$this->context = new FunctionContext(); |
|
|
|
|
$this->function = ''; |
|
|
|
|
$this->functionDef = null; |
|
|
|
|
$this->variantVars = []; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
protected function resetMethod(): void |
|
|
|
|
@ -3946,6 +3957,33 @@ class CompilerBase implements PropertyAccessContext |
|
|
|
|
$this->fatalError($left, "Cannot re-assign $varName from `{$fromType}` to `{$toType}`"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Mirror of checkVarAssignExpr()'s compatibility rules without the fatal side |
|
|
|
|
* effect. Returns true when a variable of type $existing may legally receive a |
|
|
|
|
* value of type $new (used by computeVariantVars() to decide whether a variable |
|
|
|
|
* must be promoted to a runtime variant). |
|
|
|
|
*/ |
|
|
|
|
protected function areTypesAssignable(string $existing, string $new): bool |
|
|
|
|
{ |
|
|
|
|
if ($existing === Type::VAR or $new === Type::VAR) { |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
if ($existing === Type::REF or $new === Type::REF) { |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
if ($existing === $new) { |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
if ($this->isNativeType($existing) and $this->isNativeType($new)) { |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
$bigTypes = [Type::BIGINT, Type::DECIMAL, Type::BIGFLOAT]; |
|
|
|
|
if (in_array($existing, $bigTypes, true) or in_array($new, $bigTypes, true)) { |
|
|
|
|
return true; |
|
|
|
|
} |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Check a value against a composite PHP type when the value's static type |
|
|
|
|
* is precise enough to prove a mismatch. Composite declarations still use |
|
|
|
|
|