adamcz added inline comments.

================
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
----------------
sammccall wrote:
> 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.
Not right now, but I left a comment that we should improve that. The way it is 
now, code is correct, always, and improving it is just a matter or removing the 
"no record types" check and replacing with a bit more code. Seems better to me.


================
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()), "",
----------------
kadircet wrote:
> sammccall wrote:
> > 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? :-)
> > 
> > 
> done, let me know if it doesn't work :D
Works great, thanks!


================
Comment at: clang-tools-extra/clangd/refactor/tweaks/AddUsing.cpp:226
+
+    if (auto Err = R.add(tooling::Replacement(SM, InsertionPoint->Loc, 0,
+                                              UsingTextStream.str()))) {
----------------
sammccall wrote:
> 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)
OK, I added the assert(). I'm not against asserts in general, I just find them 
often insufficient and not a replacement for better error handling. In this 
case, it certainly won't hurt.


================
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.
----------------
sammccall wrote:
> sammccall wrote:
> > yikes, this copies the tokenbuffer, I didn't think that was even possible!
> > auto&
> Copy constructor deleted in 94938d7d41cd11c4539ff93b801fe53cb4fddba2
Haha, that's a lovely bug. Thanks for fixing the ctor. 


================
Comment at: clang-tools-extra/clangd/refactor/tweaks/AddUsing.cpp:249
+  const auto *SpelledBeginTok =
+      TB.spelledTokenAt(SM.getSpellingLoc(ExpandedTokens.front().location()));
+  const auto *SpelledEndTok =
----------------
kadircet wrote:
> why not use spelledForExpanded in here as well?
It didn't work before. Thanks for improving it!


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