Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 7 additions & 1 deletion graphify/extractors/solidity.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,13 @@ def add_member(
value_name = _read_text(value, source)
value_id = _make_id(enum_id, value_name)
add_node(value_id, value_name, value, kind="enum_value")
add_edge(enum_id, value_id, "contains", value)
# An enum value is a discriminant case, not a declared
# field, so it gets `case_of` like every other language
# with enums (Java #1719, C#, Swift, Rust, VB.NET). The
# relation also matters to resolution: `case_of` targets
# are excluded from constructor binding, so an enum value
# named like a type can no longer be mistaken for one.
add_edge(enum_id, value_id, "case_of", value)
continue

simple_kinds = {
Expand Down
34 changes: 34 additions & 0 deletions tests/test_solidity_extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,40 @@ def test_solidity_types_imports_inheritance_overloads_and_modifiers(tmp_path):
assert len([edge for edge in result["edges"] if edge["relation"] == "imports_from"]) == 2


def test_solidity_enum_values_emit_case_of_not_contains(tmp_path):
"""A Solidity enum value is a discriminant case, so it must get a `case_of`
edge like every other language with enums (Java #1719, C#, Swift, Rust,
VB.NET), not the `contains` edge used for real declared members. Solidity
was routing enum values through the same `contains` path as struct fields.
The relation also matters to resolution: `case_of` targets are excluded
from constructor binding, so an enum value named like a type can no longer
be mistaken for one.
"""
source = tmp_path / "Status.sol"
source.write_text(
"pragma solidity ^0.8.0;\n"
"contract Order {\n"
" enum Status { Pending, Shipped, Delivered }\n"
" struct Point { uint x; uint y; }\n"
" Status public status;\n"
"}\n",
encoding="utf-8",
)

result = extract([source], cache_root=tmp_path)

case_of = _edge_labels(result, "case_of")
contains = _edge_labels(result, "contains")
# Each enum value hangs off its enum via case_of, not contains.
assert ("Status", "Pending") in case_of
assert ("Status", "Shipped") in case_of
assert ("Status", "Delivered") in case_of
assert ("Status", "Pending") not in contains
# A real struct field is a declaration, not a case: it keeps `contains`.
assert ("Point", "x") in contains
assert ("Point", "x") not in case_of


def test_solidity_fixture_uses_normal_extract_path(tmp_path):
result = extract([FIXTURE], cache_root=tmp_path)

Expand Down
Loading