From 297075c3f39a2085537e7bfddfba2b73e410ce7a Mon Sep 17 00:00:00 2001 From: Synvoya <16019863+Synvoya@users.noreply.github.com> Date: Wed, 1 Jul 2026 22:10:25 +1000 Subject: [PATCH] fix(systemverilog): emit field references for qualified class properties The SystemVerilog class-body field regex in _augment_systemverilog_semantics matched only unqualified ` ;` declarations. Its `^\s*` prefix consumes leading whitespace but not leading class-property qualifiers, so a qualified field such as `rand Config m_cfg;` (three tokens) failed the two-token shape and its type reference was silently dropped from the graph. Consume optional leading qualifiers (rand/randc/local/protected/static/const/ automatic/var) before the type token. Zero qualifiers preserves the existing behavior; the type and name capture are unchanged. Adds test_systemverilog_qualified_field_references plus rand- and protected-qualified fields (and a Config class) to the shared .sv fixture. --- graphify/extract.py | 6 +++++- tests/fixtures/sample.sv | 5 +++++ tests/test_languages.py | 12 ++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/graphify/extract.py b/graphify/extract.py index 9b51db18..6e8ab996 100644 --- a/graphify/extract.py +++ b/graphify/extract.py @@ -6637,7 +6637,11 @@ def _augment_systemverilog_semantics( body, flags=re.DOTALL, ) - for field in re.finditer(r"^\s*([A-Za-z_]\w*(?:\s*#\s*\([^;]+?\))?)\s+\w+\s*;", body_without_functions, re.MULTILINE): + # Optional leading class-property qualifiers (rand/local/protected/etc.) + # must be consumed: otherwise a qualified field like `rand Config x;` + # (three tokens) fails the ` ;` shape and its type reference + # is silently dropped. + for field in re.finditer(r"^\s*(?:(?:rand|randc|local|protected|static|const|automatic|var)\s+)*([A-Za-z_]\w*(?:\s*#\s*\([^;]+?\))?)\s+\w+\s*;", body_without_functions, re.MULTILINE): # Count to the start of the type token (group 1), not the match # start: `^\s*` consumes the leading newline(s), so field.start() # would resolve to the class's line instead of the field's. diff --git a/tests/fixtures/sample.sv b/tests/fixtures/sample.sv index ad8ce4e9..2bdaa8a6 100644 --- a/tests/fixtures/sample.sv +++ b/tests/fixtures/sample.sv @@ -10,12 +10,17 @@ endclass class Payload; endclass +class Config; +endclass + class Result #(type T = Payload); T value; endclass class DataProcessor extends BaseProcessor implements Processor; Result #(Payload) current; + rand Config m_cfg; + protected BaseProcessor m_parent; function Result #(Payload) build(Payload input); return current; diff --git a/tests/test_languages.py b/tests/test_languages.py index 28775a79..941fcac8 100644 --- a/tests/test_languages.py +++ b/tests/test_languages.py @@ -2651,6 +2651,18 @@ def test_systemverilog_field_parameter_return_and_generic_contexts(): assert ("build", "Payload") in _edge_labels(r, "references", "generic_arg") +def test_systemverilog_qualified_field_references(): + """Class properties with leading qualifiers (rand/local/protected/etc.) must + still emit `references` field edges. The field regex only matched unqualified + ` ;` declarations, so `rand Config x;` (three tokens) failed to + match and its type reference was silently dropped. + """ + r = extract_verilog(FIXTURES / "sample.sv") + field_refs = _edge_labels(r, "references", "field") + assert ("DataProcessor", "Config") in field_refs, "rand-qualified field dropped" + assert ("DataProcessor", "BaseProcessor") in field_refs, "protected-qualified field dropped" + + def test_systemverilog_does_not_emit_type_parameter_refs(): r = extract_verilog(FIXTURES / "sample.sv") assert ("Result", "T") not in _edge_labels(r, "references", "field")