Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Non-clean rebuild for mutually dependent grammars is still sometimes broken due to default ref sets #673

Open
krame505 opened this issue Jul 9, 2022 · 0 comments
Labels

Comments

@krame505
Copy link
Member

krame505 commented Jul 9, 2022

Even with #670, there are still some situations for which a non-clean build can fail after some changes that should be okay. Consider the following:

grammar a;
imports b;

inherited attribute env1::[String];
inherited attribute env2::[String];

nonterminal Thing with env1, env2, foo;
flowtype Thing = decorate {env1, env2};

production thing
top::Thing ::=
{ top.foo = [top]; }
grammar b;
imports a;

synthesized attribute getStuff::[Decorated Thing];

Here the type of the foo attribute is determined by the default reference set of Thing, and the type of the occurs-on relation for foo on Thing is determined by the type of foo declared in the other grammar. This means that if we change the reference set in grammar a to just env, then we rebuild grammar a (using b's interface file), then see that it's interface file is dirty, so we go rebuild grammar b. However we don't go rebuild grammar a again! We check it for errors using its initial build, which saw the old interface file for b, and think that the equation for foo has the wrong type!

The reason that we used b's interface file in the first place is that we didn't know before compiling and type checking a that b did, in fact need to be recompiled. As things stand, the only workable solution would be to compile a twice, which in this case ends up doing more work than a clean build would have. Actually making this change would make the build process even more complicated.

This problem fundamentally stems from that declarations in one grammar can change the interface of another grammar -- which is not the case in any other well-designed language, AFAIK. (I'm discounting C++ from being a well-designed language...) I think there are a few possible ways forward:

  1. Get rid of default reference sets, or require writing out the full reference set in every global signature type. This is a pretty annoying change, as having to manually specify (and update) a complicated default reference set of a nonterminal in a bunch of places is really annoying in practice.
  2. Move to a multi-pass compilation model, where we always re-parse all grammars, extract all information that can change the interfaces of dependant grammars (i.e. just default reference sets, I think), and then build everything like usual using interface files for the rest of the environment. This would be a big headache to refactor, and always parsing everything would be slower.
  3. Change the build process to identify mutually-recursive clusters of grammars, and if any grammar in a cluster changes, then we rebuild all grammars in the cluster. Implementing this would be a pretty big change, that I definitely don't feel like doing any time soon. Although I think this might be similar to something that @remexre was proposing at one point?
  4. Live with it, and build with --clean when changing a default reference set.

Honestly, I don't think that 4 is that bad of an option. This bug doesn't cause any java compilation issues when --clean is omitted, since reference set info is only used in the type/flow analyses. Also, we still don't properly look for changes in inferred flow defs when considering whether a grammar is dirty, so for the moment we still need to build with --clean to guarantee that all flow errors are reported properly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant