fix(php): emit type references for promoted constructor properties
PHP 8 constructor property promotion (`__construct(private Repo $repo)`) parses the promoted parameter as `property_promotion_parameter`, not `simple_parameter`. The PHP parameter loop filtered on `simple_parameter` only, so promoted params were skipped entirely: their type emitted no `parameter_type` edge on the constructor, and — because a promoted param is also a real class field — no `field` edge on the class either. A non-promoted param in the same signature still emitted `parameter_type`, so the type reference was silently dropped for exactly the promoted case. The promoted param's type sits in the same direct named-child shape the loop already reads for `simple_parameter`, so widening the filter to accept `property_promotion_parameter` makes the existing type extraction emit the `parameter_type` edge. Additionally, for a promoted param, emit a `field`-context references edge on the class (mirroring the `property_declaration` handler), guarded so it only fires when a parent class is in scope and the target is not the class node itself. Normal `simple_parameter` behaviour is unchanged. Adds a promoted-property constructor to tests/fixtures/sample.php and test_php_constructor_property_promotion_contexts asserting the promoted type appears as both `field` and `parameter_type`, and that a non-promoted param does not leak a field edge.
This commit is contained in:
+14
-1
@@ -4069,8 +4069,14 @@ def _extract_generic(
|
||||
break
|
||||
if params_container is not None:
|
||||
for p in params_container.children:
|
||||
if p.type != "simple_parameter":
|
||||
# PHP 8 constructor property promotion (`__construct(private
|
||||
# Repo $repo)`) parses the promoted param as
|
||||
# property_promotion_parameter, not simple_parameter. Its
|
||||
# type sits in the same direct named child shape, so accept
|
||||
# both here; a promoted param is additionally a class field.
|
||||
if p.type not in ("simple_parameter", "property_promotion_parameter"):
|
||||
continue
|
||||
is_promoted = p.type == "property_promotion_parameter"
|
||||
type_node = None
|
||||
for sub in p.children:
|
||||
if sub.type in ("named_type", "primitive_type", "nullable_type",
|
||||
@@ -4084,6 +4090,13 @@ def _extract_generic(
|
||||
target_nid = ensure_named_node(ref_name, line)
|
||||
if target_nid != func_nid:
|
||||
add_edge(func_nid, target_nid, "references", line, context=ctx)
|
||||
# A promoted param declares a real class field; mirror
|
||||
# the property_declaration field-context edge so the
|
||||
# type is discoverable as a class field too.
|
||||
if is_promoted and parent_class_nid and target_nid != parent_class_nid:
|
||||
fctx = "generic_arg" if role == "generic_arg" else "field"
|
||||
add_edge(parent_class_nid, target_nid, "references",
|
||||
line, context=fctx)
|
||||
return_node = _php_method_return_type_node(node)
|
||||
if return_node is not None:
|
||||
refs = []
|
||||
|
||||
Vendored
+7
@@ -66,6 +66,13 @@ class DataProcessor extends BaseProcessor implements Loggable
|
||||
}
|
||||
}
|
||||
|
||||
class Service
|
||||
{
|
||||
public function __construct(private Result $result, string $label)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
function parseResponse(string $raw): array
|
||||
{
|
||||
return json_decode($raw, true);
|
||||
|
||||
@@ -777,6 +777,16 @@ def test_php_property_parameter_and_return_contexts():
|
||||
assert ("run", "Result") in _edge_labels(r, "references", "return_type")
|
||||
|
||||
|
||||
def test_php_constructor_property_promotion_contexts():
|
||||
# PHP 8 constructor property promotion: a promoted param is both a
|
||||
# constructor parameter (parameter_type) and a class field (field).
|
||||
r = extract_php(FIXTURES / "sample.php")
|
||||
assert ("Service", "Result") in _edge_labels(r, "references", "field")
|
||||
assert ("__construct", "Result") in _edge_labels(r, "references", "parameter_type")
|
||||
# A non-promoted param must not leak a field edge onto the class.
|
||||
assert ("Service", "string") not in _edge_labels(r, "references", "field")
|
||||
|
||||
|
||||
# ── Swift ────────────────────────────────────────────────────────────────────
|
||||
|
||||
def test_swift_no_error():
|
||||
|
||||
Reference in New Issue
Block a user