TypePHP 编译器
https://swoole.com/aot/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
378 lines
12 KiB
378 lines
12 KiB
<?php
|
|
|
|
use TypePhp\CompilerTest;
|
|
use TypePhp\Exception\TestError;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
class InheritanceErrorTest extends TestCase
|
|
{
|
|
private function exec(string $expected, string $file): void
|
|
{
|
|
try {
|
|
$compiler = CompilerTest::create(ROOT_PATH);
|
|
$testFile = __DIR__ . '/../code/' . $file;
|
|
$compiler->addFiles([$testFile]);
|
|
$compiler->prepareFile($testFile);
|
|
$compiler->convertFile($testFile);
|
|
} catch (TestError $exception) {
|
|
$this->assertStringContainsString($expected, $exception->getMessage());
|
|
return;
|
|
}
|
|
$this->fail('Expected TestError exception was not thrown');
|
|
}
|
|
|
|
private function assertCompiles(string $file): void
|
|
{
|
|
global $translator;
|
|
$compiler = CompilerTest::create(ROOT_PATH);
|
|
$translator = $compiler;
|
|
$testFile = __DIR__ . '/../code/' . $file;
|
|
$compiler->addFiles([$testFile]);
|
|
$compiler->prepareFile($testFile);
|
|
$compiler->convertFile($testFile);
|
|
$this->addToAssertionCount(1);
|
|
}
|
|
|
|
public function testParameterCountMismatch()
|
|
{
|
|
$this->exec('must be compatible', 'inheritance_error.php');
|
|
}
|
|
|
|
public function testParameterTypeMismatch()
|
|
{
|
|
$this->exec('must be compatible', 'inheritance_error_type.php');
|
|
}
|
|
|
|
public function testReturnTypeMismatch()
|
|
{
|
|
$this->exec('must be compatible', 'inheritance_error_return.php');
|
|
}
|
|
|
|
public function testReturnTypeCannotBeContravariant()
|
|
{
|
|
$this->exec('must be compatible', 'inheritance_error_return_contravariant_class.php');
|
|
}
|
|
|
|
public function testUnionReturnTypeCannotBeWidenedToUnrelatedType(): void
|
|
{
|
|
$this->exec('must be compatible', 'inheritance_error_return_union_widened.php');
|
|
}
|
|
|
|
public function testIntersectionReturnTypeCannotDropAMember(): void
|
|
{
|
|
$this->exec('must be compatible', 'inheritance_error_return_intersection_missing.php');
|
|
}
|
|
|
|
public function testDnfReturnTypeCannotBeWidened(): void
|
|
{
|
|
$this->exec('must be compatible', 'inheritance_error_dnf_return_widened.php');
|
|
}
|
|
|
|
public function testStaticReturnTypeCannotBeWidenedToSelf(): void
|
|
{
|
|
$this->exec('must be compatible', 'inheritance_error_return_static_widened.php');
|
|
}
|
|
|
|
public function testNeverReturnTypeCannotBeWidenedToVoid(): void
|
|
{
|
|
$this->exec('must be compatible', 'inheritance_error_return_never_widened.php');
|
|
}
|
|
|
|
public function testGeneratorReturnTypeCannotBeWidenedToIterable(): void
|
|
{
|
|
$this->exec('must be compatible', 'inheritance_error_generator_return_widened.php');
|
|
}
|
|
|
|
public function testIntersectionReturnTypeCanNarrowToIntersectionOrConcreteSubtype(): void
|
|
{
|
|
$this->assertCompiles('return_type_covariance_intersection.php');
|
|
}
|
|
|
|
public function testParameterTypeCannotBeCovariant()
|
|
{
|
|
$this->exec('must be compatible', 'inheritance_error_param_covariant_class.php');
|
|
}
|
|
|
|
public function testDnfParameterTypeCannotBeNarrowed(): void
|
|
{
|
|
$this->exec('must be compatible', 'inheritance_error_dnf_param_narrowed.php');
|
|
}
|
|
|
|
public function testUnionParameterCannotNarrowUntypedParent()
|
|
{
|
|
$this->exec('must be compatible', 'interface_param_union_narrows_untyped.php');
|
|
}
|
|
|
|
public function testUnionParameterCannotNarrowMixedParent()
|
|
{
|
|
$this->exec('must be compatible', 'interface_param_union_narrows_mixed.php');
|
|
}
|
|
|
|
public function testByRefMismatch()
|
|
{
|
|
$this->exec('must be compatible', 'inheritance_error_byref.php');
|
|
}
|
|
|
|
public function testVariadicMismatch()
|
|
{
|
|
$this->exec('must be compatible', 'inheritance_error_variadic.php');
|
|
}
|
|
|
|
public function testMethodVisibilityMismatch()
|
|
{
|
|
$this->exec('must be compatible', 'inheritance_error_visibility_narrow.php');
|
|
}
|
|
|
|
public function testMethodVisibilityWideningIsAllowed()
|
|
{
|
|
$this->assertCompiles('inheritance_error_visibility.php');
|
|
}
|
|
|
|
public function testParentScopeMayAccessChildProtectedMethod()
|
|
{
|
|
$this->assertCompiles('inheritance_protected_child_method_access.php');
|
|
}
|
|
|
|
public function testParentScopeMayAccessChildProtectedConstant()
|
|
{
|
|
$this->assertCompiles('inheritance_protected_child_const_access.php');
|
|
}
|
|
|
|
public function testMethodStaticMismatch()
|
|
{
|
|
$this->exec('must be compatible', 'inheritance_error_static.php');
|
|
}
|
|
|
|
public function testCannotOverrideFinalMethod()
|
|
{
|
|
$this->exec('Cannot override final method', 'inheritance_error_final_method.php');
|
|
}
|
|
|
|
public function testInterfaceMethodStaticMismatch()
|
|
{
|
|
$this->exec('must be compatible', 'interface_method_static_mismatch.php');
|
|
}
|
|
|
|
public function testChildMayAddOptionalTrailingParameter()
|
|
{
|
|
$this->assertCompiles('inheritance_optional_param_allowed.php');
|
|
}
|
|
|
|
public function testChildMayDeclareReturnTypeWhenParentHasNone()
|
|
{
|
|
$this->assertCompiles('inheritance_return_type_covariant_from_none.php');
|
|
}
|
|
|
|
public function testChildReturnTypeMayBeCovariant()
|
|
{
|
|
$this->assertCompiles('inheritance_return_type_covariant_class.php');
|
|
}
|
|
|
|
public function testChildParameterTypeMayBeContravariant()
|
|
{
|
|
$this->assertCompiles('inheritance_parameter_type_contravariant_class.php');
|
|
}
|
|
|
|
public function testPropertyTypeMismatch()
|
|
{
|
|
$this->exec('must be compatible', 'inheritance_error_prop_type.php');
|
|
}
|
|
|
|
public function testPropertyVisibilityMismatch()
|
|
{
|
|
$this->exec('must be compatible', 'inheritance_error_prop_visibility.php');
|
|
}
|
|
|
|
public function testPropertyVisibilityMayBeWidened()
|
|
{
|
|
$this->assertCompiles('inheritance_prop_visibility_widen.php');
|
|
}
|
|
|
|
public function testPrivateParentPropertyCannotBeRedeclared()
|
|
{
|
|
$this->exec('property shadowing across inheritance is not allowed', 'inheritance_private_prop_redeclare.php');
|
|
}
|
|
|
|
public function testPrivateParentPropertyCannotBeShadowedByPublicProperty()
|
|
{
|
|
$this->exec('property shadowing across inheritance is not allowed', 'accessibility/private-prop-in-parent.php');
|
|
}
|
|
|
|
public function testPrivateTraitPropertyCannotBeShadowedByPublicProperty()
|
|
{
|
|
$this->exec('property shadowing across inheritance is not allowed', 'accessibility/private-prop-in-trait.php');
|
|
}
|
|
|
|
public function testConstantTypeMismatch()
|
|
{
|
|
$this->exec('must be compatible', 'inheritance_error_const_type.php');
|
|
}
|
|
|
|
public function testTypedConstantCannotBeOverriddenWithoutDeclaredType()
|
|
{
|
|
$this->exec('must be compatible', 'inheritance_error_const_missing_type.php');
|
|
}
|
|
|
|
public function testFinalConstantCannotBeOverridden()
|
|
{
|
|
$this->exec('Cannot override final constant', 'inheritance_error_const_final.php');
|
|
}
|
|
|
|
public function testConstantVisibilityMismatch()
|
|
{
|
|
$this->exec('must be compatible', 'inheritance_error_const_visibility.php');
|
|
}
|
|
|
|
public function testConstantVisibilityMayBeWidened()
|
|
{
|
|
$this->assertCompiles('inheritance_const_visibility_widen.php');
|
|
}
|
|
|
|
public function testPrivateParentConstantMayBeRedeclared()
|
|
{
|
|
$this->assertCompiles('inheritance_private_const_redeclare.php');
|
|
}
|
|
|
|
public function testPropertyReadonlyMismatch()
|
|
{
|
|
$this->exec('must be compatible', 'inheritance_error_prop_readonly.php');
|
|
}
|
|
|
|
public function testInterfaceMethodMissing()
|
|
{
|
|
$this->exec('must implement method', 'interface_method_missing.php');
|
|
}
|
|
|
|
public function testInterfaceMethodSignatureMismatch()
|
|
{
|
|
$this->exec('must be compatible', 'interface_method_signature_mismatch.php');
|
|
}
|
|
|
|
public function testInterfaceMethodProvidedByTrait()
|
|
{
|
|
$this->assertCompiles('interface_method_from_trait.php');
|
|
}
|
|
|
|
public function testAbstractClassMayDeferInterfaceMethodImplementation()
|
|
{
|
|
$this->assertCompiles('interface_abstract_class_missing.php');
|
|
}
|
|
|
|
public function testAbstractMethodMayImplementInterfaceContract()
|
|
{
|
|
$this->assertCompiles('interface_abstract_method_signature.php');
|
|
}
|
|
|
|
public function testAbstractMethodWithReferenceParameter()
|
|
{
|
|
// 抽象方法的按引用参数签名应被正确识别,基类构造中向未定义变量按引用传参不报错
|
|
$this->assertCompiles('abstract_method_byref.php');
|
|
}
|
|
|
|
public function testAbstractMethodWithReferenceParameterAcrossNamespace()
|
|
{
|
|
// 跨命名空间的抽象方法按引用参数签名应被正确识别(使用完全限定类名解析)
|
|
$this->assertCompiles('abstract_method_byref_namespace.php');
|
|
}
|
|
|
|
public function testInterfaceMethodWithReferenceParameter()
|
|
{
|
|
// 接口的按引用方法签名应被正确识别
|
|
$this->assertCompiles('abstract_method_byref_interface.php');
|
|
}
|
|
|
|
public function testInterfaceTypedReceiverWithReferenceParameter()
|
|
{
|
|
// 接口类型接收者必须从接口及其父接口解析按引用参数签名。
|
|
$this->assertCompiles('abstract_method_byref_interface_typed.php');
|
|
}
|
|
|
|
public function testAbstractMethodWithReferenceParameterMultilevel()
|
|
{
|
|
// 多级继承下,沿父类链查找抽象方法的按引用参数签名
|
|
$this->assertCompiles('abstract_method_byref_multilevel.php');
|
|
}
|
|
|
|
public function testAbstractInterfaceMethodSignatureMismatch()
|
|
{
|
|
$this->exec('must be compatible', 'interface_abstract_method_mismatch.php');
|
|
}
|
|
|
|
public function testInterfaceArrayConstantInitializesRuntimeValue()
|
|
{
|
|
global $translator;
|
|
$compiler = CompilerTest::create(ROOT_PATH);
|
|
$translator = $compiler;
|
|
$testFile = __DIR__ . '/../code/interface_array_constant.php';
|
|
$compiler->addFiles([$testFile]);
|
|
$compiler->prepareFile($testFile);
|
|
$compiler->convertFile($testFile);
|
|
$extensionFile = $compiler->genExtension();
|
|
|
|
$this->assertStringContainsString('php::updateConstant("InterfaceArrayConstant", "ITEMS"', file_get_contents($extensionFile));
|
|
}
|
|
|
|
public function testInterfaceArrayConstantPropagatesToImplementingClass()
|
|
{
|
|
global $translator;
|
|
$compiler = CompilerTest::create(ROOT_PATH);
|
|
$translator = $compiler;
|
|
$testFile = __DIR__ . '/../code/interface_array_constant_implements.php';
|
|
$compiler->addFiles([$testFile]);
|
|
$compiler->prepareFile($testFile);
|
|
$cppFile = $compiler->convertFile($testFile);
|
|
$extensionFile = $compiler->genExtension();
|
|
|
|
$this->assertStringContainsString('php_interfacearrayconstantcontract__items', file_get_contents($cppFile));
|
|
$this->assertStringContainsString('php::updateConstant("InterfaceArrayConstantImpl", "ITEMS"', file_get_contents($extensionFile));
|
|
}
|
|
|
|
public function testConcreteClassMustImplementInheritedAbstractMethod()
|
|
{
|
|
$this->exec('must implement abstract method', 'abstract_parent_method_missing.php');
|
|
}
|
|
|
|
public function testConcreteClassMustImplementInheritedInterfaceMethod()
|
|
{
|
|
$this->exec('must implement method', 'interface_abstract_parent_missing.php');
|
|
}
|
|
|
|
public function testCompatibleTraitMemberDuplicatesCompile()
|
|
{
|
|
$this->assertCompiles('trait_member_compatible.php');
|
|
}
|
|
|
|
public function testTraitConstantConflict()
|
|
{
|
|
$this->exec('constant `VALUE` conflicts', 'trait_constant_conflict.php');
|
|
}
|
|
|
|
public function testTraitPropertyConflict()
|
|
{
|
|
$this->exec('property `count` conflicts', 'trait_property_conflict.php');
|
|
}
|
|
|
|
public function testAbstractMethodSignatureMismatch()
|
|
{
|
|
$this->exec('must be compatible', 'abstract_method_signature_mismatch.php');
|
|
}
|
|
|
|
public function testTraitMethodMustBeCompatibleWithParent()
|
|
{
|
|
// A trait method flattened into a class must remain signature-compatible
|
|
// with any same-named parent method, just like a directly-declared
|
|
// override. Without this check the incompatibility only surfaces as a
|
|
// runtime fatal error that the compiled binary would otherwise ignore.
|
|
$this->exec('must be compatible', 'trait-method-override-incompatible.php');
|
|
}
|
|
|
|
public function testTraitMethodCannotOverrideFinalParentMethod()
|
|
{
|
|
$this->exec('Cannot override final method', 'trait-method-override-final.php');
|
|
}
|
|
|
|
public function testTraitParentCallRequiresParentClass()
|
|
{
|
|
$this->exec('does not extend any class', 'trait-parent-without-parent.php');
|
|
}
|
|
}
|
|
|