metaborg / nabl

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

Inline functions #66

Open MeAmAnUsername opened 3 years ago

MeAmAnUsername commented 3 years ago

Short description User can select a functional constraint or predicate constraint and use Spoofax > refactoring > inline or Spoofax > refactoring > recursively inline. The first one inlines the body of the function, the second one inlines recursively, i.e. inline body, inline any function calls in that body, inline any function calls in those bodies, etc.

Problem description. I'm currently debugging my Statix spec and it basically is just importing my full project and then doing all this inlining by hand, interleaved with removing elements that are not relevant, until I find the bug. If this is implemented I don't have to do the inlining by hand anymore, only removing the irrelevant details before inlining again.

Describe the solution you'd like Example:

  someFunc(s) :-
    declareVar(s, "this", getThis(s)),
    someOtherFunc(s).

  declareVar(s, name, T) :-
    !var[name, T] in s,
    resolveVar(s, name) == [_] | error $[duplicate variable [name]],
    @name.type := T.

  resolveVar(s, name) = occs :-
    query var
      filter P and { name' :- name == name' }
         min $ < P and true
          in s |-> occs.

User can select declareVar(s, "this", getThis(s))) and use Spoofax > refactoring > inline, which results in

  someFunc(s) :-
    {tmp_1}
    tmp_1 == getThis(s),
    !var["this", tmp_1] in s,
    resolveVar(s, "this") == [_] | error $[duplicate variable "this"],
    @"this".type := tmp_1,
    someOtherFunc(s).

  declareVar(s, name, T) :-
    !var[name, T] in s,
    resolveVar(s, name) == [_] | error $[duplicate variable [name]],
    @name.type := T.

  resolveVar(s, name) = occs :-
    query var
      filter P and { name' :- name == name' }
         min $ < P and true
          in s |-> occs.

User can also select declareVar(s, "this", getThis(s))) and use Spoofax > refactoring > inline recursively, which results in

  someFunc(s) :-
    {tmp_1}
    tmp_1 == getThis(s),
    !var["this", tmp_1] in s,
    query var filter P and { name' :- "this" == name' } min $ < P and true in s |-> [_]  | error $[duplicate variable "this"],
    @"this".type := tmp_1,
    someOtherFunc(s).

  declareVar(s, name, T) :-
    !var[name, T] in s,
    resolveVar(s, name) == [_] | error $[duplicate variable [name]],
    @name.type := T.

  resolveVar(s, name) = occs :-
    query var
      filter P and { name' :- name == name' }
         min $ < P and true
          in s |-> occs.

Open questions If it cannot be determined which rule for a function should be used, there are two options:

  1. Don't inline the function
  2. Inline the common parts, create a helper function which provides rules for the distinct parts. Option 1 is probably better in most cases. It's also possible to ask the user what to do.

The example shows @"this".type := tmp_1, but this is a no-op for strings without origin information (I think). Should this be inlined as well or just discarded? Note that this also did not do anything in the original code (assuming that it is indeed a no-op for strings without origin info).

Describe alternatives you've considered An earlier iteration of this idea had Statix automatically inline everything in a stxtest.

Additional context In the examples, the functions resolveVar and declareVar are now unused. It would be nice if there was an action to find and remove unused functions (just finding would be enough).

AZWN commented 3 years ago

That is a feature I have considered as well, in order to make debugging easier. Also, I think it would be perfectly valid to inline regular constraints as well (not only functions). Thanks for reporting this, that helps me prioritizing :-)

I also think that, when a rule cannot be determined uniquely, not inlining is the best option. Also, we should consider inlining variables, because that can help selecting the correct rule (compare {T} T == INT(), isInt(T) vs. isInt(INT())).