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
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits