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

Remove context from properties, methods, constructors and initializers #38

Merged
merged 2 commits into from
Mar 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 9 additions & 16 deletions src/main/php/lang/reflection/Constructor.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,32 +30,25 @@ public function toString() {
* Creates a new instance of the type this constructor belongs to
*
* @param var[] $args
* @param ?string|?lang.XPClass|?lang.reflection.Type $context
* @return object
* @throws lang.reflection.InvocationFailed
* @throws lang.reflection.CannotInstantiate
*/
public function newInstance(array $args= [], $context= null) {
public function newInstance(array $args= []) {
try {
$pass= PHP_VERSION_ID < 80000 && $args ? self::pass($this->reflect, $args) : $args;

// Workaround for non-public constructors: Set accessible, then manually
// invoke after creating an instance without invoking the constructor.
if ($context && !$this->reflect->isPublic()) {
if (Reflection::of($context)->is($this->class->name)) {
$instance= $this->class->newInstanceWithoutConstructor();
$this->reflect->setAccessible(true);
$this->reflect->invokeArgs($instance, $pass);
return $instance;
}
if (!$this->reflect->isPublic()) {
$instance= $this->class->newInstanceWithoutConstructor();
$this->reflect->setAccessible(true);
$this->reflect->invokeArgs($instance, $pass);
return $instance;
} else {
return $this->class->newInstanceArgs($pass);
}

return $this->class->newInstanceArgs($pass);
} catch (ArgumentCountError $e) {
throw new CannotInstantiate($this->class, $e);
} catch (TypeError $e) {
throw new CannotInstantiate($this->class, $e);
} catch (ReflectionException $e) {
} catch (ReflectionException|ArgumentCountError|TypeError $e) {
throw new CannotInstantiate($this->class, $e);
} catch (Throwable $e) {

Expand Down
15 changes: 5 additions & 10 deletions src/main/php/lang/reflection/Initializer.class.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php namespace lang\reflection;

use ArgumentCountError, Error, ReflectionException, Throwable, TypeError;
use ArgumentCountError, Error, ReflectionException, ReflectionFunction, Throwable, TypeError;
use lang\Reflection;

/**
Expand All @@ -13,7 +13,7 @@ class Initializer extends Routine implements Instantiation {
private $class, $function;

static function __static() {
self::$NOOP= new \ReflectionFunction(function() { });
self::$NOOP= new ReflectionFunction(function() { });
}

/**
Expand Down Expand Up @@ -41,12 +41,11 @@ public function compoundName(): string { return strtr($this->class->name, '\\',
* Creates a new instance of the type this constructor belongs to
*
* @param var[] $args
* @param ?string|?lang.XPClass|?lang.reflection.Type $context
* @return object
* @throws lang.reflection.InvocationFailed
* @throws lang.reflection.CannotInstantiate
*/
public function newInstance(array $args= [], $context= null) {
public function newInstance(array $args= []) {
try {
$instance= $this->class->newInstanceWithoutConstructor();
} catch (ReflectionException $e) {
Expand All @@ -57,13 +56,9 @@ public function newInstance(array $args= [], $context= null) {

try {
$pass= PHP_VERSION_ID < 80000 && $args ? Routine::pass($this->reflect, $args) : $args;
$this->function->__invoke($instance, $pass, $context);
$this->function->__invoke($instance, $pass);
return $instance;
} catch (ArgumentCountError $e) {
throw new CannotInstantiate($this->class, $e);
} catch (TypeError $e) {
throw new CannotInstantiate($this->class, $e);
} catch (ReflectionException $e) {
} catch (ReflectionException|ArgumentCountError|TypeError $e) {
throw new CannotInstantiate($this->class, $e);
} catch (Throwable $e) {

Expand Down
3 changes: 1 addition & 2 deletions src/main/php/lang/reflection/Instantiation.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ interface Instantiation {
* Creates a new instance of the type this constructor belongs to
*
* @param var[] $args
* @param ?string|?lang.XPClass|?lang.reflection.Type $context
* @return object
* @throws lang.reflection.InvocationFailed
* @throws lang.reflection.CannotInstantiate
*/
public function newInstance(array $args= [], $context= null);
public function newInstance(array $args= []);
}
14 changes: 2 additions & 12 deletions src/main/php/lang/reflection/Method.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,24 +44,14 @@ public function closure($instance= null) {
*
* @param ?object $instance
* @param var[] $args
* @param ?string|?lang.XPClass|?lang.reflection.Type $context
* @return var
* @throws lang.reflection.CannotInvoke if prerequisites to the invocation fail
* @throws lang.reflection.InvocationFailed if invocation raises an exception
*/
public function invoke($instance, $args= [], $context= null) {

// Only allow invoking non-public methods when given a compatible context
if (!$this->reflect->isPublic()) {
if ($context && Reflection::type($context)->is($this->reflect->class)) {
$this->reflect->setAccessible(true);
} else {
throw new CannotInvoke($this, new ReflectionException('Trying to invoke non-public method'));
}
}

public function invoke(?object $instance, $args= []) {
try {
$pass= PHP_VERSION_ID < 80000 && $args ? self::pass($this->reflect, $args) : $args;
$this->reflect->setAccessible(true);
return $this->reflect->invokeArgs($instance, $pass);
} catch (ReflectionException|ArgumentCountError|TypeError $e) {
throw new CannotInvoke($this, $e);
Expand Down
33 changes: 8 additions & 25 deletions src/main/php/lang/reflection/Property.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,25 +42,18 @@ public function constraint() {
* Gets this property's value
*
* @param ?object $instance
* @param ?string|?lang.XPClass|?lang.reflection.Type $context
* @return var
* @throws lang.reflection.CannotAccess
* @throws lang.reflection.AccessingFailed if getting raises an exception
*/
public function get($instance, $context= null) {

// Only allow reading non-public properties when given a compatible context
if (!$this->reflect->isPublic()) {
if ($context && Reflection::of($context)->is($this->reflect->getDeclaringClass()->name)) {
$this->reflect->setAccessible(true);
} else {
throw new CannotAccess($this, new ReflectionException('Trying to read non-public property'));
}
}

public function get(?object $instance) {
try {
$this->reflect->setAccessible(true);
return $this->reflect->getValue($instance);
} catch (ReflectionException $e) {
throw new CannotAccess($this, $e);
} catch (Throwable $e) {
throw new AccessingFailed($this, $e);
}
}

Expand All @@ -69,23 +62,13 @@ public function get($instance, $context= null) {
*
* @param ?object $instance
* @param var $value
* @param ?string|?lang.XPClass|?lang.reflection.Type $context
* @return var The given value
* @throws lang.reflection.CannotAccess
* @throws lang.reflection.AccessFailed if setting raises an exception
* @throws lang.reflection.AccessingFailed if setting raises an exception
*/
public function set($instance, $value, $context= null) {

// Only allow reading non-public properties when given a compatible context
if (!$this->reflect->isPublic()) {
if ($context && Reflection::of($context)->is($this->reflect->getDeclaringClass()->name)) {
$this->reflect->setAccessible(true);
} else {
throw new CannotAccess($this, new ReflectionException('Trying to write non-public property'));
}
}

public function set(?object $instance, $value) {
try {
$this->reflect->setAccessible(true);
$this->reflect->setValue($instance, $value);
return $value;
} catch (ReflectionException $e) {
Expand Down
8 changes: 3 additions & 5 deletions src/main/php/lang/reflection/Type.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,15 +162,13 @@ public function initializer($function) {
return new Initializer($this->reflect);
} else if ($function instanceof \Closure) {
$reflect= new ReflectionFunction($function);
return new Initializer($this->reflect, $reflect, function($instance, $args, $context) use($function) {
return new Initializer($this->reflect, $reflect, function($instance, $args) use($function) {
return $function->call($instance, ...$args);
});
} else if ($this->reflect->hasMethod($function)) {
$reflect= $this->reflect->getMethod($function);
return new Initializer($this->reflect, $reflect, function($instance, $args, $context) use($reflect) {
if ($context && !$reflect->isPublic() && Reflection::of($context)->isInstance($instance)) {
$reflect->setAccessible(true);
}
return new Initializer($this->reflect, $reflect, function($instance, $args) use($reflect) {
$reflect->setAccessible(true);
return $reflect->invokeArgs($instance, $args);
});
}
Expand Down
12 changes: 0 additions & 12 deletions src/test/php/lang/reflection/unittest/InstantiationTest.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,6 @@ public function newInstance_cannot_instantiate_using_non_public_constructor($mod
$t->newInstance();
}

#[Test, Expect(CannotInstantiate::class), Values(['private', 'protected'])]
public function constructor_cannot_instantiate_using_non_public_constructor($modifier) {
$t= $this->declare('{ '.$modifier.' function __construct() { } }');
$t->constructor()->newInstance();
}

#[Test, Values(['private', 'protected'])]
public function instantiate_with_non_public_constructor_in_context($modifier) {
$t= $this->declare('{
Expand All @@ -113,12 +107,6 @@ public function instantiate_with_constructor_promotion() {
Assert::equals($this, $t->constructor()->newInstance([$this])->value);
}

#[Test, Expect(CannotInstantiate::class)]
public function cannot_instantiate_with_private_constructor_in_incorrect_context() {
$t= $this->declare('{ private function __construct() { } }');
$t->constructor()->newInstance([], typeof($this));
}

#[Test, Expect(CannotInstantiate::class)]
public function interfaces_cannot_be_instantiated() {
Reflection::type(Runnable::class)->newInstance();
Expand Down
22 changes: 5 additions & 17 deletions src/test/php/lang/reflection/unittest/InvocationTest.class.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php namespace lang\reflection\unittest;

use lang\reflection\{CannotInvoke, InvocationFailed};
use lang\{CommandLine, IllegalAccessException, Reflection, Runnable};
use lang\{CommandLine, IllegalAccessException, Reflection, Runnable, IllegalStateException};
use test\{Assert, AssertionFailedError, Before, Expect, Test, Values};

class InvocationTest {
Expand Down Expand Up @@ -32,22 +32,10 @@ public function invoke_instance_method_from($context) {
Assert::equals('External', $method->invoke($this->fixtures[$context]->newInstance(), []));
}

#[Test, Values([['friend'], ['internal']]), Expect(CannotInvoke::class)]
public function cannot_invoke_non_public_method_by_default($method) {
$method= $this->fixtures['parent']->method($method);
$method->invoke($this->fixtures['parent']->newInstance(), []);
}

#[Test, Values([['parent', 'parent'], ['child', 'parent'], ['parent', 'child']])]
public function invoke_private_method_in_context($instance, $context) {
$method= $this->fixtures['parent']->method('internal');
Assert::equals('Internal', $method->invoke($this->fixtures[$instance]->newInstance(), [], $this->fixtures[$context]));
}

#[Test, Expect(CannotInvoke::class)]
public function cannot_invoke_private_method_in_incorrect_context() {
$method= $this->fixtures['parent']->method('internal');
$method->invoke($this->fixtures['parent']->newInstance(), [], typeof($this));
Assert::equals('Internal', $method->invoke($this->fixtures[$instance]->newInstance(), []));
}

#[Test, Expect(CannotInvoke::class)]
Expand Down Expand Up @@ -137,11 +125,11 @@ public function invocation_failed_target() {
}

#[Test]
public function cannot_invoke_target() {
$t= $this->declare('{ private static function fixture() { } }');
public function cannot_invoke_exceptions_target_member() {
$t= $this->declare('{ private static function fixture($a) { } }');
try {
$t->method('fixture')->invoke(null, []);
throw new AssertionFailedError('No exception was raised');
throw new IllegalStateException('No exception was raised');
} catch (CannotInvoke $expected) {
Assert::equals($t->method('fixture'), $expected->target());
}
Expand Down
47 changes: 6 additions & 41 deletions src/test/php/lang/reflection/unittest/PropertiesTest.class.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<?php namespace lang\reflection\unittest;

use lang\reflection\{AccessingFailed, CannotAccess, Constraint, Modifiers};
use lang\{Primitive, Type, TypeIntersection, TypeUnion, XPClass};
use lang\{Primitive, Type, TypeIntersection, TypeUnion, XPClass, IllegalStateException};
use test\verify\Runtime;
use test\{Action, Assert, AssertionFailedError, Expect, Test, Values};
use test\{Action, Assert, Expect, Test, Values};

class PropertiesTest {
use TypeDefinition;
Expand Down Expand Up @@ -79,44 +79,20 @@ public function set_static() {
Assert::equals('Modified', $class::$fixture);
}

#[Test, Expect(CannotAccess::class)]
public function cannot_read_private_by_default() {
$type= $this->declare('{ private static $fixture = "Test"; }');
$type->property('fixture')->get(null);
}

#[Test, Expect(CannotAccess::class)]
public function cannot_write_private_by_default() {
$type= $this->declare('{ private static $fixture = "Test"; }');
$type->property('fixture')->set(null, 'Modified');
}

#[Test, Expect(CannotAccess::class)]
public function cannot_read_private_with_incorrect_context() {
$type= $this->declare('{ private static $fixture = "Test"; }');
$type->property('fixture')->get(null, typeof($this));
}

#[Test, Expect(CannotAccess::class)]
public function cannot_write_private_with_incorrect_context() {
$type= $this->declare('{ private static $fixture = "Test"; }');
$type->property('fixture')->set(null, 'Modified', typeof($this));
}

#[Test, Expect(AccessingFailed::class)]
public function type_mismatch() {
$type= $this->declare('{ private static array $fixture; }');
$type->property('fixture')->set(null, 1, $type);
$type->property('fixture')->set(null, 1);
}

#[Test]
public function private_instance_roundtrip() {
$type= $this->declare('{ private $fixture = "Test"; }');
$instance= $type->newInstance();
$property= $type->properties()->named('fixture');
$property->set($instance, 'Modified', $type);
$property->set($instance, 'Modified');

Assert::equals('Modified', $property->get($instance, $type));
Assert::equals('Modified', $property->get($instance));
}

#[Test]
Expand Down Expand Up @@ -242,7 +218,7 @@ public function string_representation_with_union_type_declaration() {
}

#[Test]
public function accessing_failed_target() {
public function set_accessing_failed_exceptions_target_member() {
$t= $this->declare('{ public static array $fixture; }');
try {
$t->property('fixture')->set(null, 1);
Expand All @@ -251,15 +227,4 @@ public function accessing_failed_target() {
Assert::equals($t->property('fixture'), $expected->target());
}
}

#[Test]
public function cannot_access_target() {
$t= $this->declare('{ private static $fixture; }');
try {
$t->property('fixture')->get(null);
throw new AssertionFailedError('No exception was raised');
} catch (CannotAccess $expected) {
Assert::equals($t->property('fixture'), $expected->target());
}
}
}
Loading