From c6c04d7e961a3250048fc88f275bc8e5bff6f8d1 Mon Sep 17 00:00:00 2001 From: rango Date: Tue, 4 Aug 2026 22:32:44 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=AF=BB=E5=8F=96=E5=8A=A8?= =?UTF-8?q?=E6=80=81=E5=B1=9E=E6=80=A7=E5=A4=9A=E8=B0=83=E7=94=A8=E4=B8=80?= =?UTF-8?q?=E6=AC=A1=20=5F=5Fisset=20=E9=AD=94=E6=9C=AF=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Generator/Utils.php | 5 ++ src/Optimizer/FuncCallOptimizer.php | 2 +- src/Optimizer/SsaPropOptimizer.php | 4 +- src/Parser/NullsafeAccessTrait.php | 2 +- src/Parser/PropertyAccessTrait.php | 8 ++-- .../magic_methods/read-skips-isset.phpt | 47 +++++++++++++++++++ 6 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 tests/compiler/magic_methods/read-skips-isset.phpt diff --git a/src/Generator/Utils.php b/src/Generator/Utils.php index 1b14f62d..9327324b 100644 --- a/src/Generator/Utils.php +++ b/src/Generator/Utils.php @@ -75,6 +75,11 @@ trait Utils return $bool ? 'true' : 'false'; } + protected function escapeAttrMode(bool $update): string + { + return $update ? 'php::AttrMode::Update' : 'php::AttrMode::Get'; + } + protected function escapeVarName(string $name): string { if ($name === 'this') { diff --git a/src/Optimizer/FuncCallOptimizer.php b/src/Optimizer/FuncCallOptimizer.php index 2d08684b..c347e319 100644 --- a/src/Optimizer/FuncCallOptimizer.php +++ b/src/Optimizer/FuncCallOptimizer.php @@ -371,7 +371,7 @@ trait FuncCallOptimizer if ($this->isPropertyFetch($arg) and $this->isVarExpr($arg->var)) { $obj = $this->parseIdentifier($arg->var); $tmpRef = $this->genTmpVarName(); - $this->context->beforeStmtLines[] = 'auto&& ' . $tmpRef . ' = ' . $obj . '.attr(' . $this->identifierToStr($arg->name) . ', true);'; + $this->context->beforeStmtLines[] = 'auto&& ' . $tmpRef . ' = ' . $obj . '.attr(' . $this->identifierToStr($arg->name) . ', php::AttrMode::Update);'; return $tmpRef; } return $this->getArg($expr, $i); diff --git a/src/Optimizer/SsaPropOptimizer.php b/src/Optimizer/SsaPropOptimizer.php index 0e7a5155..ec9fdfde 100644 --- a/src/Optimizer/SsaPropOptimizer.php +++ b/src/Optimizer/SsaPropOptimizer.php @@ -823,7 +823,7 @@ trait SsaPropOptimizer } if ($this->context->inLoop || $this->context->scopeLevel > 1) { - $refGetter = $objName . '.attr(' . $id . ', true)'; + $refGetter = $objName . '.attr(' . $id . ', php::AttrMode::Update)'; $zvalMacro = $this->getZvalValueMacroForPropType($cType); if ($zvalMacro !== null) { return $zvalMacro . '(' . $refGetter . '.unwrap_ptr())'; @@ -831,7 +831,7 @@ trait SsaPropOptimizer return $refGetter; } - $refGetter = $objName . '.attr(' . $id . ', true)'; + $refGetter = $objName . '.attr(' . $id . ', php::AttrMode::Update)'; $zvalMacro = $this->getZvalValueMacroForPropType($cType); if ($zvalMacro !== null) { $this->context->beforeStmtLines[] = $cType . ' &' . $propVar . ' = ' . $zvalMacro . '(' . $refGetter . '.unwrap_ptr());'; diff --git a/src/Parser/NullsafeAccessTrait.php b/src/Parser/NullsafeAccessTrait.php index 3c9d287a..2c600433 100644 --- a/src/Parser/NullsafeAccessTrait.php +++ b/src/Parser/NullsafeAccessTrait.php @@ -84,7 +84,7 @@ trait NullsafeAccessTrait $code .= "if ({$object}.isNull()) { return " . self::VALUE_NULL . '; }'; } if ($item[0] == 'property') { - $update = $this->escapeBool($this->isPropertyFetchUpdate($item[2])); + $update = $this->escapeAttrMode($this->isPropertyFetchUpdate($item[2])); $code .= $this->getIndent() . "{$tmpVar} = {$object}.attr({$item[1]}, {$update});"; } else { $beforeStmtCount = count($this->context->beforeStmtLines); diff --git a/src/Parser/PropertyAccessTrait.php b/src/Parser/PropertyAccessTrait.php index 51eec046..f3d40de7 100644 --- a/src/Parser/PropertyAccessTrait.php +++ b/src/Parser/PropertyAccessTrait.php @@ -163,12 +163,12 @@ trait PropertyAccessTrait protected function emitDynamicPropertyAppendArray(string $object, string $property, string $value): string { - return "{$object}.attr({$property}, true).newItem() = {$value}"; + return "{$object}.attr({$property}, php::AttrMode::Update).newItem() = {$value}"; } protected function emitDynamicPropertyUpdateArray(string $object, string $property, string $dim, string $value): string { - return "{$object}.attr({$property}, true).item({$dim}, true) = {$value}"; + return "{$object}.attr({$property}, php::AttrMode::Update).item({$dim}, true) = {$value}"; } protected function assertDynamicPropertyTarget(PropertyWriteTarget $target): void @@ -729,7 +729,7 @@ trait PropertyAccessTrait if ($this->hasObjectPropVar($propVar)) { $lines[] = $propVar . ' = ' . $restoreDefault . ';'; } else { - $lines[] = $object . '.attr(' . $propertyId . ', true) = ' . $restoreDefault . ';'; + $lines[] = $object . '.attr(' . $propertyId . ', php::AttrMode::Update) = ' . $restoreDefault . ';'; } } } @@ -839,7 +839,7 @@ trait PropertyAccessTrait $this->errorUndefinedVariable($object); } $objectVar = $objectName; - $getProperty = $objectVar . '.attr(' . $id . ', ' . $this->escapeBool($update) . ')'; + $getProperty = $objectVar . '.attr(' . $id . ', ' . $this->escapeAttrMode($update) . ')'; $def = $this->getNativePropertyDef($expr); if ($def and $this->nativeTypes) { $propName = $this->parseIdentifier($property); diff --git a/tests/compiler/magic_methods/read-skips-isset.phpt b/tests/compiler/magic_methods/read-skips-isset.phpt new file mode 100644 index 00000000..291f5b3e --- /dev/null +++ b/tests/compiler/magic_methods/read-skips-isset.phpt @@ -0,0 +1,47 @@ +--TEST-- +Magic Methods - a plain property read invokes __get, never __isset +--FILE-- + 'TypePHP']; + + public function __get(string $name) + { + echo "__get($name)\n"; + return $this->data[$name] ?? null; + } + + public function __isset(string $name) + { + echo "__isset($name)\n"; + return isset($this->data[$name]); + } +} + +function main(): void +{ + $obj = new MagicReadTest(); + + echo "--- plain read ---\n"; + var_dump($obj->aaa); + + echo "--- isset ---\n"; + var_dump(isset($obj->aaa)); + + echo "--- empty ---\n"; + var_dump(empty($obj->aaa)); +} +?> +--EXPECTF-- +--- plain read --- +__get(aaa) +NULL +--- isset --- +__isset(aaa) +bool(false) +--- empty --- +__isset(aaa) +bool(true)