sammccall accepted this revision.
sammccall added a comment.
This revision is now accepted and ready to land.

LG, thanks! Let me know if/when I should land this for you.



================
Comment at: clang-tools-extra/clangd/refactor/tweaks/AddUsing.cpp:105
+// argument.
+bool isInMacroNotArg(const SourceManager &SM, const SourceLocation Loc) {
+  return Loc.isMacroID() && !SM.isMacroArgExpansion(Loc);
----------------
This is just SourceManager::isMacroBodyExpansion(), I think.


================
Comment at: clang-tools-extra/clangd/refactor/tweaks/AddUsing.cpp:163
+  if (isInMacroNotArg(SM, QualifierToRemove.getBeginLoc()) ||
+      isInMacroNotArg(SM, QualifierToRemove.getEndLoc())) {
+    return false;
----------------
I think the endloc check should rather be SM.isWrittenInSameFile - it's no good 
if e.g. they're both macro arg expansions, but different ones.


================
Comment at: clang-tools-extra/clangd/refactor/tweaks/AddUsing.cpp:171
+// The insertion point might be a little awkward if the decl we're anchoring to
+// has a comment in an unfortunate place (e.g. directly above using, or
+// immediately following "namespace {". We should add some helpers for dealing
----------------
nit: you've mentioned the two cases I don't think we should bother fixing, but 
not the crucial one: we're anchoring to a regular decl, and we're going to 
insert in between the decl and its doc comment.


================
Comment at: clang-tools-extra/clangd/refactor/tweaks/AddUsing.cpp:245
+  auto &SM = Inputs.AST->getSourceManager();
+  auto TB = Inputs.AST->getTokens();
+  // Determine the length of the qualifier under the cursor, then remove it.
----------------
yikes, this copies the tokenbuffer, I didn't think that was even possible!
auto&


================
Comment at: clang-tools-extra/clangd/refactor/tweaks/AddUsing.cpp:44
+  // SourceLocation if the "using" statement already exists.
+  llvm::Expected<InsertionPointData>
+  findInsertionPoint(const Selection &Inputs);
----------------
adamcz wrote:
> sammccall wrote:
> > this doesn't use any state - make it static or a free function?
> It uses Name and NNSL . Would you prefer this as free function, with both 
> passed as arguments. My initial thought was that, since prepare() and apply() 
> already share these via class members this was fine, but I'm certainly not 
> against making this a free function.
Oops, right. I think since this is the complicated part and it has a fairly 
simple interface, making its inputs/outputs explicit is nice hygiene.

(prepare and apply do communicate through class state, which feels a bit hacky 
to me. We didn't come up with anything nicer but still flexible+simple when 
designing this. It is conventional and documented at least)


================
Comment at: clang-tools-extra/clangd/refactor/tweaks/AddUsing.cpp:71
+      return true;
+    }
+    if (D->getDeclContext()->Encloses(SelectionDeclContext)) {
----------------
adamcz wrote:
> sammccall wrote:
> > nit: we conventionally leave out the {} on one-line if bodies etc.
> Uhh...is that a hard rule? I personally hate that, it's just asking for Apple 
> SSL-style bugs
> https://www.imperialviolet.org/2014/02/22/applebug.html
There's no hard rule, but we do use that style consistently and consistency has 
value too.

The clang-tidy check `readability-misleading-indentation` catches that bug. Can 
I suggest:
 - (for us) adding it to `.clang-tidy` in `llvm-project`? This will affect 
clangd and the phabricator linter
 - (for the world) we could start a list of on-by-default clang-tidy checks for 
clangd users with no `.clang-tidy` config, and include this check


================
Comment at: clang-tools-extra/clangd/refactor/tweaks/AddUsing.cpp:89
+
+  // If we're looking at a type or NestedNameSpecifier, walk up the tree until
+  // we find the "main" node we care about, which would be ElaboratedTypeLoc or
----------------
adamcz wrote:
> sammccall wrote:
> > I like the idea here, but I'm not sure it quite works. e.g. any typeloc has 
> > a directly enclosing typeloc, the inner one can't be targeted. So what 
> > about `x::S*`? (pointertypeloc > elaboratedtypeloc > recordtypeloc)?
> > 
> > - looping up from NNS until you get to something else makes sense
> > - unwrapping typeloc -> elaboratedtypeloc makes sense, but I don't think 
> > you ever want to unwrap multiple levels?
> > - i don't think these cases overlap at all, you want one or the other
> > 
> > Am I missing something?
> If you have a class foo::bar::cc and a struct st inside it, and then do:
> foo::bar::c^c::st *p;
> you end up with:
> PointerTypeLoc -> ElaboratedTypeLoc ->NestedNameSpecifierLoc -> RecordTypeLoc
> in which case you need to go up from type to NNSL and then up again, from 
> NNSL to something that's not NNSL.
> 
> You have a good point with the PointerTypeLoc, that's a bug. We should stop 
> going up the tree as soon as we find ElaboratedTypeLoc. I added a test for 
> that.
> foo::bar::c^c::st *p;

But this isn't a case we support, right? We only support extraction when the 
NNS consists entirely of namespaces, and such NNSes don't have any children 
apart from the qualifier NNS.


================
Comment at: clang-tools-extra/clangd/refactor/tweaks/AddUsing.cpp:206
+  tooling::Replacements R;
+  if (auto Err = R.add(tooling::Replacement(
+          SM, CharSourceRange::getTokenRange(NNSL.getSourceRange()), "",
----------------
adamcz wrote:
> sammccall wrote:
> > (Sorry if some of this is obvious: TL;DR: we should use TokenBuffer to 
> > handle macros in this case)
> > 
> > Whenever we try to use SourceLocations to reason about written source code, 
> > we have to think about macros. Some libraries try to encapsulate this, but 
> > the road to confusion is paved with good intentions, and it's often best to 
> > decide on the policy and be explicit about it.
> > 
> > For example, when provided a macro location, the tooling::Replacement 
> > constructor uses its spelling location. 
> > Given: 
> > ```
> > // imagine we're trying to abstract away namespaces for C or something
> > #define NS(X) foo::X
> > NS(Bar) boo;
> > ```
> > Running this action on `N` would result in changing the definition of the 
> > `NS` macro to delete the "foo::", as that's where the qualifier was spelled!
> > 
> > It would be reasonable (but actually not trivial!) to try to detect any 
> > macro usage and bail out. The general case of "is there some sequence of 
> > source-code tokens that correspond to this range of preprocessed tokens and 
> > nothing else" is pretty hard.
> > 
> > However TokenBuffer does have APIs for this exact question, as it was 
> > designed for writing refactorings that replace nodes. I think you want:
> >  - `expandedTokens(NNSL.getSourceRange())`
> >  - `spelledForExpanded(ExpandedTokens)`
> >  - `Token::range(SM, Spelled.front(), Spelled.back())`
> >  - `Replacement("fname", Range.Offset, Range.Length, "")`
> > 
> > (ugh, that's really awkward. Maybe we should have a helper in TokenBuffer 
> > that combines 1-3)
> > 
> You have a good point that this doesn't work well with macros, but I'm not 
> entirely sure how you think this should work.
> 
> I'd argue that code actions should refer to the thing under the cursor, not 
> it's expansion. That would be confusing to the user, as they would not be 
> able to understand what the action offered is, nor how it would affect other 
> places. So in your example of 
> #define NS(X) foo::X
> I'd argue that we should not offer the action. 
> We should, however, support something like:
> EXPECT_TRUE(fo^o::bar());
> In this case, the qualifier is spelled under the cursor and the fact that 
> EXPECT_TRUE is a macro and not a function should not matter to the user.
> 
> I updated the code to support that and added tests.
> We can use isMacroID() and isMacroArgExpansion() to filter out macros, except 
> for macro arguments. Note that we can't use spelledForExpanded(), since the 
> qualifier might not be the entire expansion of the macro, thus 
> spelledForExpanded will return None.
> 
> Let me know if any of what I did here now doesn't make sense.
> 
> in your example of #define NS(X) foo::X I'd argue that we should not offer 
> the action.

Indeed, sorry I didn't spell that out - I was highlighting it because the 
original code silently did something (bad) in this case.

> can't use spelledForExpanded(), since the qualifier might not be the entire 
> expansion of the macro

Ah, D75446 hasn't landed yet. @kadircet, want to land this soon? :-)




================
Comment at: clang-tools-extra/clangd/refactor/tweaks/AddUsing.cpp:226
+
+    if (auto Err = R.add(tooling::Replacement(SM, InsertionPoint->Loc, 0,
+                                              UsingTextStream.str()))) {
----------------
adamcz wrote:
> sammccall wrote:
> > Again macros.
> > This seems a bit more pressing, there's a nontrivial chance that you're 
> > inserting inside a namespace which is introduced by invoking a 
> > `FOO_NS_BEGIN` macro.
> > 
> > Unfortunately I don't see a really simple way to insert after this macro - 
> > using the expansion location fails if macro doesn't end after the `}`. 
> > (e.g. `ANON_NAMESPACE(f::X y;)`)
> > (Also you'd need to take care of inserting a space after the macro 
> > invocation to avoid `FOO_NS_BEGINusing`.)
> > 
> > I'd suggest simply skipping over any candidate locations (namespaces or 
> > using declarations) that aren't in the main file, using the expansion 
> > location of the first-top-level-decl fallback, and asserting here that the 
> > location is in the main file. (We use raw `assert()` in LLVM)
> We already skip over "using" not in main file. I added check for namespace 
> and fixed the above-first-top-level-decl to be above getExpandedLoc() of 
> that, which works for something like a macro declaring a namespace. It's not 
> ideal, but I think it should be good enough, at least for now. Also added a 
> test.
> 
> Not sure what the point of assert() here would be? Wouldn't it be far better 
> to return an error, since it's trivial at this point and it won't crash the 
> whole server, including the background index and such? I get that assert() 
> can be useful in places where returning error is difficult and for things 
> that should never, ever happen, but here seems like an overkill.
> 
> Anyway, editMainFile() already returns an error when the edit is not in the 
> main file. IMHO that's good enough, what do you think?
Behaviour looks good.

> Not sure what the point of assert() here would be?
This is a consistency check - we're relying on an invariant we think we've 
established elsewhere. If this goes wrong, it's a programmer error, and for 
better or worse, LLVM defines those as asserts.

Practical benefits:
 - developers are more likely to notice when these are violated (assuming an 
-UNDEBUG build)
 - we get a stacktrace, which is often useful (not really here). Returning an 
error doesn't give us that.
 - we don't pay for the check in release builds
 - it documents the distinction between invariant-breaking bugs and 
dynamically-possible error conditions
 - consistency (in principle we could change all our err-handling, but we can't 
change this in clang)


================
Comment at: clang-tools-extra/clangd/unittests/TweakTests.cpp:2436
+#include "test.hpp"
+namespace {using one::two::ff;
+
----------------
adamcz wrote:
> sammccall wrote:
> > alternately: `using ::one::two::ff`.
> > Correct more often, uglier/longer, probably a style preference but not 
> > worth having an option for. Pick one :-)
> The current code keeps the :: if it was there, but won't add it, so basically 
> we respect whatever the user typed. Seems OK to me, do you agree?
> 
> I changed one test to include this aspect.
Actually yeah, that's nice and neatly avoids having to make and defend a policy 
:-)


Repository:
  rG LLVM Github Monorepo

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D76432/new/

https://reviews.llvm.org/D76432



_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to