Creating a SelectionTree at the location where macro expands allows
us to obtain the associated expression, which might then be used to
evaluate compile-time values if possible.
Closes clangd/clangd#1595.
Differential D148457
[clangd] Support macro evaluation on hover zyounan on Apr 16 2023, 2:01 AM. Authored by
Details Creating a SelectionTree at the location where macro expands allows Closes clangd/clangd#1595.
Diff Detail
Event TimelineComment Actions Thanks for the patch and the thorough testcase! I wonder if we should make this a bit more strict: if the macro expansion itself is an expression, show the value of that expression, but not e.g. an enclosing expression. Otherwise, the Definition field of the hover (which shows the tokens of the macro expansion) and the Value and Type fields (which show the value and type of a larger expression) could be out of sync and confusing. A (somewhat contrived) example would be: #define PLUS_TWO + 2 int x = 40 PLUS_TW^O; // huh, the value of "+ 2" is "42"? Of your existing testcases, I think the only one this stricter rule would break is #6 (define_lambda_begin), and I think that's fine.
Comment Actions Sorry for responding late and thank you for the good catch!
Comment Actions Oops, an inline comment disappears accidentally.
The reason is we're getting a SelectionNode with a wider range. Here is the layout of the tree: txt Built selection tree TranslationUnitDecl FunctionDecl void check() CompoundStmt { … .BinaryOperator 40 + 2 *IntegerLiteral 2 The dot prior to AST node kind indicates partial selection and the asterisk indicates complete selection according to SelectionTree::print. By invoking Tree.commonAncestor() it would stop at BinaryOperator despite the fact that we're expecting IntegerLiteral, that represents the number being accordance with the macro expansion. In order to address such case, I think we could go down the SelectionTree a little further and start evaluating at the node with complete selection. Comment Actions (This looks really cool, nice work! only a driveby comment from me I'm afraid) I think rather if the common ancestor isn't completely selected, evaluation shouldn't happen. (I think this is what Nathan was suggesting by "strict"?) (Here, "+ 2" doesn't have a value, the + is binary rather than unary) The partial/fully selected concept in SelectionTree was originally intended for exactly this kind of thing, so I'm happy to see it get some use :-) Comment Actions Haha. My bad for misunderstanding.
I don't think it is a feasible approach to simply rule out partial selection. Please consider this snippet: #define SizeOf sizeof void check() { Siz^eOf(int); } And associative selection tree: Built selection tree TranslationUnitDecl FunctionDecl void check() CompoundStmt { … .UnaryExprOrTypeTraitExpr sizeof(int) # Partial selected. Exclude it? Obviously we're expecting evaluation on such macro (which is just what the original issue addresses). And it's worth noting that if we define the macro a whole expression e.g., sizeof(int), rather than a token sizeof, the common ancestor will be seen as completely selected. OTOH, it doesn't seem so trivial to me to tell if an expression or token is appropriate (or to say, full enough?) to evaluate. Take Nathan's case for example. The macros #define PLUS_TWO +2 and #define TWO 2 are both transformed into the same AST. The mapped selection tree is, .BinaryOperator 40 + 2 *IntegerLiteral 2 # For either '40 PLU^S_TWO' or '40 + T^WO' ...or even more extreme, #define PLUS_TWO +2 40 + PL^US_TWO; // it should be seen as '+2', right? As of now I didn't come up with a better idea to split the cases. Any suggestion? :) Comment Actions
That wasn't (and isn't) obvious to me - the issue didn't mention the macro in question, i assumed it was #define alignof(x) __alignof(x) or similar. Looks like both forms are in use but clang provides #define alignof __alignof as a built-in. A couple of ideas:
Comment Actions Thank you very much for the ideas.
This strategy looks reasonable to me and it passes my test cases. I've updated my patch again. :)
Comment Actions Thanks for the update, and thank you Sam for the suggestions! I was going to suggest a variant of the second option, where if a macro expands to a single token, the behaviour is the same as if the expanded token had been written in the source and that's what had been hovered over. However, I don't have any strong concerns about the additional scenarios supported by the third option, so since this is what was implemented, let's go with it. We can always tweak this in the future if necessary. Circling back to my original concern: the original selection being "partial" vs. "complete" is just one part of this concern. Another part is the loop in visitExprFromSelectionTree that can choose a larger enclosing expression than the original selection (even if it's "complete"), so that we can get a Definition for a smaller expression but a Value for a larger one. However, I discovered that this sort of discrepancy can already arise without macros, where the same loop means Type is given for a smaller expression and Value for a larger one. I filed https://github.com/clangd/clangd/issues/1622 for this with an example. Since this is a pre-existing issue, I think it's fine not to worry about it in this patch. Perhaps in a follow-up patch we can find a solution that addresses both the macro and non-macro cases.
Comment Actions Thank you for the opinions. I've updated and please take a look.
Comment Actions Thanks! LGTM with one final nit (the other comment is just food for future thought)
|
Instead of generalizing printExprValue to take an arbitrary callback, can we modify the original function to return both the Expr* and its printed value (grouped in a pair or small struct)? And then our new call site can call getType() on the Expr*.
(I realize this would be a slight change in semantics as the callback for the macro codepath currently exits the loop as soon as we have a type... but I think there's value in keeping the behaviour consistent between the macro and non-macro cases.)