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

Resolving deep mixins #11082

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
d8bea79
Part 1. Fail tests of named mixins with object methods
issidorov Mar 26, 2024
a1992a7
Part 1. Moving code into the handleMixins method
issidorov Feb 4, 2024
8cf6dbf
Part 1. Add variable method_exists
issidorov Feb 4, 2024
0e665e5
Part 1. Extracting variables in the handleRegularMixins method
issidorov Feb 4, 2024
72817c1
Part 1. Resolve tests of named mixins with object methods
issidorov Feb 4, 2024
bfd2937
Part 2. Fail tests of templated mixins with object methods
issidorov Mar 26, 2024
fecae81
Part 2. Refactoring the handleTemplatedMixins method
issidorov Feb 4, 2024
229c072
Part 2. Overriding the fq_class_name variable
issidorov Feb 5, 2024
95e2e54
Part 2. Resolve tests of templated mixins with object methods
issidorov Feb 5, 2024
cd71295
Part 3. Regression tests of combined mixins with object methods
issidorov Mar 26, 2024
c5ea30e
Part 4. Fail tests of named mixins with static methods
issidorov Feb 15, 2024
ecbb340
Part 4. Moving code into the handleRegularMixins method
issidorov Feb 15, 2024
0d0a8b5
Part 4. Resolve tests of named mixins with static methods
issidorov Feb 16, 2024
08b50d6
Part 5. Fail tests of mixins with properties
issidorov Feb 17, 2024
d681ca3
Part 5. Move code to method handleRegularMixins
issidorov Feb 17, 2024
0ce8418
Part 5. Resolve test of mixins with properties
issidorov Feb 19, 2024
635d38e
Part 6. Fix mixin collisions on object methods
issidorov Mar 27, 2024
83b437a
Part 6. Fix mixin collisions on static methods
issidorov Mar 27, 2024
b206f1a
Part 6. Fix mixin collisions on properties
issidorov Mar 27, 2024
021c77b
Part 7. Success regression tests with UndefinedMixinClass
issidorov Sep 12, 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
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
use function array_values;
use function count;
use function get_class;
use function in_array;
use function reset;
use function strtolower;

Expand Down Expand Up @@ -295,45 +296,22 @@ public static function analyze(
}
}

$naive_method_exists = false;

// @mixin attributes are an absolute pain! Lots of complexity here,
// as they can redefine the called class, method id etc.
if ($class_storage->templatedMixins
&& $lhs_type_part instanceof TGenericObject
&& $class_storage->template_types
) {
[$lhs_type_part, $class_storage, $naive_method_exists, $method_id, $fq_class_name]
= self::handleTemplatedMixins(
$class_storage,
$lhs_type_part,
$method_name_lc,
$codebase,
$context,
$method_id,
$source,
$stmt,
$statements_analyzer,
$fq_class_name,
);
} elseif ($class_storage->mixin_declaring_fqcln
&& $class_storage->namedMixins
) {
[$lhs_type_part, $class_storage, $naive_method_exists, $method_id, $fq_class_name]
= self::handleRegularMixins(
$class_storage,
$lhs_type_part,
$method_name_lc,
$codebase,
$context,
$method_id,
$source,
$stmt,
$statements_analyzer,
$fq_class_name,
$lhs_var_id,
);
}
[$lhs_type_part, $class_storage, , $naive_method_exists, $method_id, $fq_class_name]
= self::handleMixins(
$class_storage,
$lhs_type_part,
$method_name_lc,
$codebase,
$context,
$method_id,
$source,
$stmt,
$statements_analyzer,
$fq_class_name,
$lhs_var_id,
);
}

$all_intersection_return_type = null;
Expand Down Expand Up @@ -725,7 +703,81 @@ private static function handleInvalidClass(

/**
* @param lowercase-string $method_name_lc
* @return array{TNamedObject, ClassLikeStorage, bool, MethodIdentifier, string}
* @param string[] $ignore_mixins
* @return array{TNamedObject, ClassLikeStorage, bool, bool, MethodIdentifier, string}
*/
private static function handleMixins(
ClassLikeStorage $class_storage,
TNamedObject $lhs_type_part,
string $method_name_lc,
Codebase $codebase,
Context $context,
MethodIdentifier $method_id,
StatementsSource $source,
PhpParser\Node\Expr\MethodCall $stmt,
StatementsAnalyzer $statements_analyzer,
string $fq_class_name,
?string $lhs_var_id,
array $ignore_mixins = []
): array {
$method_exists = false;
$naive_method_exists = false;

// @mixin attributes are an absolute pain! Lots of complexity here,
// as they can redefine the called class, method id etc.
if ($class_storage->templatedMixins
&& $lhs_type_part instanceof TGenericObject
&& $class_storage->template_types
) {
[$lhs_type_part, $class_storage, $method_exists, $naive_method_exists, $method_id, $fq_class_name]
= self::handleTemplatedMixins(
$class_storage,
$lhs_type_part,
$method_name_lc,
$codebase,
$context,
$method_id,
$source,
$stmt,
$statements_analyzer,
$fq_class_name,
$lhs_var_id,
$ignore_mixins,
);
} elseif ($class_storage->mixin_declaring_fqcln
&& $class_storage->namedMixins
) {
[$lhs_type_part, $class_storage, $method_exists, $naive_method_exists, $method_id, $fq_class_name]
= self::handleRegularMixins(
$class_storage,
$lhs_type_part,
$method_name_lc,
$codebase,
$context,
$method_id,
$source,
$stmt,
$statements_analyzer,
$fq_class_name,
$lhs_var_id,
$ignore_mixins,
);
}

return [
$lhs_type_part,
$class_storage,
$method_exists,
$naive_method_exists,
$method_id,
$fq_class_name,
];
}

/**
* @param lowercase-string $method_name_lc
* @param string[] $ignore_mixins
* @return array{TNamedObject, ClassLikeStorage, bool, bool, MethodIdentifier, string}
*/
private static function handleTemplatedMixins(
ClassLikeStorage $class_storage,
Expand All @@ -737,10 +789,15 @@ private static function handleTemplatedMixins(
StatementsSource $source,
PhpParser\Node\Expr\MethodCall $stmt,
StatementsAnalyzer $statements_analyzer,
string $fq_class_name
string $fq_class_name,
?string $lhs_var_id,
array $ignore_mixins
): array {
$method_exists = false;
$naive_method_exists = false;

$ignore_mixins[] = $fq_class_name;

if ($class_storage->templatedMixins
&& $lhs_type_part instanceof TGenericObject
&& $class_storage->template_types
Expand Down Expand Up @@ -773,6 +830,8 @@ private static function handleTemplatedMixins(
$lhs_type_part_new->value,
);

$mixin_fq_class_name = $mixin_class_storage->name;

if ($codebase->methods->methodExists(
$new_method_id,
$context->calling_method_id,
Expand All @@ -787,15 +846,44 @@ private static function handleTemplatedMixins(
true,
$context->insideUse(),
)) {
$lhs_type_part = $lhs_type_part_new;
$class_storage = $mixin_class_storage;

$method_exists = true;
$naive_method_exists = true;
$method_id = $new_method_id;
} elseif (isset($mixin_class_storage->pseudo_methods[$method_name_lc])) {
}

if (!$method_exists) {
$method_exists = isset($mixin_class_storage->pseudo_methods[$method_name_lc]);
}

if (!$method_exists && !in_array($mixin_fq_class_name, $ignore_mixins)) {
[
$lhs_type_part_new,
$mixin_class_storage,
$method_exists,
$naive_method_exists,
$new_method_id,
$mixin_fq_class_name,
] = self::handleMixins(
$mixin_class_storage,
$lhs_type_part_new,
$method_name_lc,
$codebase,
$context,
$new_method_id,
$source,
$stmt,
$statements_analyzer,
$mixin_fq_class_name,
$lhs_var_id,
$ignore_mixins,
);
}

if ($method_exists) {
$lhs_type_part = $lhs_type_part_new;
$class_storage = $mixin_class_storage;
$method_id = $new_method_id;
$fq_class_name = $mixin_fq_class_name;
break;
}
}
}
Expand All @@ -806,6 +894,7 @@ private static function handleTemplatedMixins(
return [
$lhs_type_part,
$class_storage,
$method_exists,
$naive_method_exists,
$method_id,
$fq_class_name,
Expand All @@ -814,7 +903,8 @@ private static function handleTemplatedMixins(

/**
* @param lowercase-string $method_name_lc
* @return array{TNamedObject, ClassLikeStorage, bool, MethodIdentifier, string}
* @param string[] $ignore_mixins
* @return array{TNamedObject, ClassLikeStorage, bool, bool, MethodIdentifier, string}
*/
private static function handleRegularMixins(
ClassLikeStorage $class_storage,
Expand All @@ -827,14 +917,59 @@ private static function handleRegularMixins(
PhpParser\Node\Expr\MethodCall $stmt,
StatementsAnalyzer $statements_analyzer,
string $fq_class_name,
?string $lhs_var_id
?string $lhs_var_id,
array $ignore_mixins
): array {
$method_exists = false;
$naive_method_exists = false;

$ignore_mixins[] = $fq_class_name;

foreach ($class_storage->namedMixins as $mixin) {
if (!$class_storage->mixin_declaring_fqcln) {
continue;
}
if (!$codebase->classlike_storage_provider->has($mixin->value)) {
continue;
}

$mixin_declaring_class_storage = $codebase->classlike_storage_provider->get(
$class_storage->mixin_declaring_fqcln,
);

$mixin_class_template_params = ClassTemplateParamCollector::collect(
$codebase,
$mixin_declaring_class_storage,
$codebase->classlike_storage_provider->get($fq_class_name),
null,
$lhs_type_part,
$lhs_var_id === '$this',
);

$mixin_lhs_type_part = $mixin->replaceTemplateTypesWithArgTypes(
new TemplateResult([], $mixin_class_template_params ?: []),
$codebase,
);

$lhs_type_expanded = TypeExpander::expandUnion(
$codebase,
new Union([$mixin_lhs_type_part]),
$mixin_declaring_class_storage->name,
$fq_class_name,
$class_storage->parent_class,
true,
false,
$class_storage->final,
);

$_lhs_type_part = $lhs_type_expanded->getSingleAtomic();
if ($_lhs_type_part instanceof TNamedObject) {
$mixin_lhs_type_part = $_lhs_type_part;
}

$mixin_class_storage = $codebase->classlike_storage_provider->get($mixin->value);

$mixin_fq_class_name = $mixin_class_storage->name;

$new_method_id = new MethodIdentifier(
$mixin->value,
Expand All @@ -855,54 +990,48 @@ private static function handleRegularMixins(
true,
$context->insideUse(),
)) {
$mixin_declaring_class_storage = $codebase->classlike_storage_provider->get(
$class_storage->mixin_declaring_fqcln,
);

$mixin_class_template_params = ClassTemplateParamCollector::collect(
$codebase,
$mixin_declaring_class_storage,
$codebase->classlike_storage_provider->get($fq_class_name),
null,
$lhs_type_part,
$lhs_var_id === '$this',
);

$lhs_type_part = $mixin->replaceTemplateTypesWithArgTypes(
new TemplateResult([], $mixin_class_template_params ?: []),
$codebase,
);
$method_exists = true;
$naive_method_exists = true;
}

$lhs_type_expanded = TypeExpander::expandUnion(
if (!$method_exists && !in_array($mixin_fq_class_name, $ignore_mixins)) {
[
$mixin_lhs_type_part,
$mixin_class_storage,
$method_exists,
$naive_method_exists,
$new_method_id,
$mixin_fq_class_name,
] = self::handleMixins(
$mixin_class_storage,
$mixin_lhs_type_part,
$method_name_lc,
$codebase,
new Union([$lhs_type_part]),
$mixin_declaring_class_storage->name,
$fq_class_name,
$class_storage->parent_class,
true,
false,
$class_storage->final,
$context,
$new_method_id,
$source,
$stmt,
$statements_analyzer,
$mixin_fq_class_name,
$lhs_var_id,
$ignore_mixins,
);
}

$new_lhs_type_part = $lhs_type_expanded->getSingleAtomic();

if ($new_lhs_type_part instanceof TNamedObject) {
$lhs_type_part = $new_lhs_type_part;
}

$mixin_class_storage = $codebase->classlike_storage_provider->get($mixin->value);

$fq_class_name = $mixin_class_storage->name;
if ($method_exists) {
$lhs_type_part = $mixin_lhs_type_part;
$fq_class_name = $mixin_fq_class_name;
$mixin_class_storage->mixin_declaring_fqcln = $class_storage->mixin_declaring_fqcln;
$class_storage = $mixin_class_storage;
$naive_method_exists = true;
$method_id = $new_method_id;
break;
}
}

return [
$lhs_type_part,
$class_storage,
$method_exists,
$naive_method_exists,
$method_id,
$fq_class_name,
Expand Down
Loading
Loading