Skip to content

fix(compiler): apply unary plus numeric conversion - #132

Merged
matyhtf merged 4 commits into
swoole:masterfrom
yavon007:codex/fix-unary-plus-conversion
Sep 22, 2026
Merged

matyhtf merged 4 commits into
swoole:masterfrom
yavon007:codex/fix-unary-plus-conversion

Conversation

@yavon007

@yavon007 yavon007 commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Unary plus currently returns its operand unchanged, so numeric strings and booleans keep the wrong value type:

 $text = '42'; $decimal = '1.5'; $flag = true;
 var_dump(+$text, +$decimal, +$flag);
- string(2) "42"
- string(3) "1.5"
- bool(true)
+ int(42)
+ float(1.5)
+ int(1)

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

  • Before: the minimal PHPT produces strings/boolean instead of the expected numeric results.
    After: regression coverage includes numeric strings, booleans, null, typed returns, assignments, invalid operands, conversion warnings, references, signed zero, and an operand evaluated exactly once.
  • Additional PHPT covers typed property/parameter defaults and class constants, including safe generation of the minimum integer literal and nullable bool/int/float properties. Nullable numeric properties use the conversion path because their runtime value can be null.
  • A code-generation test confirms known int/float operands do not introduce Variant boxing.
  • Typed-property assignment regression: +($box->number = 2) with a float property produced int(2) before this correction and now produces float(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.
  • Fresh local assignments infer their initial type from the RHS before registration; a dedicated PHPT preserves BigInt, BigFloat, Decimal, int, and float unary-plus values. Existing variables (including names escaped for C++) and typed properties continue to use the assignment target type.
  • Latest related PHPT run: 78 passed; one existing BigFloat formatting mismatch (3.14 versus 3.1400000000000001) also reproduces on unmodified upstream in this environment.
  • Full PHPUnit on Linux ARM64 / PHP 8.5.10 ZTS with phpy: 2,336 tests, 6,794 assertions, no failures; 67 existing warnings, 36 deprecations, one skip.
  • git diff --check passes. 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.

@matyhtf matyhtf left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@yavon007
yavon007 requested a review from matyhtf September 22, 2026 10:18
matyhtf

This comment was marked as outdated.

@matyhtf
matyhtf merged commit c54b590 into swoole:master Sep 22, 2026
14 checks passed

@matyhtf matyhtf left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants