diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 626a7fdd..a8a310fc 100755 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: false matrix: - php-versions: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5', '8.6'] + php-versions: ['8.0', '8.1', '8.2', '8.3', '8.4', '8.5', '8.6'] os: [ubuntu-latest, windows-latest] steps: diff --git a/README.md b/README.md index 12b54880..40b0e607 100755 --- a/README.md +++ b/README.md @@ -4,8 +4,7 @@ XP Compiler [![Build status on GitHub](https://github.com/xp-framework/compiler/workflows/Tests/badge.svg)](https://github.com/xp-framework/compiler/actions) [![XP Framework Module](https://raw.githubusercontent.com/xp-framework/web/master/static/xp-framework-badge.png)](https://github.com/xp-framework/core) [![BSD Licence](https://raw.githubusercontent.com/xp-framework/web/master/static/licence-bsd.png)](https://github.com/xp-framework/core/blob/master/LICENCE.md) -[![Requires PHP 7.4+](https://raw.githubusercontent.com/xp-framework/web/master/static/php-7_4plus.svg)](http://php.net/) -[![Supports PHP 8.0+](https://raw.githubusercontent.com/xp-framework/web/master/static/php-8_0plus.svg)](http://php.net/) +[![Requires PHP 8.0+](https://raw.githubusercontent.com/xp-framework/web/master/static/php-8_0plus.svg)](http://php.net/) [![Latest Stable Version](https://poser.pugx.org/xp-framework/compiler/version.svg)](https://packagist.org/packages/xp-framework/compiler) Compiles future PHP to today's PHP. @@ -16,7 +15,7 @@ After adding the compiler to your project via `composer require xp-framework/com Example ------- -The following code uses Hack language, PHP 8.6, PHP 8.5, PHP 8.4, PHP 8.3, PHP 8.2, 8.1 and 8.0 features but runs on anything >= PHP 7.4. Builtin features from newer PHP versions are translated to work with the currently executing runtime if necessary. +The following code uses Hack language, PHP 8.6, PHP 8.5, PHP 8.4, PHP 8.3, PHP 8.2, 8.1 and 8.0 features but runs on anything >= PHP 8.0. Builtin features from newer PHP versions are translated to work with the currently executing runtime if necessary. ```php [] @FileSystemCL<./vendor/xp-framework/compiler/src/main/php> -lang.ast.emit.PHP74 lang.ast.emit.PHP80 lang.ast.emit.PHP81 lang.ast.emit.PHP82 diff --git a/composer.json b/composer.json index 94ca42cf..12346d25 100755 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "xp-framework/core": "^12.0 | ^11.6 | ^10.16", "xp-framework/reflection": "^3.2 | ^2.15", "xp-framework/ast": "^13.0 | ^12.2", - "php" : ">=7.4.0" + "php" : ">=8.0.0" }, "require-dev" : { "xp-framework/test": "^2.0 | ^1.5" diff --git a/src/main/php/lang/ast/emit/ArbitrayNewExpressions.class.php b/src/main/php/lang/ast/emit/ArbitrayNewExpressions.class.php deleted file mode 100755 index d3a6551e..00000000 --- a/src/main/php/lang/ast/emit/ArbitrayNewExpressions.class.php +++ /dev/null @@ -1,30 +0,0 @@ -type instanceof IsExpression)) return parent::emitNew($result, $new); - - // Emit supported `new $var`, rewrite unsupported `new ($expr)` - if ($new->type->expression instanceof Variable && $new->type->expression->const) { - $result->out->write('new $'.$new->type->expression->pointer.'('); - $this->emitArguments($result, $new->arguments); - $result->out->write(')'); - } else { - $t= $result->temp(); - $result->out->write('('.$t.'= '); - $this->emitOne($result, $new->type->expression); - $result->out->write(') ? new '.$t.'('); - $this->emitArguments($result, $new->arguments); - $result->out->write(') : null'); - } - } -} \ No newline at end of file diff --git a/src/main/php/lang/ast/emit/AttributesAsComments.class.php b/src/main/php/lang/ast/emit/AttributesAsComments.class.php deleted file mode 100755 index 09929b96..00000000 --- a/src/main/php/lang/ast/emit/AttributesAsComments.class.php +++ /dev/null @@ -1,67 +0,0 @@ -out->write('\\'.$annotation->name); - if (empty($annotation->arguments)) return; - - // Check whether arguments are constant, enclose in `eval` array - // otherwise. This is not strictly necessary but will ensure - // forward compatibility with PHP 8 - foreach ($annotation->arguments as $argument) { - if ($this->isConstant($result, $argument)) continue; - - $escaping= new Escaping($result->out, ["'" => "\\'", '\\' => '\\\\']); - $result->out->write('(eval: ['); - foreach ($annotation->arguments as $name => $argument) { - is_string($name) && $result->out->write("'{$name}'=>"); - - $result->out->write("'"); - $result->out= $escaping; - $this->emitOne($result, $argument); - $result->out= $escaping->original(); - $result->out->write("',"); - } - $result->out->write('])'); - return; - } - - // We can use named arguments here as PHP 8 attributes are parsed - // by the XP reflection API when using PHP 7. However, we may not - // emit trailing commas here! - $result->out->write('('); - $i= 0; - foreach ($annotation->arguments as $name => $argument) { - $i++ > 0 && $result->out->write(','); - is_string($name) && $result->out->write("{$name}:"); - $this->emitOne($result, $argument); - } - $result->out->write(')'); - } - - protected function emitAnnotations($result, $annotations) { - $line= $annotations->line; - $result->out->write('#['); - - $result->out= new Escaping($result->out, ["\n" => " "]); - foreach ($annotations->named as $annotation) { - $this->emitOne($result, $annotation); - $result->out->write(','); - } - $result->out= $result->out->original(); - - $result->out->write("]\n"); - $result->line= $line + 1; - } -} \ No newline at end of file diff --git a/src/main/php/lang/ast/emit/ChainScopeOperators.class.php b/src/main/php/lang/ast/emit/ChainScopeOperators.class.php deleted file mode 100755 index 13bd4411..00000000 --- a/src/main/php/lang/ast/emit/ChainScopeOperators.class.php +++ /dev/null @@ -1,32 +0,0 @@ -type instanceof Node)) return $this->rewriteDynamicClassConstants($result, $scope); - - if ($scope->member instanceof Literal && 'class' === $scope->member->expression) { - $result->out->write('\\get_class('); - $this->emitOne($result, $scope->type); - $result->out->write(')'); - } else { - $t= $result->temp(); - $result->out->write('(null==='.$t.'='); - $this->emitOne($result, $scope->type); - $result->out->write(")?null:{$t}::"); - $this->emitOne($result, $scope->member); - } - } -} \ No newline at end of file diff --git a/src/main/php/lang/ast/emit/Declaration.class.php b/src/main/php/lang/ast/emit/Declaration.class.php index 190038f2..967d2d4e 100755 --- a/src/main/php/lang/ast/emit/Declaration.class.php +++ b/src/main/php/lang/ast/emit/Declaration.class.php @@ -6,15 +6,10 @@ use lang\reflection\Modifiers; class Declaration extends Type { - private $type, $codegen; static function __static() { } - /** @param lang.ast.nodes.TypeDeclaration $type */ - public function __construct($type, $codegen) { - $this->type= $type; - $this->codegen= $codegen; - } + public function __construct(private $type, private $codegen) { } /** @return string */ public function name() { return ltrim($this->type->name, '\\'); } diff --git a/src/main/php/lang/ast/emit/Escaping.class.php b/src/main/php/lang/ast/emit/Escaping.class.php index e096035f..f6c0ad56 100755 --- a/src/main/php/lang/ast/emit/Escaping.class.php +++ b/src/main/php/lang/ast/emit/Escaping.class.php @@ -3,12 +3,8 @@ use io\streams\OutputStream; class Escaping implements OutputStream { - private $target, $replacements; - public function __construct(OutputStream $target, array $replacements) { - $this->target= $target; - $this->replacements= $replacements; - } + public function __construct(private OutputStream $target, private array $replacements) { } public function write($bytes) { $this->target->write(strtr($bytes, $this->replacements)); diff --git a/src/main/php/lang/ast/emit/GeneratedCode.class.php b/src/main/php/lang/ast/emit/GeneratedCode.class.php index 5e17b2cb..ef89d369 100755 --- a/src/main/php/lang/ast/emit/GeneratedCode.class.php +++ b/src/main/php/lang/ast/emit/GeneratedCode.class.php @@ -1,18 +1,15 @@ prolog= $prolog; - $this->epilog= $epilog; + public function __construct($out, private $prolog= '', private $epilog= '') { parent::__construct($out); } diff --git a/src/main/php/lang/ast/emit/InType.class.php b/src/main/php/lang/ast/emit/InType.class.php index 4df91107..14f6873e 100755 --- a/src/main/php/lang/ast/emit/InType.class.php +++ b/src/main/php/lang/ast/emit/InType.class.php @@ -1,13 +1,10 @@ type= $type; - } + public function __construct(public $type) { } } \ No newline at end of file diff --git a/src/main/php/lang/ast/emit/Incomplete.class.php b/src/main/php/lang/ast/emit/Incomplete.class.php index dfda0688..ee21e0ea 100755 --- a/src/main/php/lang/ast/emit/Incomplete.class.php +++ b/src/main/php/lang/ast/emit/Incomplete.class.php @@ -1,10 +1,9 @@ name= $name; } + /** Creates a new incomplete type */ + public function __construct(private string $name) { } /** @return string */ public function name() { return $this->name; } diff --git a/src/main/php/lang/ast/emit/MatchAsTernaries.class.php b/src/main/php/lang/ast/emit/MatchAsTernaries.class.php deleted file mode 100755 index 9e82693d..00000000 --- a/src/main/php/lang/ast/emit/MatchAsTernaries.class.php +++ /dev/null @@ -1,44 +0,0 @@ -temp(); - if (null === $match->expression) { - $result->out->write('('.$t.'=true)'); - } else { - $result->out->write('('.$t.'='); - $this->emitOne($result, $match->expression); - $result->out->write(')'); - } - - $b= 0; - foreach ($match->cases as $case) { - foreach ($case->expressions as $expression) { - $b && $result->out->write($t); - $result->out->write('===('); - $this->emitOne($result, $expression); - $result->out->write(')?'); - $this->emitAsExpression($result, $case->body); - $result->out->write(':('); - $b++; - } - } - - // Match without cases => create something that will never match - $b || $result->out->write('===NAN?:'); - - // Emit IIFE for raising an error until we have throw expressions - if (null === $match->default) { - $result->out->write('(function() use('.$t.') { throw new \\Error("Unhandled match value of type ".gettype('.$t.')); })()'); - } else { - $this->emitAsExpression($result, $match->default); - } - $result->out->write(str_repeat(')', $b)); - } -} \ No newline at end of file diff --git a/src/main/php/lang/ast/emit/NonCapturingCatchVariables.class.php b/src/main/php/lang/ast/emit/NonCapturingCatchVariables.class.php deleted file mode 100755 index cd431929..00000000 --- a/src/main/php/lang/ast/emit/NonCapturingCatchVariables.class.php +++ /dev/null @@ -1,20 +0,0 @@ -variable ? '$'.$catch->variable : $result->temp(); - if (empty($catch->types)) { - $result->out->write('catch(\\Throwable '.$capture.') {'); - } else { - $result->out->write('catch('.implode('|', $catch->types).' '.$capture.') {'); - } - $this->emitAll($result, $catch->body); - $result->out->write('}'); - } -} \ No newline at end of file diff --git a/src/main/php/lang/ast/emit/NullsafeAsTernaries.class.php b/src/main/php/lang/ast/emit/NullsafeAsTernaries.class.php deleted file mode 100755 index 32fe0a5b..00000000 --- a/src/main/php/lang/ast/emit/NullsafeAsTernaries.class.php +++ /dev/null @@ -1,17 +0,0 @@ -temp(); - $result->out->write('null===('.$t.'='); - $this->emitOne($result, $instance->expression); - $result->out->write(')?null:'.$t.'->'); - $this->emitOne($result, $instance->member); - } -} \ No newline at end of file diff --git a/src/main/php/lang/ast/emit/OmitArgumentNames.class.php b/src/main/php/lang/ast/emit/OmitArgumentNames.class.php deleted file mode 100755 index eb487440..00000000 --- a/src/main/php/lang/ast/emit/OmitArgumentNames.class.php +++ /dev/null @@ -1,17 +0,0 @@ -out->write(','); - $this->emitOne($result, $argument); - } - } -} \ No newline at end of file diff --git a/src/main/php/lang/ast/emit/PHP74.class.php b/src/main/php/lang/ast/emit/PHP74.class.php deleted file mode 100755 index 18c1a2ce..00000000 --- a/src/main/php/lang/ast/emit/PHP74.class.php +++ /dev/null @@ -1,61 +0,0 @@ - literal mappings */ - public function __construct() { - $this->literals= [ - IsArray::class => function($t) { return 'array'; }, - IsMap::class => function($t) { return 'array'; }, - IsFunction::class => function($t) { return 'callable'; }, - IsValue::class => function($t) { $l= $t->literal(); return 'static' === $l ? 'self' : $l; }, - IsNullable::class => function($t) { $l= $this->literal($t->element); return null === $l ? null : '?'.$l; }, - IsUnion::class => function($t) { return null; }, - IsIntersection::class => function($t) { return null; }, - IsLiteral::class => function($t) { - static $rewrite= [ - 'mixed' => 1, - 'null' => 1, - 'never' => 'void', - 'true' => 'bool', - 'false' => 'bool', - ]; - - $l= $t->literal(); - return (1 === ($r= $rewrite[$l] ?? $l)) ? null : $r; - }, - IsGeneric::class => function($t) { return null; } - ]; - } -} \ No newline at end of file diff --git a/src/main/php/lang/ast/emit/Result.class.php b/src/main/php/lang/ast/emit/Result.class.php index 76203df3..aaf63c9f 100755 --- a/src/main/php/lang/ast/emit/Result.class.php +++ b/src/main/php/lang/ast/emit/Result.class.php @@ -5,7 +5,6 @@ use lang\ast\CodeGen; class Result implements Closeable { - public $out; public $codegen; public $line= 1; public $locals= []; @@ -15,7 +14,7 @@ class Result implements Closeable { * * @param io.streams.OutputStream $out */ - public function __construct(OutputStream $out) { + public function __construct(public OutputStream $out) { $this->out= $out; $this->codegen= new CodeGen(); $this->initialize(); diff --git a/src/main/php/lang/ast/emit/RewriteThrowableExpressions.class.php b/src/main/php/lang/ast/emit/RewriteThrowableExpressions.class.php deleted file mode 100755 index 777a9dc7..00000000 --- a/src/main/php/lang/ast/emit/RewriteThrowableExpressions.class.php +++ /dev/null @@ -1,19 +0,0 @@ -out->write('('); - $this->enclose($result, $throw->expression, null, false, function($result, $expression) { - $result->out->write('throw '); - $this->emitOne($result, $expression); - $result->out->write(';'); - }); - $result->out->write(')()'); - } -} \ No newline at end of file diff --git a/src/main/php/lang/ast/syntax/php/Using.class.php b/src/main/php/lang/ast/syntax/php/Using.class.php index 3d0ce77f..a5a44dd9 100755 --- a/src/main/php/lang/ast/syntax/php/Using.class.php +++ b/src/main/php/lang/ast/syntax/php/Using.class.php @@ -23,8 +23,8 @@ * } * ``` * - * @see https://github.com/xp-framework/compiler/pull/33 - * @test xp://lang.ast.unittest.emit.UsingTest + * @see https://github.com/xp-framework/compiler/pull/33 + * @test lang.ast.unittest.emit.UsingTest */ class Using implements Extension { diff --git a/src/test/php/lang/ast/unittest/TypeLiteralsTest.class.php b/src/test/php/lang/ast/unittest/TypeLiteralsTest.class.php index 999e24ce..3c41d3a7 100755 --- a/src/test/php/lang/ast/unittest/TypeLiteralsTest.class.php +++ b/src/test/php/lang/ast/unittest/TypeLiteralsTest.class.php @@ -1,6 +1,6 @@ base(); - yield [new IsLiteral('object'), 'object']; - yield [new IsLiteral('void'), 'void']; - yield [new IsLiteral('never'), 'void']; - yield [new IsLiteral('iterable'), 'iterable']; - yield [new IsLiteral('mixed'), null]; - yield [new IsLiteral('null'), null]; - yield [new IsLiteral('false'), 'bool']; - yield [new IsLiteral('true'), 'bool']; - yield [new IsNullable(new IsLiteral('string')), '?string']; - yield [new IsNullable(new IsLiteral('object')), '?object']; - yield [new IsUnion([new IsLiteral('string'), new IsLiteral('int')]), null]; - yield [new IsUnion([new IsLiteral('string'), new IsLiteral('false')]), null]; - yield [new IsIntersection([new IsValue('Test'), new IsValue('Iterator')]), null]; - } - /** * PHP 8.0 added `mixed` and union types * @@ -104,11 +82,6 @@ private function php82() { yield [new IsIntersection([new IsValue('Test'), new IsValue('Iterator')]), 'Test&Iterator']; } - #[Test, Values(from: 'php74')] - public function php74_literals($type, $literal) { - Assert::equals($literal, (new PHP74())->literal($type)); - } - #[Test, Values(from: 'php80')] public function php80_literals($type, $literal) { Assert::equals($literal, (new PHP80())->literal($type)); diff --git a/src/test/php/lang/ast/unittest/emit/ChainScopeOperatorsTest.class.php b/src/test/php/lang/ast/unittest/emit/ChainScopeOperatorsTest.class.php deleted file mode 100755 index 7e874e67..00000000 --- a/src/test/php/lang/ast/unittest/emit/ChainScopeOperatorsTest.class.php +++ /dev/null @@ -1,31 +0,0 @@ -emit( - new ScopeExpression(new Variable('instance'), new Literal('class'))) - ); - } - - #[Test] - public function does_not_rewrite_type_literal() { - Assert::equals('self::class', $this->emit( - new ScopeExpression('self', new Literal('class')), - [new ClassDeclaration([], new IsValue('\\T'), null, [], [], null, null, 1)] - )); - } -} \ No newline at end of file diff --git a/src/test/php/lang/ast/unittest/loader/CompilingClassLoaderTest.class.php b/src/test/php/lang/ast/unittest/loader/CompilingClassLoaderTest.class.php index 604d6fcf..b6514542 100755 --- a/src/test/php/lang/ast/unittest/loader/CompilingClassLoaderTest.class.php +++ b/src/test/php/lang/ast/unittest/loader/CompilingClassLoaderTest.class.php @@ -51,7 +51,7 @@ public function can_create() { CompilingClassLoader::instanceFor(self::$runtime); } - #[Test, Values(['7.4.0', '7.4.12', '8.0.0', '8.1.0', '8.2.0', '8.3.0', '8.4.0'])] + #[Test, Values(['8.0.0', '8.1.0', '8.2.0', '8.3.0', '8.4.0', '8.5.0'])] public function supports_php($version) { CompilingClassLoader::instanceFor('php:'.$version); }