metaborg / nabl

Spoofax' Name Binding Language
Apache License 2.0
7 stars 12 forks source link

Get list of references to an AST node #77

Closed MeAmAnUsername closed 2 years ago

MeAmAnUsername commented 2 years ago

Short description Some way to get the list of nodes referencing an AST node.

Problem description. I want to find unused variables, imports, etc.

Describe the solution you'd like A built-in function (or a library function) that takes an AST node and returns a list of AST nodes: getRefs : astId -> list(astId). Note: I use astId as general AST node, but if there is another way that would work too. Problems with this solution: the returned list is just general AST nodes. Those cannot be used in function that expect a specific sort without casting, which is currently not implemented.

Describe alternatives you've considered 1 To solve the problem, a function to just return whether a node has references would be enough:isUsed : astId -> BOOLEAN. BOOLEAN is not a built-in type though. Additionally, returning a list of references may be useful for purposes besides my current use-case.

2 Detecting unused variables can (probably) also be done with a helper relation where a declaration is made when a variable is used, which can then be checked by the original node. I think this would work without deadlocking, but it seems a little boilerplate-heavy.

relations
  used : string // the name of the variable. Uses string so that it works for every possible name.

rules
  declareVar : scope * VAR_ID * TYPE
  declareVar(s, name, T) :-
    !var[name, T] in s,
    // duplicate checks, etc. omitted for simplicity
    query used in s |-> [_|_] | warning $[variable [name] is unused] @name.

  resolveVar : scope * VAR_ID -> TYPE
  resolveVar(s, name) = T :-
    query var filter {name'' :- name == name'' } in s |-> [(path, (name', T))],
    // could not resolve errors etc. omitted for simplicity
    @name.ref := name',
    !used[name'] in getResolvedScope(path).

  getResolvedScope : path -> scope // get the end scope from a path.

3 Use DynSem to detect and give warnings for unused variables. DynSem can give warnings on useless assignments, so that will almost certainly also work for unused variables. I don't think that this is part of the DynSem domain though.

Additional context Giving warnings on unused variables allows the user to remove those variables, which improves the code quality.

AZWN commented 2 years ago

Thanks for reporting this issue. Note however that a fourth (pretty neat IMHO) alternative exists, which does not require any adaption of Statix, namely defining resolveVar as follows:

  resolveVar(s, name) = T :-
    query var filter {name'' :- name == name'' } in s |-> [(path, (name', T))],
    @name.ref := name',
    @name'.refs += name.

Now, each declaration that has no values in its refs bag property (or only a single one, if resolveVar is used for duplication checking) is effectively unused.

Now, using stx-get-ast-property, this property can be checked for each declaration, and, if needed, appropriate warnings can be generated.

Can you comment on whether this approach is satisfactory for your original problem?

MeAmAnUsername commented 2 years ago

That might work, I'd have to figure out how to generate warnings with Stratego. A cursory glance of the documentation makes it seem like it is not possible to use both Stratego and Statix to generate warnings.

AZWN commented 2 years ago

Ok, as I explained on Slack, I think that is possible. I'm closing this now, but feel free to reopen if it does not work out for some reason.