Initial version of DefineInline action that will fully qualify every
name inside function body. It has some problems while qualyfying dependent
template arguments, see FIXME in the unittests.
Details
Diff Detail
- Repository
- rG LLVM Github Monorepo
- Build Status
Buildable 38673 Build 38672: arc lint + arc unit
Event Timeline
clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp | ||
---|---|---|
164 | Are we trying to replace the whole name? We can avoid this problem by not qualifying template arguments in the first place. [[baz<Bar>]] -> [[::ns::baz<Bar>]] We could simply replace the part before template arguments: [[baz]]<Bar> -> [[::ns::baz]]<Bar> |
Currently we add too many qualifiers in some cases, e.g. here's a common pattern in clangd:
// foo.h #include "Decl.h" namespace clang::clangd { std::string printName(Decl*D); } // foo.cpp #include "foo.h" namespace clang::clangd { std::string printName(Decl *D) { NamedDecl* ND = ...; } ... }
this will result in:
// foo.h namespace clang::clangd { std::string printName(Decl*D) { clang::NamedDecl *ND = ...; // <-- (!) we did not even had any 'using' declarations or 'using namespace's } }
It's ok to leave this out of the initial change, but could we describe our strategy to tackle this somewhere in the comments - how we want to fix this and when.
clang-tools-extra/clangd/unittests/TweakTesting.h | ||
---|---|---|
77 | It seems weird that we have this inconsistency between the contents for current file (passed through the return value) and contents for other files (pass through the fields). A few better alternatives that come to mind:
|
We also need to rename parameters sometimes, right?
// Sometimes we need to rename parameters. void usages(int decl_param, int); void usages(int def_param, int now_named) { llvm::errs() << def_param + now_named; } // And template parameters! (these are even more interesting) template <class T> struct Foo { template <class U, class> void usages(); }; template <class L> template <class R, class NowNamed> void Foo<L>::usages() { llvm::errs() << L() + R() + NowNamed(); }
clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp | ||
---|---|---|
177 | I don't think it's true in presence of using declarations: namespace ns { void foo(int*); } namespace other_ns { void foo(char*); } using ns::foo; using other_ns::foo; template <class T> void usages() { foo(T()); // <-- would we add `ns::foo` or `other_ns::foo`? } Not qualifying in this case is probably ok, but adding any of the qualifiers is probably wrong. | |
183 | NIT: could you add an example of a member expression here? Note that sometimes there is no left side (it's implicit), but in that case we shouldn't qualify either. | |
184 | I believe we could also avoid qualifying anything from non-namespace scope. namespace ns { class X { static void foo(); void usages(); }; } using ns::X::foo; // <- not allowed! void ns::X::usages() { foo(); // <-- so no need to qualify this! } If I'm reading the code correctly, we will get ns::foo() in the current version. | |
295 | Not sure if this covers these two cases (could you please add them to the tests?): template <class T> struct vector { void foo(); }; template <class T> void vector<T>::foo() {} template <class T> struct list { template <class U> void foo(); } template <class T> template <class U> void list<T>::foo() {} Or are we disabled in those cases? | |
376 | This never fails since there are no replacements to conflict with, right? |
This is tricky, and there are also cases where the declaration doesn't provide the name of parameter, e.g. llvm::json::Value toJSON(const CodeAction &);. Maybe we can overwrite the declaration signature with the definition's, or just disable the tweak on these cases.
I actually have a fixme saying:
// FIXME: Instead of fully qualifying we should try deducing visible scopes at // target location and generate minimal edits.
Elaborating on it by saying "we can start by using the namespaces in targetcontext".
clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp | ||
---|---|---|
177 | as discussed offline; | |
clang-tools-extra/clangd/unittests/TweakTesting.h | ||
77 | It is left-out this way since most of the checks only care about a single file, and there are lots of them so changing would definitely require some plumbing. I am not sure having the inconsistency between main file and others matters so much though; we have similar discrimination towards non-main files in a bunch of other places in testing infrastructure e.g TestTU. As for the suggestions, adding another apply function for multifile case doesn't seem so nice, so I would rather move forward with first one, if you believe this is a strong concern. |
Are we planning to fix this right away or should we keep this indefinitely?
If latter, I believe we should think about some heuristics to avoid qualification in the short term instead.
If we want to do it right away, that's great! (albeit more complicated, I guess)
That's the end goal, but I am currently working on a patch that will at least strip away the common namespaces
of function declaration and symbols used inside.
clang-tools-extra/clangd/unittests/TweakTesting.h | ||
---|---|---|
77 |
Agree that no matter what we do, we should let the current usages stay the same.
What kind of inconsistency are you referring to? Both main file and extra files are fields inside TestTU? It outputs ParsedAST and it's a return value of the build() function.
Yeah, totally ok with this. I'm not a big fan of out parameters myself and prefer putting stuff into the return value instead. |
So currently AST doesn't store any information regarding template parameter locations except the deepest one.
Therefore I've changed the availability to discard any methods inside templated classes, since there is no way to
validate the template parameter names. Hopefully this should be a rare use-case, and I believe most of the times
people have same template paramater names on declaration and definition. Let me know what you think about
it.
In addition to that implemented renaming for function parameters and template parameters in case of templated
functions.
Thanks! LG, I also think this should be a rare case. The deepest template parameters are typically the only ones anyway.
In addition to that implemented renaming for function parameters and template parameters in case of templated
functions.
Thanks, will take a look!
clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp | ||
---|---|---|
68 | "Lexes" is probably a not very relevant implementation detail. | |
167 | NIT: Maybe store Error directly? | |
173 | NIT: IIUC, this could be simply if (Ref.Qualifier) | |
180 | As discussed before, Targets can actually come from different scopes, e.g. from ADL. | |
181 | It just occurred to me that this is a really bad idea. I think the order of items in Targets is non-deterministic. | |
194 | You would want to use ND->printNestedNameSpecifier() instead to avoid printing inline namespaces: namespace std { inline namespace v1 { struct vector {}; }} ^-- I believe the current code would print std::v1:: for vector and we want std::. | |
197 | Maybe return a generic error in the end instead of the last error? Should be a better UX for anyone who tries to explore what went wrong. | |
221 | NIT: mention that both function parameters and template parameters are updated here. | |
224 | This does not rename any references to those parameters, right? template <class T> void foo(T x); template <class U> void foo(U x) {} would turn into template <class U> void foo(T x); right? |
Mostly comments about tests, the implementation itself LG.
clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp | ||
---|---|---|
68 | NIT: s/SemiColon/Semicolon | |
79 | What are the tokens we expect to see before the semicolon here? Any reason to have an actual loop here? | |
180 | NIT: could we mention the tweak name here? to make sure it's easy to detect those lines in the logs: | |
207 | NIT: braces are redundant | |
361–412 | NIT: use auto | |
366 | NIT: braces are redundant | |
clang-tools-extra/clangd/unittests/TweakTesting.cpp | ||
85 | Could you document EditedFiles contains all other edited files and the original std::string only contains results for the main file? | |
clang-tools-extra/clangd/unittests/TweakTests.cpp | ||
894 | Why have a comment in this test? | |
911 | Template declarations inside function bodies are not allowed in C++. | |
950 | Same here: we don't need the comment here. | |
953 | Either remove using namespace a from the function body or put a FIXME this shouldn't actually qualify? | |
978 | Maybe move this matcher to the start of the file? | |
1018 | We prefer to have using ::testing::ElementsAre at the start of the file and not qualify the usage everywhere in our tests. | |
1065 | This is really uncommon, I'm not even sure we should test this. template <class T> void foo(T p); template <> void foo<int>(int p) { /* do something */ } Could we add a test that we don't suggest this action in that case? | |
1156 | Can we test with a single namespace? Instead, we could validate with separate tests:
I believe that would keep both tests more focused | |
1204 | Could you indent here and in other tests? it's hard to follow the fact that Foo is inside b without indentation. Alternatively, put on one line if there's just a single declaration inside the namespace |
- Address comments
clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp | ||
---|---|---|
79 | it has been a long time since our offline discussions around this one. but the idea was could have any attributes before semicolon, these could be macros that will expand to one thing in some platforms and another in a different platform. Therefore we've decided to skip anything until we've found a semicolon. I don't think I follow the second part of the question though? What do you mean by "having an actual loop"? | |
207 | I've thought you were a fan of braces when the inner statement spans multiple lines, even if it is a single statement :D | |
clang-tools-extra/clangd/unittests/TweakTests.cpp | ||
911 | well, it was still working and inlining the function definition, apparently despite the diagnostic, ranges were correct. | |
1065 | yes there is a test for this in availability checks patch: EXPECT_UNAVAILABLE(R"cpp( template <typename T> void foo(); template<> void f^oo<int>() { })cpp"); | |
1204 | no more nested namespaces |
clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp | ||
---|---|---|
79 | The loop I mentioned is while (!CurToken.is(tok::semi)) I believe this could be replaced with auto NextTok = Lexer::findNextToken(...); if (NextTok && NextTok->is(tok::semi)) ... There are two common attribute applications we should mostly care about: // Attribute in declaration specifiers. [[noreturn]] int foo(); // Attribute after declarator name int foo [[noreturn]](); Both work with a simple if statement, rather than the attributes. The case we're covering with the loop is super uncommon and I don't think we should even bother to support it: int foo() [[some::attribute]]; |
clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp | ||
---|---|---|
207 | It's more complicated than that... Let me explain...
if (something) { // Here comes a comment... return 10; }
if (something) { for (;;) do_something_else(); } In your case, I'd not use braces... | |
clang-tools-extra/clangd/unittests/TweakTests.cpp | ||
911 | I guess we didn't need to do any edits there, so we just carried the text unchanged. Range of the function body is correct, since the parser can easily recover in those cases. | |
1204 | Yay! Thanks! |
A few last NITs and one important comment about handling the case when function definition come from macro expansions
clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp | ||
---|---|---|
364 | NIT: s/SemiColon/Semicolon | |
375 | Could we add a test when the semicolon comes from a macro expansion? I believe the action won't be available in that case, which is ok. Just to make sure we cover this corner-case. #define SEMI ; void foo() SEMI Also interesting cases:
I believe they might catch a few bugs here, as we seem to assume all locations are file locations... | |
clang-tools-extra/clangd/unittests/TweakTesting.h | ||
71 | Hm, maybe even call ADD_FAILURE() when there are changes to other files and tests pass null to EditedFiles? | |
clang-tools-extra/clangd/unittests/TweakTests.cpp | ||
989 | Argh... Without clang-format that looks SO weird :-) | |
1029 | Maybe put it before using namespace a;? | |
1095 | How is this test different from the previous one? | |
1137 | Do we test the following case anywhere? template <class T> void foo(); template <> void ^foo<int>() { // should not be available return; } |
clang-tools-extra/clangd/refactor/tweaks/DefineInline.cpp | ||
---|---|---|
375 | see macro tests for the behavior in such cases. | |
clang-tools-extra/clangd/unittests/TweakTests.cpp | ||
1095 | yes that's the point, adding a comment. | |
1137 | Yes, there is once check in availability tests for: EXPECT_UNAVAILABLE(R"cpp( template <typename T> void foo(); template<> void f^oo<int>() { })cpp"); |
"Lexes" is probably a not very relevant implementation detail.
I'd say this function probably doesn't need a comment, it's clear what it does from its name.