Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add emitting support for asymmetric visibility #183

Merged
merged 24 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
401ef99
Add emitting support for asymmetric visibility
thekid Aug 24, 2024
a6be1f6
Require feature branch
thekid Aug 24, 2024
7172e6e
Add support for protected(set)
thekid Aug 24, 2024
6760f34
Make errors consistent with PHP implementation
thekid Aug 24, 2024
865d2e9
Test writing from within private context
thekid Aug 24, 2024
f281452
Test promoted constructor parameters
thekid Aug 24, 2024
72cde23
Allow explicit `public(set)`
thekid Aug 24, 2024
ee835aa
QA: Code locality
thekid Aug 24, 2024
b1f428c
Extract visibility checks into trait
thekid Aug 24, 2024
bd19d6b
QA: API docs
thekid Aug 24, 2024
59ed2c1
Verify setting protected(set) from inherited scope
thekid Aug 24, 2024
f553374
Support readonly
thekid Aug 24, 2024
7fba427
Add reflection
thekid Aug 25, 2024
c1fb7dd
Readonly implies `protected(set)`
thekid Aug 25, 2024
2416557
Adjust constants after integration-testing with PHP compiled from PR
thekid Aug 25, 2024
7c9cee8
Adjust expected error message to work for PHP compiled from PR branch
thekid Aug 25, 2024
501c3d5
Remove test for `public public(set)`, where the set hook is erased
thekid Aug 25, 2024
d0e2732
Fold declarations like `[visibility] [visibility](set)` to just the v…
thekid Aug 25, 2024
28403bd
Add support for emitting properties with asymmetric visibility natively
thekid Aug 25, 2024
4a35832
Test against target version, not runtime version
thekid Aug 25, 2024
1db0df4
Use xp-framework/reflection release version
thekid Aug 26, 2024
e4a24c0
Use xp-framework/ast release version
thekid Aug 26, 2024
31eac56
Adjust modifier bits to reflection library
thekid Aug 27, 2024
c69c867
Restore BC
thekid Aug 27, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"require" : {
"xp-framework/core": "^12.0 | ^11.6 | ^10.16",
"xp-framework/reflection": "^3.0 | ^2.13",
"xp-framework/ast": "^11.1",
"xp-framework/ast": "dev-feature/asymmetric-visibility as 11.3.0",
"php" : ">=7.4.0"
},
"require-dev" : {
Expand Down
43 changes: 43 additions & 0 deletions src/main/php/lang/ast/emit/AsymmetricVisibility.class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php namespace lang\ast\emit;

use lang\ast\Code;
use lang\ast\nodes\{
Assignment,
Block,
InstanceExpression,
Literal,
OffsetExpression,
ReturnStatement,
Variable
};

trait AsymmetricVisibility {

protected function emitProperty($result, $property) {
$literal= new Literal("'{$property->name}'");
$virtual= new InstanceExpression(new Variable('this'), new OffsetExpression(new Literal('__virtual'), $literal));

if (in_array('private(set)', $property->modifiers)) {
$check= (
'$scope= debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]["class"] ?? null;'.
'if (__CLASS__ !== $scope && \\lang\\VirtualProperty::class !== $scope)'.
'throw new \\Error("Cannot access private property ".__CLASS__."::".$name);'
);
} else if (in_array('protected(set)', $property->modifiers)) {
$check= (
'$scope= debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2)[1]["class"] ?? null;'.
'if (__CLASS__ !== $scope && !is_subclass_of($scope, __CLASS__) && \\lang\\VirtualProperty::class !== $scope)'.
'throw new \\Error("Cannot access protected property ".__CLASS__."::".$name);'
);
}

$scope= $result->codegen->scope[0];
$scope->virtual[$property->name]= [
new ReturnStatement($virtual),
new Block([new Code($check), new Assignment($virtual, '=', new Variable('value'))]),
];
if (isset($property->expression)) {
$scope->init[sprintf('$this->__virtual["%s"]', $property->name)]= $property->expression;
}
}
}
4 changes: 2 additions & 2 deletions src/main/php/lang/ast/emit/PHP81.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ class PHP81 extends PHP {
RewriteBlockLambdaExpressions,
RewriteDynamicClassConstants,
RewriteStaticVariableInitializations,
RewriteProperties,
ReadonlyClasses,
OmitConstantTypes,
PropertyHooks
OmitConstantTypes
;

/** Sets up type => literal mappings */
Expand Down
4 changes: 2 additions & 2 deletions src/main/php/lang/ast/emit/PHP82.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ class PHP82 extends PHP {
RewriteBlockLambdaExpressions,
RewriteDynamicClassConstants,
RewriteStaticVariableInitializations,
RewriteProperties,
ReadonlyClasses,
OmitConstantTypes,
PropertyHooks
OmitConstantTypes
;

/** Sets up type => literal mappings */
Expand Down
2 changes: 1 addition & 1 deletion src/main/php/lang/ast/emit/PHP83.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
* @see https://wiki.php.net/rfc#php_83
*/
class PHP83 extends PHP {
use RewriteBlockLambdaExpressions, ReadonlyClasses, PropertyHooks;
use RewriteBlockLambdaExpressions, RewriteProperties, ReadonlyClasses;

/** Sets up type => literal mappings */
public function __construct() {
Expand Down
7 changes: 5 additions & 2 deletions src/main/php/lang/ast/emit/RewriteProperties.class.php
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
<?php namespace lang\ast\emit;

trait RewriteProperties {
use PropertyHooks, ReadonlyProperties {
use PropertyHooks, ReadonlyProperties, AsymmetricVisibility {
PropertyHooks::emitProperty as emitPropertyHooks;
ReadonlyProperties::emitProperty as emitReadonlyProperties;
AsymmetricVisibility::emitProperty as emitAsymmetricVisibility;
}

protected function emitProperty($result, $property) {
if ($property->hooks) {
return $this->emitPropertyHooks($result, $property);
} else if (in_array('readonly', $property->modifiers)) {
} else if (in_array('private(set)', $property->modifiers) || in_array('protected(set)', $property->modifiers)) {
return $this->emitAsymmetricVisibility($result, $property);
} else if (PHP_VERSION_ID <= 80100 && in_array('readonly', $property->modifiers)) {
return $this->emitReadonlyProperties($result, $property);
}
parent::emitProperty($result, $property);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php namespace lang\ast\unittest\emit;

use lang\Error;
use test\{Assert, Expect, Test};

/**
* Asymmetric visibility tests
*
* @see https://wiki.php.net/rfc/asymmetric-visibility-v2
*/
class AsymmetricVisibilityTest extends EmittingTest {

#[Test]
public function reading() {
$t= $this->declare('class %T {
public private(set) $fixture= "Test";
}');
Assert::equals('Test', $t->newInstance()->fixture);
}

#[Test, Expect(class: Error::class, message: '/Cannot access private property T.+::fixture/')]
public function writing_private() {
$t= $this->declare('class %T {
public private(set) $fixture= "Test";
}');
$t->newInstance()->fixture= 'Changed';
}

#[Test, Expect(class: Error::class, message: '/Cannot access protected property T.+::fixture/')]
public function writing_protected() {
$t= $this->declare('class %T {
public protected(set) $fixture= "Test";
}');
$t->newInstance()->fixture= 'Changed';
}
}
Loading