Skip to content

(Closes #1734 #2975 #1258 #2949) Add stricter checking for symbols#3358

Open
arporter wants to merge 68 commits intomasterfrom
1734_stricter_symbols
Open

(Closes #1734 #2975 #1258 #2949) Add stricter checking for symbols#3358
arporter wants to merge 68 commits intomasterfrom
1734_stricter_symbols

Conversation

@arporter
Copy link
Member

@arporter arporter commented Mar 2, 2026

I've broken this out of the work on #1823 - that work revealed issues with our Symbol handling so I thought I might as well do the job properly.

We now no longer add a wildcard import of constants_mod in LFRic generated code. This revealed a few bugs/misssing symbols that I've now fixed.

arporter and others added 30 commits January 21, 2026 13:21
@arporter arporter marked this pull request as ready for review March 4, 2026 14:54
@arporter
Copy link
Member Author

arporter commented Mar 4, 2026

Despite what it says above, if I go to CodeCov then it says there are 0 indirect coverage changes.
Therefore, I declare this ready for review. Probably one for @sergisiso.

Copy link
Collaborator

@sergisiso sergisiso left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a great step to make symbols internally consistent, thanks @arporter. There are several related issues that I would like to understand where this change leave us.

Comment on lines +128 to +129
if module in self._symtab.symbols:
mod_sym_tab = self._symtab
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems odd that the find_symbol_table(self._symtab.node) below doesn't already search the given scope as the parameter is :param node: the PSyIR node from which to search. but the implementation starts with a node.scope, wich means the ancestor scope.

Could you check if including the given scope in the search would break other things. I think this would match the expected behaviour of this method name. And even if it breaks other things should those other calls then be ...find_symbol_table(parent) instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is odd. I've just checked node.scope though and it does:

 node = self.ancestor(ScopingNode, include_self=True)

so it will return its own table first. I think the problem is that I hacked this code in towards the end because I hit situations where self._symtab.node was None. I'll refactor it.

# A mapping of 'i_def' etc. to the corresponding DataSymbol
_precision_map = {}

def __init__(self, node=None, default_visibility=Symbol.Visibility.PUBLIC):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always happy to see methods deleted from this class, as we have a good strategy to do DSL nodes (by uplifting-lowering), but subclassing other parts of PSyclone for API-specific logic is problematic (discussed here: #1954).

This PR is big enough, but do you think we could fully remove this class? This is now just helpers to create symbols with the LFRic precisions, but the generic find_or_create already accepts a datatype argument to specify these.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes we could. I started doing it and it was going well but it got quite big so I decided to do the Right Thing and break it into #3366.

_precision_map = {}

def __init__(self, node=None, default_visibility=Symbol.Visibility.PUBLIC):
super().__init__(node, default_visibility)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, asking about related issues, does this PR bring any update to #2659 #1258 or #2975?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arguably, #1258 could already be closed but I'll add it to the list for this PR. Unfortunately this PR doesn't do anything to address the root of #2659 - that is a harder problem. (Although it will improve the consistency of a tree as soon as it is copied.) #2975 will be closed by this (thanks for spotting).

Comment on lines +473 to +475
# Take care in case we've found an existing ContainerSymbol and
# it's in an outer scope.
actual_table = container.find_symbol_table(symbol_table.node)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am a bit confused about why do we need here, is it to "declare the new imported symbols in the same scope as container they come from"? If thats the case this may be a better comment.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, updated

# Reorganise the symbol table construction to occur before we add
# the children.
self._symbol_table = other.symbol_table.deep_copy(self)
self._symbol_table = other.symbol_table.deep_copy(new_node=self)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not the fault of this PR but I always found that argument name strange. Could we replace "new_node" to "attached_to"? Since the symtab method that does the same is call "attach"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yes, that's a good idea. I also realised that we didn't check the type of the supplied node so I've added that plus test.

Comment on lines +295 to +296
new_sym.interface.container_symbol = new_st.find_or_create(
name, symbol_type=ContainerSymbol)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a bit unexpected that deep_copy creates new symbols or changes the name of symbols (find_or_create behaviour). But I understand this is necessary if the symbol has a dependency to an outer scope AND we change the associated node, which could make a lookup go wrong. Is this the reason?

I will be happier if we extend the second paragraph of the docstring to explain this behaviour as a consequence of providing a different associated scope node.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Note that it won't change the name of a Symbol but it will add a new ContainerSymbol if none is found. I tried removing this and ~70 tests broke, primarily because of the way we create a temporary copy of a SymbolTable within LFRicKern.reference_accesses(). That's already the subject of #2874 so I've put in a few more TODOs. I've extended the paragraph as you suggested.

However, I've also realised that perhaps I should be stricter about imported Symbols. As things are now, I check that a suitable ContainerSymbol is in scope when a Symbol with ImportInterface is added to a table. We could tighten this further to require that the ContainerSymbol is in the same table as the imported symbol. What do you think?

other_sym.name, other_table=other_table))
self.update_import_interface(isym)

def update_symbol_dependencies(self):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"thinking out loud" comment, no need to agree, but I am not sure if "update_" is too generic to find out what these two methods do without reading the docstrings but I am not sure of good alternatives. What I could think of is:

  • localise_symbol_dependencies
  • bind_symbol_dependencies_to_scope

same for the next one where I also wonder if adding "_of" would help make clear is the argument that we update, .e.g. "localise_import_interface_of(symbol)"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I've gone for localise_all_symbol_dependencies and localise_import_interface_of.

refers to a Container in this or an ancestor scope.

If no ContainerSymbol is found with the name of the one in the
ImportInterface of the Symbol, then the ContainerSymboll in the
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"ContainerSymbol"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops

with pytest.raises(symbols.SymbolError) as err:
table.update_symbol_dependencies()
assert ("which is not of the same type as the original dependency"
in str(err.value))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I am not wrong, this test also needs a case with an outer symbol that has been shadowed in the inner scope, but the "update" should not change the interface of an import in the inner scope (something the implementation already accounts for if I understand the comments correctly)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Test added - let me know if it isn't quite what you had in mind.

try:
constants_container = symtab.lookup(const_mod)
except KeyError:
# TODO Once #696 is done, we should *always* have a
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are we with #696?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another good spot - #696 is done so I've closed it. I've just grepped and found no remaining TODOs for it.

@arporter arporter changed the title (Closes #1734) Add stricter checking for symbols (Closes #1734 #2975) Add stricter checking for symbols Mar 10, 2026
@arporter arporter changed the title (Closes #1734 #2975) Add stricter checking for symbols (Closes #1734 #2975 #1258) Add stricter checking for symbols Mar 10, 2026
@arporter arporter changed the title (Closes #1734 #2975 #1258) Add stricter checking for symbols (Closes #1734 #2975 #1258 #2949) Add stricter checking for symbols Mar 10, 2026
@arporter
Copy link
Member Author

Ready for another look now @sergisiso. I've fired off the ITs again...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants