@@ -179,14 +179,11 @@ deleteTokensWithKind(const syntax::TokenBuffer &TokBuf, tok::TokenKind Kind,
179179// looked up in the context containing the function/method.
180180// FIXME: Drop attributes in function signature.
181181llvm::Expected<std::string>
182- getFunctionSourceCode (const FunctionDecl *FD, llvm::StringRef TargetNamespace ,
182+ getFunctionSourceCode (const FunctionDecl *FD, const DeclContext *TargetContext ,
183183 const syntax::TokenBuffer &TokBuf,
184184 const HeuristicResolver *Resolver) {
185185 auto &AST = FD->getASTContext ();
186186 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" );
190187
191188 llvm::Error Errors = llvm::Error::success ();
192189 tooling::Replacements DeclarationCleanups;
@@ -216,7 +213,7 @@ getFunctionSourceCode(const FunctionDecl *FD, llvm::StringRef TargetNamespace,
216213 }
217214 const NamedDecl *ND = Ref.Targets .front ();
218215 const std::string Qualifier =
219- getQualification (AST, * TargetContext,
216+ getQualification (AST, TargetContext,
220217 SM.getLocForStartOfFile (SM.getMainFileID ()), ND);
221218 if (auto Err = DeclarationCleanups.add (
222219 tooling::Replacement (SM, Ref.NameLoc , 0 , Qualifier)))
@@ -232,7 +229,7 @@ getFunctionSourceCode(const FunctionDecl *FD, llvm::StringRef TargetNamespace,
232229 if (const auto *Destructor = llvm::dyn_cast<CXXDestructorDecl>(FD)) {
233230 if (auto Err = DeclarationCleanups.add (tooling::Replacement (
234231 SM, Destructor->getLocation (), 0 ,
235- getQualification (AST, * TargetContext,
232+ getQualification (AST, TargetContext,
236233 SM.getLocForStartOfFile (SM.getMainFileID ()),
237234 Destructor))))
238235 Errors = llvm::joinErrors (std::move (Errors), std::move (Err));
@@ -319,29 +316,9 @@ getFunctionSourceCode(const FunctionDecl *FD, llvm::StringRef TargetNamespace,
319316}
320317
321318struct InsertionPoint {
322- std::string EnclosingNamespace;
319+ const DeclContext * EnclosingNamespace = nullptr ;
323320 size_t Offset;
324321};
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- }
345322
346323// Returns the range that should be deleted from declaration, which always
347324// contains function body. In addition to that it might contain constructor
@@ -409,14 +386,9 @@ class DefineOutline : public Tweak {
409386 }
410387
411388 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 ());
419390 Source = getSelectedFunction (Sel.ASTSelection .commonAncestor ());
391+
420392 // Bail out if the selection is not a in-line function definition.
421393 if (!Source || !Source->doesThisDeclarationHaveABody () ||
422394 Source->isOutOfLine ())
@@ -429,19 +401,24 @@ class DefineOutline : public Tweak {
429401 if (Source->getTemplateSpecializationInfo ())
430402 return false ;
431403
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 ;
445422 }
446423 }
447424
@@ -453,8 +430,8 @@ class DefineOutline : public Tweak {
453430
454431 Expected<Effect> apply (const Selection &Sel) override {
455432 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);
458435 if (!CCFile)
459436 return error (" Couldn't find a suitable implementation file." );
460437 assert (Sel.FS && " FS Must be set in apply" );
@@ -464,8 +441,7 @@ class DefineOutline : public Tweak {
464441 if (!Buffer)
465442 return llvm::errorCodeToError (Buffer.getError ());
466443 auto Contents = Buffer->get ()->getBuffer ();
467- auto InsertionPoint = getInsertionPoint (
468- Contents, Source->getQualifiedNameAsString (), Sel.AST ->getLangOpts ());
444+ auto InsertionPoint = getInsertionPoint (Contents, Sel);
469445 if (!InsertionPoint)
470446 return InsertionPoint.takeError ();
471447
@@ -499,17 +475,77 @@ class DefineOutline : public Tweak {
499475 HeaderUpdates = HeaderUpdates.merge (*DelInline);
500476 }
501477
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+ }
508488 return std::move (*Effect);
509489 }
510490
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+
511546private:
512547 const FunctionDecl *Source = nullptr ;
548+ bool SameFile = false ;
513549};
514550
515551REGISTER_TWEAK (DefineOutline)
0 commit comments