Skip to content

Commit c0561ac

Browse files
saidctbclaude
andcommitted
Carry an enum's enumerators as the constants they already are
PRIK models a Fortran enumerator as a constant `SemanticVariable`, but the accessibility and re-export inventory never looked at `module.enums`. `_module_declared_names()` listed procedures, derived types, variables and interfaces, so a plain `use` of a module declaring `enumerator :: red = 1` carried nothing for `red`. `_declared_entity_kind()` likewise did not recognize one, so `use colors, only : red` produced a re-export of kind `unknown` -- which a contract may still publish while the module-variable publication machinery, which attaches a second namespace only to a re-export classified `variable`, passes it by. Read enumerators wherever this layer reads a module's variables, as the variables they become. `_module_declaration_dependencies()` reads their initializers too, so an enumerator whose value names an imported constant records that constant as a declaration dependency rather than a publication. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DN6oB7jC7wZXgFmuec4B7Q
1 parent aabd16d commit c0561ac

3 files changed

Lines changed: 89 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ release tags add a leading `v` to the package version.
77

88
## Unreleased
99

10+
- An enum's enumerators are carried by `use` like the constants they are. A
11+
plain `use` of a module declaring `enumerator :: red = 1` carried nothing for
12+
`red`, and naming it in an `only` list produced a re-export of unknown kind,
13+
which the module-variable publication machinery does not attach. Enumerators
14+
are now read as variables wherever this layer reads a module's declarations,
15+
including as declaration dependencies when an enumerator's value names an
16+
imported constant.
17+
1018
- Following a name through an intermediate module applies that module's own
1119
accessibility. A module importing `x` and declaring `private :: x` no longer
1220
passes a route to the declaration behind it, and a module reaching two

prik/semantics/fortran2ir.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1694,6 +1694,18 @@ def _module_interfaces(module: FortranModule):
16941694
if str(getattr(interface, "declaring_scope_kind", "module")).casefold() == "module"
16951695
)
16961696

1697+
@staticmethod
1698+
def _module_enumerators(module: FortranModule):
1699+
"""Return every enumerator one module's enum blocks declare.
1700+
1701+
An enumerator is a named constant the module declares, and PRIK models
1702+
it as one: a ``use`` carries it exactly as it carries a ``parameter``,
1703+
so this layer reads it wherever it reads the module's variables.
1704+
"""
1705+
return tuple(
1706+
enumerator for enum in getattr(module, "enums", ()) for enumerator in getattr(enum, "enumerators", ())
1707+
)
1708+
16971709
@staticmethod
16981710
def _module_declared_names(module: FortranModule) -> set[str]:
16991711
"""Return the names declared by one module for accessibility resolution.
@@ -1703,11 +1715,14 @@ def _module_declared_names(module: FortranModule) -> set[str]:
17031715
it, which is what another module imports to write a ``procedure(...)``
17041716
declaration. A specific inside an ordinary generic is not separately
17051717
declared here, because the generic is the name that block introduces.
1718+
An enumerator is a declared constant, so it is named here as a variable
1719+
is, which is what it becomes.
17061720
"""
17071721
return {
17081722
*(procedure.name.casefold() for procedure in module.procedures),
17091723
*(derived.name.casefold() for derived in module.derived_types),
17101724
*(variable.name.casefold() for variable in getattr(module, "variables", ())),
1725+
*(enumerator.name.casefold() for enumerator in FortranToIRConverter._module_enumerators(module)),
17111726
*(
17121727
interface.name.casefold()
17131728
for interface in FortranToIRConverter._module_interfaces(module)
@@ -1779,6 +1794,10 @@ def add_procedure(procedure: FortranProcedureSignature) -> None:
17791794
for interface in module.interfaces:
17801795
for procedure in interface.procedures:
17811796
add_procedure(procedure)
1797+
for enumerator in cls._module_enumerators(module):
1798+
declaration_text.extend(
1799+
str(value) for value in (enumerator.symbolic_value, enumerator.value) if value is not None
1800+
)
17821801

17831802
return {
17841803
identifier.casefold()
@@ -2009,6 +2028,10 @@ def _declared_entity_kind(declaring: FortranModule | None, source_name: str) ->
20092028
return "derived_type"
20102029
if any(variable.name.casefold() == key for variable in getattr(declaring, "variables", ())):
20112030
return "variable"
2031+
# An enumerator is a named constant, which is the representation it
2032+
# already has downstream, so a route reaching one names a variable.
2033+
if any(enumerator.name.casefold() == key for enumerator in FortranToIRConverter._module_enumerators(declaring)):
2034+
return "variable"
20122035
return "unknown"
20132036

20142037
@staticmethod

tests/fortran/modules/semantics/test_reexport_accessibility.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,3 +714,61 @@ def test_an_ordinary_chain_still_reaches_the_declaring_module(tmp_path: Path):
714714
"a_mod",
715715
"x",
716716
)
717+
718+
719+
def test_an_enumerator_is_carried_and_classified_as_the_constant_it_is(tmp_path: Path):
720+
"""An enum names constants, which is how every later stage models them."""
721+
modules = _project_modules(
722+
tmp_path,
723+
"""\
724+
module colors_mod
725+
implicit none
726+
enum, bind(c)
727+
enumerator :: red = 1
728+
enumerator :: green = 2
729+
end enum
730+
end module colors_mod
731+
732+
module facade_mod
733+
use colors_mod
734+
implicit none
735+
end module facade_mod
736+
737+
module named_facade_mod
738+
use colors_mod, only : red
739+
implicit none
740+
end module named_facade_mod
741+
""",
742+
)
743+
744+
# A plain `use` carries every public name, enumerators included.
745+
carried = {item.local_name: item.entity_kind for item in modules["facade_mod"].reexports}
746+
assert carried == {"red": "variable", "green": "variable"}
747+
748+
named = {item.local_name: item for item in modules["named_facade_mod"].reexports}
749+
assert named["red"].entity_kind == "variable"
750+
assert (named["red"].origin_module, named["red"].source_name) == ("colors_mod", "red")
751+
752+
753+
def test_an_enumerator_initializer_is_a_declaration_dependency(tmp_path: Path):
754+
"""A name an enum's value reads expresses a declaration, so it is a dependency."""
755+
modules = _project_modules(
756+
tmp_path,
757+
"""\
758+
module constants_mod
759+
implicit none
760+
integer, parameter :: base = 10
761+
end module constants_mod
762+
763+
module colors_mod
764+
use constants_mod, only : base
765+
implicit none
766+
enum, bind(c)
767+
enumerator :: red = base
768+
end enum
769+
end module colors_mod
770+
""",
771+
)
772+
773+
reexports = {item.local_name: item for item in modules["colors_mod"].reexports}
774+
assert reexports["base"].declaration_dependency is True

0 commit comments

Comments
 (0)