Native Class 支持 [第六阶段]

master
韩天峰 1 week ago
parent 13344ace72
commit 7d1a90a340
  1. 4
      docs/NATIVE_CLASS_OBJECT.md
  2. 8
      phpunit/code/native-class-anonymous.php
  3. 13
      phpunit/code/native-class-private-method-access.php
  4. 13
      phpunit/code/native-class-protected-method-access.php
  5. 10
      phpunit/code/native-class-serialize.php
  6. 10
      phpunit/code/native-class-static-signature.php
  7. 15
      phpunit/code/native-class-trait-static-signature.php
  8. 10
      phpunit/code/native-class-weak-reference.php
  9. 15
      phpunit/src/GenStubVersionFlagsTest.php
  10. 53
      phpunit/src/NativeClass/NativeClassValidationTest.php
  11. 5
      src/CompilerBase.php
  12. 41
      src/NativeClass/NativeClassSupportTrait.php
  13. 39
      src/Parser/AssignOpTrait.php
  14. 3
      src/Parser/BinaryOpTrait.php
  15. 11
      src/gen_stub.php
  16. 62
      tests/compiler/native-class/constructor-argument-roots.phpt
  17. 150
      tests/compiler/native-class/failed-lifecycle-escape.phpt
  18. 50
      tests/compiler/native-class/high-precision-properties.phpt
  19. 18
      tests/compiler/native-class/match-identity.phpt
  20. 56
      tests/compiler/native-class/method-visibility.phpt
  21. 68
      tests/compiler/native-class/property-hook-native-object.phpt
  22. 62
      tests/compiler/native-class/self-parent-signatures.phpt
  23. 31
      tests/compiler/native-class/strict-identity.phpt
  24. 54
      tests/compiler/native-class/this-value.phpt

@ -858,7 +858,9 @@ Box 不能保存 Native Object。Std Container 不能作为 Native Class 属性
`NativeClass::class` 作为 value type,并保存该类或其 Native 子类。普通 PHP array
仍然不能保存 Native Object。
Native 元素 Std Container 必须是函数顶层的局部变量。编译器为该局部容器生成与其
TypePHP 当前的 Std Container 本身就只允许作为函数内的局部变量,不允许作为
global/static,因此不存在需要为 Native 元素另外设计的长期容器所有权。Native 元素
Std Container 进一步要求它是函数顶层的局部变量。编译器为该局部容器生成与其
词法生命周期一致的 `NativeContainerRootFrame`;因此它不能保存到 global/static、
Zend 或 Native 属性、PHP array,也不能被返回、取引用、捕获进 Closure/arrow
function,或通过 `toArray()`/`toAny()` 等方式转换。上述行为都会让保存裸指针的

@ -0,0 +1,8 @@
<?php
function native_anonymous_class(): void
{
$value = new #[Native] class {
public int $value = 1;
};
}

@ -0,0 +1,13 @@
<?php
#[Native]
class NativePrivateMethodAccess
{
private function hidden(): void {}
}
function native_private_method_access(): void
{
$value = new NativePrivateMethodAccess();
$value->hidden();
}

@ -0,0 +1,13 @@
<?php
#[Native]
class NativeProtectedMethodAccess
{
protected function hidden(): void {}
}
function native_protected_method_access(): void
{
$value = new NativeProtectedMethodAccess();
$value->hidden();
}

@ -0,0 +1,10 @@
<?php
#[Native]
class NativeSerializeBoundary {}
function native_serialize_boundary(): void
{
$value = new NativeSerializeBoundary();
serialize($value);
}

@ -0,0 +1,10 @@
<?php
#[Native]
class NativeStaticSignature
{
public function identity(): static
{
return $this;
}
}

@ -0,0 +1,15 @@
<?php
trait NativeTraitStaticSignatureProvider
{
public function identity(): static
{
return $this;
}
}
#[Native]
class NativeTraitStaticSignature
{
use NativeTraitStaticSignatureProvider;
}

@ -0,0 +1,10 @@
<?php
#[Native]
class NativeWeakReferenceBoundary {}
function native_weak_reference_boundary(): void
{
$value = new NativeWeakReferenceBoundary();
WeakReference::create($value);
}

@ -0,0 +1,15 @@
<?php
final class GenStubVersionFlagsTest extends BaseTest
{
public function testFlagIntroducedBeforeMinimumSupportedVersionRemainsEnabled(): void
{
$flags = new VersionFlags(['ZEND_ACC_PRIVATE']);
$flags->addForVersionsAbove('ZEND_ACC_STATIC', PHP_70_VERSION_ID);
self::assertSame(
'ZEND_ACC_PRIVATE|ZEND_ACC_STATIC',
$flags->generateVersionDependentFlagCode('%s', null),
);
}
}

@ -46,6 +46,13 @@ final class NativeClassValidationTest extends \BaseTest
$this->compile('native-class-attribute-enum.php');
}
public function testRejectsNativeAttributeOnAnonymousClass(): void
{
$this->expectException(\TypePhp\Exception\SyntaxError::class);
$this->expectExceptionMessage('Native can only be applied to named classes');
$this->compile('native-class-anonymous.php');
}
public function testRejectsUntypedProperty(): void
{
$this->expectException(TestError::class);
@ -435,6 +442,24 @@ final class NativeClassValidationTest extends \BaseTest
$this->compile('native-class-json-encode.php');
}
/**
* @dataProvider nativeZendObjectFacilityProvider
*/
public function testRejectsNativeObjectPassedToZendObjectFacilities(string $fixture): void
{
$this->expectException(TestError::class);
$this->expectExceptionMessage('Native objects cannot cross a dynamic PHP/ZendVM call boundary');
$this->compile($fixture);
}
public static function nativeZendObjectFacilityProvider(): array
{
return [
['native-class-serialize.php'],
['native-class-weak-reference.php'],
];
}
public function testRejectsNativeObjectFromUntypedReturn(): void
{
$this->expectException(TestError::class);
@ -760,10 +785,17 @@ final class NativeClassValidationTest extends \BaseTest
public function testRejectsLateStaticNativeMethodSignature(): void
{
$this->expectException(TestError::class);
$this->expectExceptionMessage('Native classes do not support late static binding in parameter or return types');
$this->expectExceptionMessage('Native classes do not support late static binding in return types');
$this->compile('native-class-static-signature.php');
}
public function testRejectsLateStaticSignatureInjectedIntoNativeClassByTrait(): void
{
$this->expectException(TestError::class);
$this->expectExceptionMessage('Native classes do not support late static binding in return types');
$this->compile('native-class-trait-static-signature.php');
}
public function testRejectsInaccessibleNativeClassConstant(): void
{
$this->expectException(TestError::class);
@ -771,6 +803,25 @@ final class NativeClassValidationTest extends \BaseTest
$this->compile('native-class-private-constant-access.php');
}
/**
* @dataProvider inaccessibleNativeMethodProvider
*/
public function testRejectsInaccessibleNativeMethods(string $fixture): void
{
$this->expectException(TestError::class);
$this->expectExceptionMessage('Method');
$this->expectExceptionMessage('is not accessible');
$this->compile($fixture);
}
public static function inaccessibleNativeMethodProvider(): array
{
return [
['native-class-private-method-access.php'],
['native-class-protected-method-access.php'],
];
}
public function testRejectsNativeObjectCastToZendObject(): void
{
$this->expectException(TestError::class);

@ -1170,7 +1170,8 @@ class CompilerBase implements PropertyAccessContext
protected function parseExprAsValue(NodeAbstract $expr): string
{
return $this->wrapVoidExprAsNull($expr, $this->parseExpr($expr));
$value = $this->wrapVoidExprAsNull($expr, $this->parseExpr($expr));
return $this->normalizeNativeObjectValueExpr($expr, $value);
}
/**
@ -1886,7 +1887,7 @@ class CompilerBase implements PropertyAccessContext
}
}
return $this->parseIdentifier($expr);
return $this->normalizeNativeObjectValueExpr($expr, $this->parseIdentifier($expr));
}
protected function detectClassOfExpr(NodeAbstract $expr): string

@ -556,6 +556,12 @@ trait NativeClassSupportTrait
Node\Stmt\Function_|Node\Stmt\ClassMethod $node,
FunctionDef $function,
): void {
if ($this->classDef?->nativeObject && $function->returnTypeKeyword === 'static') {
$this->fatalError(
$node,
'Native classes do not support late static binding in return types',
);
}
if ($this->isNativeObjectClass($function->returnClass) && $function->returnsByRef) {
$this->fatalError($node, 'Native objects cannot be returned by reference');
}
@ -825,6 +831,25 @@ trait NativeClassSupportTrait
return 'php::nativeDeref(' . $name . ', "' . addslashes($class) . '")';
}
/**
* Native method bodies receive `this_` by C++ reference so direct member
* access remains zero-cost. In a PHP value context, however, `$this` is an
* object handle and must therefore become the address of that reference.
* Keep this conversion out of parseIdentifier(): receiver contexts need
* the reference itself, while assignments, returns, arguments and
* comparisons need the typed pointer.
*/
protected function normalizeNativeObjectValueExpr(NodeAbstract $expr, string $value): string
{
if ($this->classDef?->nativeObject
&& $this->isVarExpr($expr)
&& $this->parseVariable($expr) === 'this_'
) {
return '&this_';
}
return $value;
}
protected function getNativeObjectMemberReceiver(string $name): string
{
return $this->getNativeObjectReceiver($name) . '.';
@ -1478,12 +1503,18 @@ trait NativeClassSupportTrait
}
if ($destructors !== []) {
$code .= 'static void ' . $prefix . '_finalize(void *object) {' . PHP_EOL;
$code .= ' php::NativeFinalizerChain chain;' . PHP_EOL;
foreach ($destructors as [$destructor, $destructorCpp]) {
$code .= ' chain.run([&] { ' . $destructor
. '(*static_cast<' . $destructorCpp . ' *>(object)); });' . PHP_EOL;
if (count($destructors) === 1) {
[$destructor, $destructorCpp] = $destructors[0];
$code .= ' ' . $destructor
. '(*static_cast<' . $destructorCpp . ' *>(object));' . PHP_EOL;
} else {
$code .= ' php::NativeFinalizerChain chain;' . PHP_EOL;
foreach ($destructors as [$destructor, $destructorCpp]) {
$code .= ' chain.run([&] { ' . $destructor
. '(*static_cast<' . $destructorCpp . ' *>(object)); });' . PHP_EOL;
}
$code .= ' chain.rethrow();' . PHP_EOL;
}
$code .= ' chain.rethrow();' . PHP_EOL;
$code .= '}' . PHP_EOL;
}
$code .= 'static void ' . $prefix . '_destroy(void *object) noexcept {' . PHP_EOL;

@ -575,15 +575,48 @@ trait AssignOpTrait
?PropertyWriteTarget $target,
string $setter,
): string {
$property = $this->getNativePropertyDef($left);
$nativeObjectClass = $property !== null
&& $property->type === Type::OBJECT
&& $this->isNativeObjectClass($property->class)
? $property->class
: '';
if ($target !== null) {
$this->assertCanAssignPropertyWrite($target, $right);
}
$rightExpr = $this->parseExprAsValue($right);
if ($target !== null) {
if ($nativeObjectClass !== '') {
if ($this->isNull($right)) {
if (!$property->nullable) {
$this->fatalError($right, "Cannot assign null to native property `{$nativeObjectClass}`");
}
$rightExpr = 'nullptr';
} else {
$rightClass = $this->detectClassOfExpr($right);
if ($rightClass === ''
|| !$this->isNativeObjectClass($rightClass)
|| !$this->isObjectClassStaticallyAssignableTo($rightClass, $nativeObjectClass)
) {
$this->fatalError($right, "Cannot assign value to native property of type `{$nativeObjectClass}`");
}
$rightExpr = $this->parseExprAsValue($right);
}
} else {
$rightExpr = $this->parseExprAsValue($right);
}
if ($target !== null && $nativeObjectClass === '') {
$rightExpr = $this->wrapPropertyWriteTypeCheck($target, $right, $rightExpr);
}
$tmp = $this->genTmpVarName();
$this->addLocalVar($tmp, Type::VAR);
if ($nativeObjectClass !== '') {
// The synthesized value variable is also the assignment result and
// the setter argument. Preserve its exact Native type so argument
// validation and NativeRootFrame generation do not see a mixed
// Zend value at this compiler-created boundary.
$this->addLocalVar($tmp, $this->getNativeObjectPointerType($nativeObjectClass));
$this->addNativeObject($tmp, $nativeObjectClass);
} else {
$this->addLocalVar($tmp, Type::VAR);
}
$call = $this->emitPropertyHookSetterCall($left, $setter, new Expr\Variable($tmp));
return '((' . $tmp . ' = ' . $rightExpr . ', ' . $call . '), ' . $tmp . ')';
}

@ -525,7 +525,8 @@ trait BinaryOpTrait
{
$this->assertExprCanBeUsedAsValue($expr, 'operand');
if (!$this->shouldMaterializeOrderedOperand($expr)) {
return $numeric ? $this->parseNumericIdentifier($expr) : $this->parseIdentifier($expr);
$value = $numeric ? $this->parseNumericIdentifier($expr) : $this->parseIdentifier($expr);
return $this->normalizeNativeObjectValueExpr($expr, $value);
}
[$value, $beforeStmts, $afterStmts] = $this->parseExprWithCapturedStmts($expr);

@ -1261,12 +1261,15 @@ class VersionFlags {
}
public function addForVersionsAbove(string $flag, int $minimumVersionId): void {
$write = false;
foreach (ALL_PHP_VERSION_IDS as $version) {
if ($version === $minimumVersionId || $write === true) {
// The supported-version table does not necessarily contain the
// PHP release that originally introduced a flag. For example,
// TypePHP currently starts at PHP 8.4, while ZEND_ACC_STATIC was
// introduced much earlier. Compare ranges instead of waiting for
// an exact table entry, otherwise established flags silently
// disappear when the minimum supported PHP version is raised.
if ($version >= $minimumVersionId) {
$this->flagsByVersion[$version][] = $flag;
$write = true;
}
}
}

@ -0,0 +1,62 @@
--TEST--
Native class: constructor arguments are evaluated left-to-right and rooted
--FILE--
<?php
#[Native]
class NativeConstructorArgument
{
public string $name;
public function __construct(string $name)
{
$this->name = $name;
}
}
#[Native]
class NativeConstructorPressure
{
public int $value;
}
#[Native]
class NativeConstructorPair
{
public NativeConstructorArgument $first;
public NativeConstructorArgument $second;
public function __construct(
NativeConstructorArgument $first,
NativeConstructorArgument $second,
) {
$this->first = $first;
$this->second = $second;
}
}
function makeNativeConstructorArgument(string $name): NativeConstructorArgument
{
echo 'make:', $name, PHP_EOL;
if ($name === 'B') {
for ($i = 0; $i < 300000; $i++) {
$filler = new NativeConstructorPressure();
}
}
return new NativeConstructorArgument($name);
}
function main(): void
{
$pair = new NativeConstructorPair(
makeNativeConstructorArgument('A'),
makeNativeConstructorArgument('B'),
);
echo $pair->first->name, ':', $pair->second->name, PHP_EOL;
}
?>
--EXPECT--
make:A
make:B
A:B

@ -0,0 +1,150 @@
--TEST--
Native class: objects published before constructor or clone failure never become dangling
--FILE--
<?php
class NativeFailedLifecycleProbe
{
public function __construct(public string $name)
{
}
public function __destruct()
{
global $released;
$released[] = $this->name;
}
}
#[Native]
class NativeFailedConstructionEscape
{
public object $probe;
public int $value = 42;
public function __construct()
{
global $failedConstruction;
$this->probe = new NativeFailedLifecycleProbe('constructor field');
$failedConstruction = $this;
throw new RuntimeException('constructor failed');
}
public function __destruct()
{
global $finalized;
$finalized[] = 'constructor object';
}
}
#[Native]
class NativeFailedCloneEscape
{
public object $probe;
public string $kind = 'source';
public function __construct()
{
$this->probe = new NativeFailedLifecycleProbe('source field');
}
public function __clone(): void
{
global $failedClone;
$this->kind = 'clone';
$this->probe = new NativeFailedLifecycleProbe('clone field');
$failedClone = $this;
throw new RuntimeException('clone failed');
}
public function __destruct()
{
global $finalized;
$finalized[] = $this->kind;
}
}
#[Native]
class NativeFailedLifecyclePressure
{
public int $value;
}
function forceNativeCollection(): void
{
for ($i = 0; $i < 400000; $i++) {
$filler = new NativeFailedLifecyclePressure();
}
}
function main(): void
{
global $failedConstruction, $failedClone, $released, $finalized;
$released = [];
$finalized = [];
try {
$unused = new NativeFailedConstructionEscape();
} catch (RuntimeException $error) {
echo $error->getMessage(), "\n";
}
var_dump($failedConstruction->value, $failedConstruction->probe->name);
$failedConstruction = null;
forceNativeCollection();
var_dump($released, $finalized);
$source = new NativeFailedCloneEscape();
try {
$unusedClone = clone $source;
} catch (RuntimeException $error) {
echo $error->getMessage(), "\n";
}
var_dump($failedClone->kind, $failedClone === $source, $failedClone->probe->name);
$failedClone = null;
forceNativeCollection();
var_dump($released, $finalized);
$source = null;
forceNativeCollection();
var_dump($released, $finalized);
}
?>
--EXPECT--
constructor failed
int(42)
string(17) "constructor field"
array(1) {
[0]=>
string(17) "constructor field"
}
array(0) {
}
clone failed
string(5) "clone"
bool(false)
string(11) "clone field"
array(2) {
[0]=>
string(17) "constructor field"
[1]=>
string(11) "clone field"
}
array(1) {
[0]=>
string(5) "clone"
}
array(3) {
[0]=>
string(17) "constructor field"
[1]=>
string(11) "clone field"
[2]=>
string(12) "source field"
}
array(2) {
[0]=>
string(5) "clone"
[1]=>
string(6) "source"
}

@ -0,0 +1,50 @@
--TEST--
Native class: BigInt, BigFloat and Decimal properties retain typed value semantics
--FILE--
<?php
#[Native]
class NativeHighPrecisionValues
{
public BigInt $integer;
public BigFloat $floating;
public Decimal $decimal;
}
#[Native]
class NativeHighPrecisionPressure
{
public int $value;
}
function createNativePressure(): void
{
for ($i = 0; $i < 300000; $i++) {
$filler = new NativeHighPrecisionPressure();
}
}
function main(): void
{
$values = new NativeHighPrecisionValues();
var_dump($values->integer, $values->floating, $values->decimal);
$values->integer = std::bigInt('123456789012345678901234567890');
$values->floating = std::bigFloat('3.141592653589793238462643383279');
$values->decimal = std::decimal('99.125');
createNativePressure();
echo ($values->integer + 10)->toString(), "\n";
echo $values->floating->toString(), "\n";
echo ($values->decimal * 2)->toString(), "\n";
}
?>
--EXPECT--
NULL
NULL
NULL
123456789012345678901234567900
3.141592653589793238462643383279
198.250

@ -27,6 +27,19 @@ function scalarMatchValue(): int
return 42;
}
function temporaryMatchSubject(): NativeMatchValue
{
return new NativeMatchValue();
}
function pressuredMatchValue(): NativeMatchValue
{
for ($i = 0; $i < 300000; $i++) {
$filler = new NativeMatchValue();
}
return new NativeMatchValue();
}
function choose(?NativeMatchValue $subject, NativeMatchValue $same, NativeMatchValue $other): string
{
return match ($subject) {
@ -48,6 +61,10 @@ function main(): void
scalarMatchValue() => 'scalar',
$value => 'identity',
});
var_dump(match (temporaryMatchSubject()) {
pressuredMatchValue() => 'reused',
default => 'distinct',
});
}
?>
@ -58,3 +75,4 @@ subject
unrelated
scalar
string(8) "identity"
string(8) "distinct"

@ -0,0 +1,56 @@
--TEST--
Native class: method visibility is resolved at compile time across inheritance
--FILE--
<?php
#[Native]
class NativeVisibilityBase
{
private function baseLabel(): string
{
return 'base-private';
}
protected function protectedLabel(): string
{
return 'base-protected';
}
public function callBasePrivate(): string
{
return $this->baseLabel();
}
}
#[Native]
class NativeVisibilityChild extends NativeVisibilityBase
{
private function childLabel(): string
{
return 'child-private';
}
public function callChildPrivate(): string
{
return $this->childLabel();
}
public function callProtected(): string
{
return $this->protectedLabel();
}
}
function main(): void
{
$value = new NativeVisibilityChild();
echo $value->callBasePrivate(), PHP_EOL;
echo $value->callChildPrivate(), PHP_EOL;
echo $value->callProtected(), PHP_EOL;
}
?>
--EXPECT--
base-private
child-private
base-protected

@ -0,0 +1,68 @@
--TEST--
Native class: property hooks preserve native pointer types and temporary roots
--FILE--
<?php
#[Native]
class NativeHookObjectValue
{
public string $name;
public function __construct(string $name)
{
$this->name = $name;
}
}
#[Native]
class NativeHookObjectPressure
{
public int $value;
}
#[Native]
class NativeHookObjectOwner
{
private ?NativeHookObjectValue $stored;
public ?NativeHookObjectValue $value {
get {
return $this->stored;
}
set(?NativeHookObjectValue $value) {
$this->stored = $value;
}
}
}
function makeNativeHookValueAfterPressure(string $name): NativeHookObjectValue
{
for ($i = 0; $i < 300000; $i++) {
$filler = new NativeHookObjectPressure();
}
return new NativeHookObjectValue($name);
}
function consumeNativeHookValues(
NativeHookObjectValue $first,
NativeHookObjectValue $second,
): void {
echo $first->name, ':', $second->name, PHP_EOL;
}
function main(): void
{
$owner = new NativeHookObjectOwner();
$owner->value = new NativeHookObjectValue('first');
consumeNativeHookValues(
$owner->value,
makeNativeHookValueAfterPressure('second'),
);
$owner->value = null;
echo $owner->value === null ? "NULL\n" : "not-null\n";
}
?>
--EXPECT--
first:second
NULL

@ -0,0 +1,62 @@
--TEST--
Native class: self and parent signatures preserve typed pointer semantics
--FILE--
<?php
#[Native]
class NativeSignatureBase
{
public ?self $peer;
public function identity(self $value): self
{
return $value;
}
public function nullable(?self $value): ?self
{
return $value;
}
}
#[Native]
class NativeSignatureChild extends NativeSignatureBase
{
public ?parent $parentPeer;
public function childIdentity(self $value): self
{
return $value;
}
public function parentIdentity(parent $value): parent
{
return $value;
}
}
function main(): void
{
$base = new NativeSignatureBase();
$child = new NativeSignatureChild();
$base->peer = $base;
$child->parentPeer = $child;
var_dump(
$base->identity($base) === $base,
$base->nullable(null) === null,
$child->childIdentity($child) === $child,
$child->parentIdentity($child) === $child,
$base->peer === $base,
$child->parentPeer === $child,
);
}
?>
--EXPECT--
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)

@ -6,6 +6,12 @@ Native class: strict identity never coerces raw pointers to PHP values
#[Native]
class NativeStrictIdentity {}
#[Native]
class NativeStrictIdentityBase {}
#[Native]
class NativeStrictIdentityChild extends NativeStrictIdentityBase {}
function nativeIdentityOperand(NativeStrictIdentity $value): NativeStrictIdentity
{
echo "native\n";
@ -18,6 +24,24 @@ function zendIdentityOperand(): bool
return true;
}
function makeIdentityOperand(): NativeStrictIdentity
{
return new NativeStrictIdentity();
}
function makeIdentityOperandAfterPressure(): NativeStrictIdentity
{
for ($i = 0; $i < 300000; $i++) {
$filler = new NativeStrictIdentity();
}
return new NativeStrictIdentity();
}
function identityAsBase(NativeStrictIdentityBase $value): NativeStrictIdentityBase
{
return $value;
}
function main(): void
{
$value = new NativeStrictIdentity();
@ -30,6 +54,10 @@ function main(): void
var_dump($value !== true);
var_dump(nativeIdentityOperand($value) === zendIdentityOperand());
var_dump(zendIdentityOperand() === nativeIdentityOperand($value));
var_dump(makeIdentityOperand() === makeIdentityOperandAfterPressure());
$child = new NativeStrictIdentityChild();
$base = identityAsBase($child);
var_dump($base === $child, $child === $base);
}
?>
@ -44,3 +72,6 @@ bool(false)
zend
native
bool(false)
bool(false)
bool(true)
bool(true)

@ -0,0 +1,54 @@
--TEST--
Native class: $this is a native object pointer in value contexts
--FILE--
<?php
#[Native]
class NativeThisValue
{
public int $value;
public function __construct(int $value)
{
$this->value = $value;
}
public function identity(): self
{
return $this;
}
public function sameAs(NativeThisValue $other): bool
{
$alias = $this;
return $alias === $other;
}
public function passToFunction(): int
{
return readNativeThisValue($this);
}
}
function readNativeThisValue(NativeThisValue $value): int
{
return $value->value;
}
function main(): void
{
$value = new NativeThisValue(42);
$other = new NativeThisValue(42);
var_dump($value->identity() === $value);
var_dump($value->sameAs($value));
var_dump($value->sameAs($other));
var_dump($value->passToFunction());
}
?>
--EXPECT--
bool(true)
bool(true)
bool(false)
int(42)
Loading…
Cancel
Save