fix(compiler): apply unary plus numeric conversion - #132
Conversation
matyhtf
left a comment
There was a problem hiding this comment.
There is one correctness issue in unaryPlusOperandType().
The helper unwraps every Expr\\Assign and classifies the unary-plus operand from the RHS. However, the value of an assignment expression may be converted to the target type. For example:
class Box {
public float $number = 0.0;
}
$box = new Box();
var_dump(+($box->number = 2));PHP 8.4 prints:
float(2)
With this PR, the AOT binary prints:
int(2)
The generated code confirms the incorrect inference:
tmp_var_1 = php::toInt(_object_prop_box__number = php::toFloat(2L));The RHS is an int, but the assignment expression evaluates to the converted float property value. Because unaryPlusOperandType() reports int, the compiler selects the native fast path and later converts the result back to int.
Please infer an assignment expression from its resulting target type instead of unconditionally recursing into its RHS, and add a regression test for unary plus applied to a typed-property assignment.
matyhtf
left a comment
There was a problem hiding this comment.
Approved. I am retracting my latest change request: the array-element and dynamic-property Big-number example depends on recovering a precise native Big type from a generic PHP container, which TypePHP does not support. It is therefore outside this PR's supported type model.
The original typed-property assignment issue is fixed, fresh-local inference is preserved, the added regression tests cover both paths, and all CI checks are green.
Summary
Unary plus currently returns its operand unchanged, so numeric strings and booleans keep the wrong value type:
Keep known int, float, and arbitrary-precision operands on their existing path. Convert booleans directly to int; lower other operands through Variant multiplication by one, matching Zend's unary-plus lowering and preserving conversion diagnostics and single evaluation. Infer the converted result type for assignments and returns. When unary plus wraps an assignment, use its target type after assignment conversion (including nullable instance/static properties), rather than the unconverted RHS type.
Fold numeric string and language-constant literals without runtime boxing. Let property-default validation use the existing constant evaluator for unary plus instead of inheriting the operand's type.
Evidence
After: regression coverage includes numeric strings, booleans, null, typed returns, assignments, invalid operands, conversion warnings, references, signed zero, and an operand evaluated exactly once.
+($box->number = 2)with a float property producedint(2)before this correction and now producesfloat(2). Covers instance/static properties, nullable values, error suppression, local assignment, and a side-effecting RHS evaluated once. The PHPT passes both AOT and native PHP.3.14versus3.1400000000000001) also reproduces on unmodified upstream in this environment.git diff --checkpasses. Other PHP versions and platforms are left to CI.Merge Danger
Door: Two-way
Blast Radius: Unary-plus
No change to static integer overflow semantics. Known int/float/Big-number unary-plus paths retain their existing generated operand expression. Dynamic handling is used only where the operand still needs numeric conversion; Python-specific and native-object operator dispatch remain in place.