@@ -179,14 +179,11 @@ deleteTokensWithKind(const syntax::TokenBuffer &TokBuf, tok::TokenKind Kind,
179
179
// looked up in the context containing the function/method.
180
180
// FIXME: Drop attributes in function signature.
181
181
llvm::Expected<std::string>
182
- getFunctionSourceCode (const FunctionDecl *FD, llvm::StringRef TargetNamespace ,
182
+ getFunctionSourceCode (const FunctionDecl *FD, const DeclContext *TargetContext ,
183
183
const syntax::TokenBuffer &TokBuf,
184
184
const HeuristicResolver *Resolver) {
185
185
auto &AST = FD->getASTContext ();
186
186
auto &SM = AST.getSourceManager ();
187
- auto TargetContext = findContextForNS (TargetNamespace, FD->getDeclContext ());
188
- if (!TargetContext)
189
- return error (" define outline: couldn't find a context for target" );
190
187
191
188
llvm::Error Errors = llvm::Error::success ();
192
189
tooling::Replacements DeclarationCleanups;
@@ -216,7 +213,7 @@ getFunctionSourceCode(const FunctionDecl *FD, llvm::StringRef TargetNamespace,
216
213
}
217
214
const NamedDecl *ND = Ref.Targets .front ();
218
215
const std::string Qualifier =
219
- getQualification (AST, * TargetContext,
216
+ getQualification (AST, TargetContext,
220
217
SM.getLocForStartOfFile (SM.getMainFileID ()), ND);
221
218
if (auto Err = DeclarationCleanups.add (
222
219
tooling::Replacement (SM, Ref.NameLoc , 0 , Qualifier)))
@@ -232,7 +229,7 @@ getFunctionSourceCode(const FunctionDecl *FD, llvm::StringRef TargetNamespace,
232
229
if (const auto *Destructor = llvm::dyn_cast<CXXDestructorDecl>(FD)) {
233
230
if (auto Err = DeclarationCleanups.add (tooling::Replacement (
234
231
SM, Destructor->getLocation (), 0 ,
235
- getQualification (AST, * TargetContext,
232
+ getQualification (AST, TargetContext,
236
233
SM.getLocForStartOfFile (SM.getMainFileID ()),
237
234
Destructor))))
238
235
Errors = llvm::joinErrors (std::move (Errors), std::move (Err));
@@ -319,29 +316,9 @@ getFunctionSourceCode(const FunctionDecl *FD, llvm::StringRef TargetNamespace,
319
316
}
320
317
321
318
struct InsertionPoint {
322
- std::string EnclosingNamespace;
319
+ const DeclContext * EnclosingNamespace = nullptr ;
323
320
size_t Offset;
324
321
};
325
- // Returns the most natural insertion point for \p QualifiedName in \p Contents.
326
- // This currently cares about only the namespace proximity, but in feature it
327
- // should also try to follow ordering of declarations. For example, if decls
328
- // come in order `foo, bar, baz` then this function should return some point
329
- // between foo and baz for inserting bar.
330
- llvm::Expected<InsertionPoint> getInsertionPoint (llvm::StringRef Contents,
331
- llvm::StringRef QualifiedName,
332
- const LangOptions &LangOpts) {
333
- auto Region = getEligiblePoints (Contents, QualifiedName, LangOpts);
334
-
335
- assert (!Region.EligiblePoints .empty ());
336
- // FIXME: This selection can be made smarter by looking at the definition
337
- // locations for adjacent decls to Source. Unfortunately pseudo parsing in
338
- // getEligibleRegions only knows about namespace begin/end events so we
339
- // can't match function start/end positions yet.
340
- auto Offset = positionToOffset (Contents, Region.EligiblePoints .back ());
341
- if (!Offset)
342
- return Offset.takeError ();
343
- return InsertionPoint{Region.EnclosingNamespace , *Offset};
344
- }
345
322
346
323
// Returns the range that should be deleted from declaration, which always
347
324
// contains function body. In addition to that it might contain constructor
@@ -409,14 +386,9 @@ class DefineOutline : public Tweak {
409
386
}
410
387
411
388
bool prepare (const Selection &Sel) override {
412
- // Bail out if we are not in a header file.
413
- // FIXME: We might want to consider moving method definitions below class
414
- // definition even if we are inside a source file.
415
- if (!isHeaderFile (Sel.AST ->getSourceManager ().getFilename (Sel.Cursor ),
416
- Sel.AST ->getLangOpts ()))
417
- return false ;
418
-
389
+ SameFile = !isHeaderFile (Sel.AST ->tuPath (), Sel.AST ->getLangOpts ());
419
390
Source = getSelectedFunction (Sel.ASTSelection .commonAncestor ());
391
+
420
392
// Bail out if the selection is not a in-line function definition.
421
393
if (!Source || !Source->doesThisDeclarationHaveABody () ||
422
394
Source->isOutOfLine ())
@@ -429,19 +401,24 @@ class DefineOutline : public Tweak {
429
401
if (Source->getTemplateSpecializationInfo ())
430
402
return false ;
431
403
432
- if (auto *MD = llvm::dyn_cast<CXXMethodDecl>(Source)) {
433
- // Bail out in templated classes, as it is hard to spell the class name,
434
- // i.e if the template parameter is unnamed.
435
- if (MD->getParent ()->isTemplated ())
436
- return false ;
437
-
438
- // The refactoring is meaningless for unnamed classes and definitions
439
- // within unnamed namespaces.
440
- for (const DeclContext *DC = MD->getParent (); DC; DC = DC->getParent ()) {
441
- if (auto *ND = llvm::dyn_cast<NamedDecl>(DC)) {
442
- if (ND->getDeclName ().isEmpty ())
443
- return false ;
444
- }
404
+ auto *MD = llvm::dyn_cast<CXXMethodDecl>(Source);
405
+ if (!MD) {
406
+ // Can't outline free-standing functions in the same file.
407
+ return !SameFile;
408
+ }
409
+
410
+ // Bail out in templated classes, as it is hard to spell the class name,
411
+ // i.e if the template parameter is unnamed.
412
+ if (MD->getParent ()->isTemplated ())
413
+ return false ;
414
+
415
+ // The refactoring is meaningless for unnamed classes and namespaces,
416
+ // unless we're outlining in the same file
417
+ for (const DeclContext *DC = MD->getParent (); DC; DC = DC->getParent ()) {
418
+ if (auto *ND = llvm::dyn_cast<NamedDecl>(DC)) {
419
+ if (ND->getDeclName ().isEmpty () &&
420
+ (!SameFile || !llvm::dyn_cast<NamespaceDecl>(ND)))
421
+ return false ;
445
422
}
446
423
}
447
424
@@ -453,8 +430,8 @@ class DefineOutline : public Tweak {
453
430
454
431
Expected<Effect> apply (const Selection &Sel) override {
455
432
const SourceManager &SM = Sel.AST ->getSourceManager ();
456
- auto CCFile = getSourceFile ( Sel.AST ->tuPath (), Sel);
457
-
433
+ auto CCFile = SameFile ? Sel.AST ->tuPath (). str ()
434
+ : getSourceFile (Sel. AST -> tuPath (), Sel);
458
435
if (!CCFile)
459
436
return error (" Couldn't find a suitable implementation file." );
460
437
assert (Sel.FS && " FS Must be set in apply" );
@@ -464,8 +441,7 @@ class DefineOutline : public Tweak {
464
441
if (!Buffer)
465
442
return llvm::errorCodeToError (Buffer.getError ());
466
443
auto Contents = Buffer->get ()->getBuffer ();
467
- auto InsertionPoint = getInsertionPoint (
468
- Contents, Source->getQualifiedNameAsString (), Sel.AST ->getLangOpts ());
444
+ auto InsertionPoint = getInsertionPoint (Contents, Sel);
469
445
if (!InsertionPoint)
470
446
return InsertionPoint.takeError ();
471
447
@@ -499,17 +475,77 @@ class DefineOutline : public Tweak {
499
475
HeaderUpdates = HeaderUpdates.merge (*DelInline);
500
476
}
501
477
502
- auto HeaderFE = Effect::fileEdit (SM, SM.getMainFileID (), HeaderUpdates);
503
- if (!HeaderFE)
504
- return HeaderFE.takeError ();
505
-
506
- Effect->ApplyEdits .try_emplace (HeaderFE->first ,
507
- std::move (HeaderFE->second ));
478
+ if (SameFile) {
479
+ tooling::Replacements &R = Effect->ApplyEdits [*CCFile].Replacements ;
480
+ R = R.merge (HeaderUpdates);
481
+ } else {
482
+ auto HeaderFE = Effect::fileEdit (SM, SM.getMainFileID (), HeaderUpdates);
483
+ if (!HeaderFE)
484
+ return HeaderFE.takeError ();
485
+ Effect->ApplyEdits .try_emplace (HeaderFE->first ,
486
+ std::move (HeaderFE->second ));
487
+ }
508
488
return std::move (*Effect);
509
489
}
510
490
491
+ // Returns the most natural insertion point for \p QualifiedName in \p
492
+ // Contents. This currently cares about only the namespace proximity, but in
493
+ // feature it should also try to follow ordering of declarations. For example,
494
+ // if decls come in order `foo, bar, baz` then this function should return
495
+ // some point between foo and baz for inserting bar.
496
+ // FIXME: The selection can be made smarter by looking at the definition
497
+ // locations for adjacent decls to Source. Unfortunately pseudo parsing in
498
+ // getEligibleRegions only knows about namespace begin/end events so we
499
+ // can't match function start/end positions yet.
500
+ llvm::Expected<InsertionPoint> getInsertionPoint (llvm::StringRef Contents,
501
+ const Selection &Sel) {
502
+ // If the definition goes to the same file and there is a namespace,
503
+ // we should (and, in the case of anonymous namespaces, need to)
504
+ // put the definition into the original namespace block.
505
+ if (SameFile) {
506
+ auto *Klass = Source->getDeclContext ()->getOuterLexicalRecordContext ();
507
+ if (!Klass)
508
+ return error (" moving to same file not supported for free functions" );
509
+ const SourceLocation EndLoc = Klass->getBraceRange ().getEnd ();
510
+ const auto &TokBuf = Sel.AST ->getTokens ();
511
+ auto Tokens = TokBuf.expandedTokens ();
512
+ auto It = llvm::lower_bound (
513
+ Tokens, EndLoc, [](const syntax::Token &Tok, SourceLocation EndLoc) {
514
+ return Tok.location () < EndLoc;
515
+ });
516
+ while (It != Tokens.end ()) {
517
+ if (It->kind () != tok::semi) {
518
+ ++It;
519
+ continue ;
520
+ }
521
+ unsigned Offset = Sel.AST ->getSourceManager ()
522
+ .getDecomposedLoc (It->endLocation ())
523
+ .second ;
524
+ return InsertionPoint{Klass->getEnclosingNamespaceContext (), Offset};
525
+ }
526
+ return error (
527
+ " failed to determine insertion location: no end of class found" );
528
+ }
529
+
530
+ auto Region = getEligiblePoints (
531
+ Contents, Source->getQualifiedNameAsString (), Sel.AST ->getLangOpts ());
532
+
533
+ assert (!Region.EligiblePoints .empty ());
534
+ auto Offset = positionToOffset (Contents, Region.EligiblePoints .back ());
535
+ if (!Offset)
536
+ return Offset.takeError ();
537
+
538
+ auto TargetContext =
539
+ findContextForNS (Region.EnclosingNamespace , Source->getDeclContext ());
540
+ if (!TargetContext)
541
+ return error (" define outline: couldn't find a context for target" );
542
+
543
+ return InsertionPoint{*TargetContext, *Offset};
544
+ }
545
+
511
546
private:
512
547
const FunctionDecl *Source = nullptr ;
548
+ bool SameFile = false ;
513
549
};
514
550
515
551
REGISTER_TWEAK (DefineOutline)
0 commit comments