Skip to content

Commit af71e0f

Browse files
committed
fix(scanner): include class template parameters in static method type resolution context
closes #764 Signed-off-by: azjezz <[email protected]>
1 parent f825104 commit af71e0f

File tree

4 files changed

+126
-21
lines changed

4 files changed

+126
-21
lines changed

crates/analyzer/src/statement/class_like/mod.rs

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ use mago_codex::metadata::property::PropertyMetadata;
1313
use mago_codex::misc::GenericParent;
1414
use mago_codex::ttype::TType;
1515
use mago_codex::ttype::atomic::TAtomic;
16+
use mago_codex::ttype::atomic::generic::TGenericParameter;
17+
use mago_codex::ttype::atomic::scalar::TScalar;
18+
use mago_codex::ttype::atomic::scalar::class_like_string::TClassLikeString;
1619
use mago_codex::ttype::comparator::ComparisonResult;
1720
use mago_codex::ttype::comparator::union_comparator;
1821
use mago_codex::ttype::expander::TypeExpansionOptions;
@@ -131,10 +134,24 @@ fn type_contains_template_param(type_union: &TUnion, param_name: Atom, defining_
131134
use mago_codex::ttype::TypeRef;
132135

133136
type_union.types.iter().any(|atomic| {
134-
if let TAtomic::GenericParameter(gp) = atomic
135-
&& gp.parameter_name == param_name
136-
&& let GenericParent::ClassLike(class_name) = gp.defining_entity
137-
&& class_name == defining_class
137+
if let TAtomic::GenericParameter(TGenericParameter {
138+
parameter_name,
139+
defining_entity: GenericParent::ClassLike(class_name),
140+
..
141+
}) = atomic
142+
&& *parameter_name == param_name
143+
&& *class_name == defining_class
144+
{
145+
return true;
146+
}
147+
148+
if let TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::Generic {
149+
parameter_name,
150+
defining_entity: GenericParent::ClassLike(class_name),
151+
..
152+
})) = atomic
153+
&& *parameter_name == param_name
154+
&& *class_name == defining_class
138155
{
139156
return true;
140157
}
@@ -144,6 +161,14 @@ fn type_contains_template_param(type_union: &TUnion, param_name: Atom, defining_
144161
gp.parameter_name == param_name
145162
&& matches!(gp.defining_entity, GenericParent::ClassLike(c) if c == defining_class)
146163
}
164+
TypeRef::Atomic(TAtomic::Scalar(TScalar::ClassLikeString(TClassLikeString::Generic {
165+
parameter_name,
166+
defining_entity,
167+
..
168+
}))) => {
169+
*parameter_name == param_name
170+
&& matches!(defining_entity, GenericParent::ClassLike(c) if *c == defining_class)
171+
}
147172
TypeRef::Union(u) => type_contains_template_param(u, param_name, defining_class),
148173
_ => false,
149174
})
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
abstract class AbstractController
6+
{
7+
}
8+
9+
/**
10+
* @template TEntity of object
11+
*/
12+
interface CrudControllerInterface
13+
{
14+
/**
15+
* @return class-string<TEntity>
16+
*/
17+
public static function getEntityFqcn(): string;
18+
}
19+
20+
/**
21+
* @template TEntity of object
22+
* @implements CrudControllerInterface<TEntity>
23+
*/
24+
abstract class AbstractCrudController extends AbstractController implements CrudControllerInterface
25+
{
26+
abstract public static function getEntityFqcn(): string;
27+
}
28+
29+
/**
30+
* @extends AbstractCrudController<stdClass>
31+
*/
32+
final class CrudController1 extends AbstractCrudController
33+
{
34+
public static function getEntityFqcn(): string
35+
{
36+
return stdClass::class;
37+
}
38+
}
39+
40+
/**
41+
* @extends AbstractCrudController<stdClass>
42+
*/
43+
final class CrudController2 extends AbstractCrudController
44+
{
45+
/**
46+
* {@inheritDoc}
47+
*/
48+
public static function getEntityFqcn(): string
49+
{
50+
return stdClass::class;
51+
}
52+
}
53+
54+
/**
55+
* @extends AbstractCrudController<stdClass>
56+
*/
57+
final class CrudController3 extends AbstractCrudController
58+
{
59+
/**
60+
* @inheritDoc
61+
*/
62+
public static function getEntityFqcn(): string
63+
{
64+
return stdClass::class;
65+
}
66+
}
67+
68+
/**
69+
* @extends AbstractCrudController<stdClass>
70+
*/
71+
final class CrudController4 extends AbstractCrudController
72+
{
73+
/** @return class-string<stdClass> */
74+
public static function getEntityFqcn(): string
75+
{
76+
return stdClass::class;
77+
}
78+
}

crates/analyzer/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,7 @@ test_case!(issue_752);
465465
test_case!(issue_754);
466466
test_case!(issue_755);
467467
test_case!(issue_756);
468+
test_case!(issue_764);
468469

469470
#[test]
470471
fn test_all_test_cases_are_ran() {

crates/codex/src/scanner/mod.rs

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -399,28 +399,29 @@ impl<'ctx, 'arena> MutWalker<'arena, 'arena, Context<'ctx, 'arena>> for Scanner
399399
}
400400

401401
let method_id = (class_like_metadata.name, name);
402-
let type_resolution = if method.is_static() {
403-
if !class_like_metadata.type_aliases.is_empty() || !class_like_metadata.imported_type_aliases.is_empty() {
404-
let mut context = TypeResolutionContext::new();
402+
let type_resolution_context = {
403+
let mut context = self.get_current_type_resolution_context();
405404

406-
for alias_name in class_like_metadata.type_aliases.keys() {
407-
context = context.with_type_alias(*alias_name);
408-
}
409-
410-
for (alias_name, (source_class, original_name, _span)) in &class_like_metadata.imported_type_aliases {
411-
context = context.with_imported_type_alias(*alias_name, *source_class, *original_name);
412-
}
405+
for alias_name in class_like_metadata.type_aliases.keys() {
406+
context = context.with_type_alias(*alias_name);
407+
}
413408

414-
Some(context)
415-
} else {
416-
None
409+
for (alias_name, (source_class, original_name, _span)) in &class_like_metadata.imported_type_aliases {
410+
context = context.with_imported_type_alias(*alias_name, *source_class, *original_name);
417411
}
418-
} else {
419-
Some(self.get_current_type_resolution_context())
412+
413+
context
420414
};
421415

422-
let mut function_like_metadata =
423-
scan_method(method_id, method, &class_like_metadata, context, &mut self.scope, type_resolution);
416+
let mut function_like_metadata = scan_method(
417+
method_id,
418+
method,
419+
&class_like_metadata,
420+
context,
421+
&mut self.scope,
422+
Some(type_resolution_context),
423+
);
424+
424425
let Some(method_metadata) = &function_like_metadata.method_metadata else {
425426
unreachable!("Method info should be present for method.",);
426427
};

0 commit comments

Comments
 (0)