-
Notifications
You must be signed in to change notification settings - Fork 103
Add context-free rule type that replaces AST nodes with attributes #574
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
Add context-free rule type that replaces AST nodes with attributes #574
Conversation
This change adds a new kind of context-free rule that applies to items with specific attributes, allowing an expand function to replace them with entirely new items of the same type. this is similar to extensions but doesn't require the syntactic overhead those incur. Signed-off-by: Jack Rickard <[email protected]>
Signed-off-by: Jack Rickard <[email protected]>
Signed-off-by: Jack Rickard <[email protected]>
| let loc = Common.loc_of_attribute attr in | ||
| super#payload payload; | ||
| Attribute_table.add not_seen name loc | ||
| Attribute_table.replace not_seen name loc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify this change: some attributes get duplicated in the AST (that is, they point to the same location) but we only want to include them once in not_seen so that remove_seen works as expected.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sounds sane! For future reference, do you have an example of when that happens? Something we can use to reproduce it?
Might actually be worth adding a test for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do but I'm unsure how to test it. OCaml 5.00 (and below) parses let v: (unit [@attr]) = () as let v: (unit [@attr]) = ((): (unit [@attr])) so the attr is visible down both bits of the value_binding causing it to get registered twice. I can't work out how to write a test that migrates the AST in the necessary way to check this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we take it out, does something fail internally here at Jane Street? If so, can we turn that into an external reproduction? If not, should this fix just be a separate PR?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I understand the problem correctly our tests would need to run on OCaml <= 5.0, parse a source file with an attribute set as you described above, and have a ppx driver run on it and "consume" the attribute. Before your fix this should trigger the bug and we should get an unused attribute warning because of the second "shadow" attribute not being marked as seen. After your fix this should go away. Am I correct?
I think you can simply restrict the compiler version under which the test is run, using dune's enabled_if stanza field, as it is done here for example.
You make it sound like it's trickier than that so I might have missed something.
Either way, I think this would be best fixed in its own PR as @ceastlund suggests!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would quite like this change in this PR because ppx_template fails internally without it, however I could move it out and we can maintain the patch while we work out what's going on. However, this is my current understanding:
From AST version 500 and earlier, value_binding would include the attribute in the AST twice, which would cause weirdness described. No-one hit this because nothing used Attribute_table for attributes on value bindings before.
The AST migration from 500 to 501 fixes the duplication, this means that all versions of ppxlib since then can't hit this specific version of the bug, I don't know any other places where attributes get duplicated so I can't write a test for this.
I hit this because of weirdness in our internal toolchain setup that I haven't got to the bottom of.
Given that I'd quite like to include this change, it feels conceptually correct and it fixes an issue for us internally, but I'm relatively sure that it makes no difference to ppxlib's behaviour.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm fine merging this as is, the change seem sane. I was suggesting we add a test to prevent reintroducing it someday but if it's too cumbersome to write a meaningful test, let's go ahead and merge!
|
I think this introduces a bit of confusion as to what the expanded code will look like. The clear split between attributes and extension in our rules so far also helps understand what part of the code stays, and what gets rewritten. ppx_template does not have documentation yet. Could you give me an example of how it's used and what kind of syntactic overhead it introduces when using extension points? |
|
Reading the opam description of the package:
I realize this is meant to be a temporary work around to current compiler limitations. I'm a bit more reluctant to add this to ppxlib if the ppx that justifies this feature is meant to be deleted in the end. Are none of the work around available satisfactory at the moment? One other option is to use a whole AST rewriting pass to deal with those attributes. |
|
Long-term there is probably at least 3 years, and it may well gain new features that keep it alive indefinitely.
and it will do some copy-paste + name-mangling and produce: (Don't worry too much about what that means, the important bit is that it's duplicated the function and done some name-mangling.) The motivation for this change comes at the use sites, at the moment, the code looks like one of these: let do_a_thing =
...
let a = [%template map [@mode local]] foo in
...
let%template do_b_thing =
...
let b = (map [@mode local]) foo in
...The first one is too noisy especially if there are a bunch of this kind of function near each other, the second form has confused and worried users internally about how large the scope of the extension node is and causes some confusion over whether the function itself is templated or whether it just uses templates. |
|
I don't quite understand what you mean by:
Are you saying that currently only extension nodes are rewritten and attributes only ever add code, and that split is useful? |
Signed-off-by: Jack Rickard <[email protected]>
Signed-off-by: Jack Rickard <[email protected]>
|
I think in general, it is useful for extensions to rewrite their contents and attributes to be metadata. I think for our use case and perhaps some others, attributes are more natural. In part, it's because we are rewriting the attached expression/etc. to some version of itself, rather than to some new thing. So for example I'd like us to add the ability to register attribute transformers, but perhaps we should add caveats to the documentation explaining it should be used sparingly, and only for cases where one is, essentially, transforming likes-to-likes, or a thing to a version of itself. As for @NathanReb, how does this sound? |
Yes, that's what I meant! I think one of the goals of the ppx approach was to have preprocessors agree on a universal syntax to make it easier for users to read code using preprocessors without necessarily having previous knowledge of all them. I think this feature and this ppx venture out of the conventional use of attributes and thus can lead to confusing syntax. That being said, I understand it is useful in your case and this would not be the first strange exception to the extension vs attribute rule. I just want to make sure there is no simpler alternative and that it is only used by careful and experienced ppx authors who weighted the pros and cons of introducing such syntax. @ceastlund yes, documenting this feature properly so it is not used lightly sounds good to me! |
Signed-off-by: Jack Rickard <[email protected]>
c3413ae to
74e3e3d
Compare
|
@NathanReb I've added another paragraph to the docs attempting to make that clearer. Are you happy with that? I could add that warning somewhere else if you want it to be even louder. |
|
We should also add some caveat in |
Signed-off-by: Jack Rickard <[email protected]>
| let loc = Common.loc_of_attribute attr in | ||
| super#payload payload; | ||
| Attribute_table.add not_seen name loc | ||
| Attribute_table.replace not_seen name loc |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sounds sane! For future reference, do you have an example of when that happens? Something we can use to reproduce it?
Might actually be worth adding a test for this.
|
The documentation looks fine, thanks for taking the time to add this! |
Signed-off-by: Jack Rickard <[email protected]>
Signed-off-by: Jack Rickard <[email protected]>
|
Thanks for the review, and sorry for the gap, I've been busy the past 2 weeks but I should be much more responsive now. |
This also migrates an existing test to be more like the others Signed-off-by: Jack Rickard <[email protected]>
|
Do you know a solution to #574 (comment)? (I'm mostly linking it because GitHub makes it easy to lose some review comments) |
|
Other than the copied attribute bug fix, this looks good to go. If we move it to its own PR I'm happy to merge the rest as is! |
Signed-off-by: Jack Rickard <[email protected]>
NathanReb
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, let's merge!
CHANGES: ### 5.4 Support - Add initial OCaml 5.4 support (ocaml-ppx/ppxlib#570, @patricoferris, @NathanReb) ### Other Changes - Add `Longident.to/of_compiler` to astlib to simplify maintenance of ppx-es that interacts with other parts of the compiler-libs such as the type checker. (ocaml-ppx/ppxlib#603, @NathanReb) - Fix a bug where some infix operators such as `mod` would be printed as raw identifiers by our `Pprintast`. (ocaml-ppx/ppxlib#601, @NathanReb) - Fix 5.2 -> 5.3 migration of constants. Those used to always have a `none` location which can lead to unhelpful error messages. (ocaml-ppx/ppxlib#569, @NathanReb) - Add a new context-free rule type that replaces AST nodes that have the registered attributes attached to them. (ocaml-ppx/ppxlib#574, @Skepfyr) - Allow users to derive code from module bindings and module declarations (ocaml-ppx/ppxlib#576, @patricoferris) - Expose `Ppxlib.Location.Error.t = Astlib.Location.Error.t` (ocaml-ppx/ppxlib#593, @ceastlund) - Add `@@@ppxlib.inline.end`, deprecate `@@@deriving.end`. (ocaml-ppx/ppxlib#594, @ceastlund) - Clean the AST of any ppxlib migration attributes whenever printing using Pretty_print mode and the use_compiler_pp flag. (ocaml-ppx/ppxlib#598, @patricoferris) - Add custom printer support to `pp_ast` functions via the `?printer` config parameter. (ocaml-ppx/ppxlib#526, @pedrobslisboa)
This adds a new kind of context-free rule that allows PPX authors to write PPXs that replace AST nodes that have their attributes on them. You can already achieve something like this with extension nodes, but the syntactic overhead for that is very high for some PPXs.
I wanted this specifically for ppx_template, which uses attributes to add suffixes to identifiers, adding an extension node is too heavyweight as the attributes need to get sprinkled all over your code.