diff --git a/composer.json b/composer.json index 7bc464a6..b78a74c2 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ "marcj/topsort": "^2.0", "symfony/var-dumper": "^8.0", "symfony/yaml": "^8.0", - "swoole/phpx": "~2.7.0", + "swoole/phpx": "~2.8.0", "ajaxray/ansikit": "^0.3", "ext-dom": "*" }, diff --git a/examples/objective-c-macos/objc-src/macos_bridge.mm b/examples/objective-c-macos/objc-src/macos_bridge.mm index 5ef4e1b8..7a226b33 100644 --- a/examples/objective-c-macos/objc-src/macos_bridge.mm +++ b/examples/objective-c-macos/objc-src/macos_bridge.mm @@ -147,7 +147,10 @@ php::Int php_ui_add_button( static_cast(y), static_cast(width), static_cast(height)); - button.bezelStyle = NSBezelStyleRounded; + // macOS 11+: keep the automatic button style. Forcing the legacy + // NSBezelStyleRounded disables the accent fill that the primary button + // needs behind its white title. The automatic style also makes + // bezelColor render as a proper filled button. button.controlSize = NSControlSizeLarge; button.font = [NSFont systemFontOfSize:14 weight:NSFontWeightSemibold]; if (style == 1) { @@ -165,8 +168,17 @@ void php_ui_set_control_text(php::Int controlId, php::Str text) { @autoreleasepool { NSControl *control = bridge.controls[@(static_cast(controlId))]; - if (control != nil) { - control.stringValue = to_ns_string(text); + if (control == nil) { + return; + } + NSString *value = to_ns_string(text); + if ([control isKindOfClass:NSButton.class]) { + // Update the title through the NSButton property. System-style + // buttons (macOS 11+) draw from their attributed title; assigning + // only the raw cell stringValue can leave the button blank. + ((NSButton *)control).title = value; + } else { + control.stringValue = value; } } } diff --git a/src/Parser/PropertyAccessTrait.php b/src/Parser/PropertyAccessTrait.php index d67d323e..d5c9c617 100644 --- a/src/Parser/PropertyAccessTrait.php +++ b/src/Parser/PropertyAccessTrait.php @@ -1090,19 +1090,23 @@ trait PropertyAccessTrait if ($this->isNativeObjectVar($name)) { $this->forgetNativeObjectNonNull($name); $lines[] = "{$name} = nullptr;"; - } elseif ($this->isNativeType($type)) { - $this->warning($var, "Variable of native type `\${$name}` cannot be unset"); + } elseif ($type === Type::STR || $type === Type::ARRAY) { + // String and Array reset to Zend's immutable empty + // singletons. This releases the previous value without + // allocating a replacement that will usually stay empty. + $lines[] = "{$name}.unset();"; + } elseif (($defaultValue = Type::getDefaultValueExpression($type)) !== null) { + // A fixed value type must preserve its storage invariant. + // unset() releases the previous value and restores the + // type's initial state instead of introducing UNDEF. + $lines[] = "{$name} = {$defaultValue};"; } elseif ($type === Type::OBJECT) { - // A PHP local read after unset() evaluates to null (and may - // emit an undefined-variable warning). Keep the Object - // wrapper so later object assignments remain valid, but - // store NULL rather than IS_UNDEF so strict null checks - // retain PHP value semantics. - // - // Keep the declared class: unset() changes only the value - // state and does not make null or another class assignable. + // Objects have no empty object value. Null is their valid + // initial state; keep the declared class constraint so a + // later assignment must still contain a compatible object. $lines[] = "{$name} = php::null;"; } else { + // Dynamic storage retains PHP's true undefined state. $lines[] = "{$name}.unset();"; } } else { diff --git a/src/Resolver/PropertyAssignTypeInfo.php b/src/Resolver/PropertyAssignTypeInfo.php index 2de9d879..38f55892 100644 --- a/src/Resolver/PropertyAssignTypeInfo.php +++ b/src/Resolver/PropertyAssignTypeInfo.php @@ -16,14 +16,11 @@ final class PropertyAssignTypeInfo { public function getFixedDefaultValue(PropertyDef $def): ?string { - return match ($def->type) { - Type::INT => $def->default ?? '0', - Type::FLOAT => $def->default ?? '0.0', - Type::BOOL => $def->default ?? 'false', - Type::STR => $def->default ?? Type::STR . '()', - Type::ARRAY => $def->default ?? Type::ARRAY . '{}', - default => null, - }; + $typeDefault = Type::getDefaultValueExpression($def->type); + if ($typeDefault === null) { + return null; + } + return $def->default ?? $typeDefault; } public function isFixed(PropertyDef $def): bool diff --git a/src/Type.php b/src/Type.php index 6b706e9c..da38e6c8 100644 --- a/src/Type.php +++ b/src/Type.php @@ -70,4 +70,23 @@ final class Type default => $type, }; } + + /** + * Return the C++ expression for the initial state of a fixed value type. + * + * Fixed TypePHP storage never becomes UNDEF: unset() releases the current + * value and restores this state instead. Objects are handled separately + * because null is their valid empty state rather than a value-type default. + */ + public static function getDefaultValueExpression(string $type): ?string + { + return match ($type) { + self::INT => '0', + self::FLOAT => '0.0', + self::BOOL => 'false', + self::STR => self::STR . '()', + self::ARRAY => self::ARRAY . '{}', + default => null, + }; + } }