mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[IDE] Refactoring: Expand a ternary operator into an if statement and vice-versa (#12554)
This commit is contained in:
@@ -1793,6 +1793,395 @@ bool RefactoringActionConvertStringsConcatenationToInterpolation::performChange(
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Abstract helper class containing info about an IfExpr
|
||||
/// that can be expanded into an IfStmt.
|
||||
class ExpandableTernaryExprInfo {
|
||||
|
||||
public:
|
||||
virtual ~ExpandableTernaryExprInfo() {}
|
||||
|
||||
virtual IfExpr *getIf() = 0;
|
||||
|
||||
virtual SourceRange getNameRange() = 0;
|
||||
|
||||
virtual Type getType() = 0;
|
||||
|
||||
virtual bool shouldDeclareNameAndType() {
|
||||
return !getType().isNull();
|
||||
}
|
||||
|
||||
virtual bool isValid() {
|
||||
|
||||
//Ensure all public properties are non-nil and valid
|
||||
if (!getIf() || !getNameRange().isValid())
|
||||
return false;
|
||||
if (shouldDeclareNameAndType() && getType().isNull())
|
||||
return false;
|
||||
|
||||
return true; //valid
|
||||
}
|
||||
|
||||
CharSourceRange getNameCharRange(const SourceManager &SM) {
|
||||
return Lexer::getCharSourceRangeFromSourceRange(SM, getNameRange());
|
||||
}
|
||||
};
|
||||
|
||||
/// Concrete subclass containing info about an AssignExpr
|
||||
/// where the source is the expandable IfExpr.
|
||||
class ExpandableAssignTernaryExprInfo: public ExpandableTernaryExprInfo {
|
||||
|
||||
public:
|
||||
ExpandableAssignTernaryExprInfo(AssignExpr *Assign): Assign(Assign) {}
|
||||
|
||||
IfExpr *getIf() {
|
||||
if (!Assign)
|
||||
return nullptr;
|
||||
|
||||
return dyn_cast<IfExpr>(Assign->getSrc());
|
||||
}
|
||||
|
||||
SourceRange getNameRange() {
|
||||
auto Invalid = SourceRange();
|
||||
|
||||
if (!Assign)
|
||||
return Invalid;
|
||||
|
||||
if (auto dest = Assign->getDest())
|
||||
return dest->getSourceRange();
|
||||
|
||||
return Invalid;
|
||||
}
|
||||
|
||||
Type getType() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
AssignExpr *Assign = nullptr;
|
||||
};
|
||||
|
||||
/// Concrete subclass containing info about a PatternBindingDecl
|
||||
/// where the pattern initializer is the expandable IfExpr.
|
||||
class ExpandableBindingTernaryExprInfo: public ExpandableTernaryExprInfo {
|
||||
|
||||
public:
|
||||
ExpandableBindingTernaryExprInfo(PatternBindingDecl *Binding):
|
||||
Binding(Binding) {}
|
||||
|
||||
IfExpr *getIf() {
|
||||
if (Binding && Binding->getNumPatternEntries() == 1)
|
||||
return dyn_cast<IfExpr>(Binding->getInit(0));
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SourceRange getNameRange() {
|
||||
if (auto Pattern = getNamePattern())
|
||||
return Pattern->getSourceRange();
|
||||
|
||||
return SourceRange();
|
||||
}
|
||||
|
||||
Type getType() {
|
||||
if (auto Pattern = getNamePattern())
|
||||
return Pattern->getType();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
Pattern *getNamePattern() {
|
||||
if (!Binding || Binding->getNumPatternEntries() != 1)
|
||||
return nullptr;
|
||||
|
||||
auto Pattern = Binding->getPattern(0);
|
||||
|
||||
if (!Pattern)
|
||||
return nullptr;
|
||||
|
||||
if (auto TyPattern = dyn_cast<TypedPattern>(Pattern))
|
||||
Pattern = TyPattern->getSubPattern();
|
||||
|
||||
return Pattern;
|
||||
}
|
||||
|
||||
PatternBindingDecl *Binding = nullptr;
|
||||
};
|
||||
|
||||
std::unique_ptr<ExpandableTernaryExprInfo>
|
||||
findExpandableTernaryExpression(ResolvedRangeInfo Info) {
|
||||
|
||||
if (Info.Kind != RangeKind::SingleDecl
|
||||
&& Info.Kind != RangeKind:: SingleExpression)
|
||||
return nullptr;
|
||||
|
||||
if (Info.ContainedNodes.size() != 1)
|
||||
return nullptr;
|
||||
|
||||
if (auto D = Info.ContainedNodes[0].dyn_cast<Decl*>())
|
||||
if (auto Binding = dyn_cast<PatternBindingDecl>(D))
|
||||
return llvm::make_unique<ExpandableBindingTernaryExprInfo>(Binding);
|
||||
|
||||
if (auto E = Info.ContainedNodes[0].dyn_cast<Expr*>())
|
||||
if (auto Assign = dyn_cast<AssignExpr>(E))
|
||||
return llvm::make_unique<ExpandableAssignTernaryExprInfo>(Assign);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool RefactoringActionExpandTernaryExpr::
|
||||
isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag) {
|
||||
auto Target = findExpandableTernaryExpression(Info);
|
||||
return Target && Target->isValid();
|
||||
}
|
||||
|
||||
bool RefactoringActionExpandTernaryExpr::performChange() {
|
||||
auto Target = findExpandableTernaryExpression(RangeInfo);
|
||||
|
||||
if (!Target || !Target->isValid())
|
||||
return true; //abort
|
||||
|
||||
auto NameCharRange = Target->getNameCharRange(SM);
|
||||
|
||||
auto IfRange = Target->getIf()->getSourceRange();
|
||||
auto IfCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, IfRange);
|
||||
|
||||
auto CondRange = Target->getIf()->getCondExpr()->getSourceRange();
|
||||
auto CondCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, CondRange);
|
||||
|
||||
auto ThenRange = Target->getIf()->getThenExpr()->getSourceRange();
|
||||
auto ThenCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, ThenRange);
|
||||
|
||||
auto ElseRange = Target->getIf()->getElseExpr()->getSourceRange();
|
||||
auto ElseCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, ElseRange);
|
||||
|
||||
llvm::SmallString<64> DeclBuffer;
|
||||
llvm::raw_svector_ostream OS(DeclBuffer);
|
||||
|
||||
llvm::StringRef Space = " ";
|
||||
llvm::StringRef NewLine = "\n";
|
||||
|
||||
if (Target->shouldDeclareNameAndType()) {
|
||||
//Specifier will not be replaced; append after specifier
|
||||
OS << NameCharRange.str() << tok::colon << Space;
|
||||
OS << Target->getType() << NewLine;
|
||||
}
|
||||
|
||||
OS << tok::kw_if << Space;
|
||||
OS << CondCharRange.str() << Space;
|
||||
OS << tok::l_brace << NewLine;
|
||||
|
||||
OS << NameCharRange.str() << Space;
|
||||
OS << tok::equal << Space;
|
||||
OS << ThenCharRange.str() << NewLine;
|
||||
|
||||
OS << tok::r_brace << Space;
|
||||
OS << tok::kw_else << Space;
|
||||
OS << tok::l_brace << NewLine;
|
||||
|
||||
OS << NameCharRange.str() << Space;
|
||||
OS << tok::equal << Space;
|
||||
OS << ElseCharRange.str() << NewLine;
|
||||
|
||||
OS << tok::r_brace;
|
||||
|
||||
//Start replacement with name range, skip the specifier
|
||||
auto ReplaceRange(NameCharRange);
|
||||
ReplaceRange.widen(IfCharRange);
|
||||
|
||||
EditConsumer.accept(SM, ReplaceRange, DeclBuffer.str());
|
||||
|
||||
return false; //don't abort
|
||||
}
|
||||
|
||||
/// Struct containing info about an IfStmt that can be converted into an IfExpr.
|
||||
struct ConvertToTernaryExprInfo {
|
||||
ConvertToTernaryExprInfo() {}
|
||||
|
||||
Expr *AssignDest() {
|
||||
|
||||
if (!Then || !Then->getDest() || !Else || !Else->getDest())
|
||||
return nullptr;
|
||||
|
||||
auto ThenDest = Then->getDest();
|
||||
auto ElseDest = Else->getDest();
|
||||
|
||||
if (ThenDest->getKind() != ElseDest->getKind())
|
||||
return nullptr;
|
||||
|
||||
switch (ThenDest->getKind()) {
|
||||
case ExprKind::DeclRef: {
|
||||
auto ThenRef = dyn_cast<DeclRefExpr>(Then->getDest());
|
||||
auto ElseRef = dyn_cast<DeclRefExpr>(Else->getDest());
|
||||
|
||||
if (!ThenRef || !ThenRef->getDecl() || !ElseRef || !ElseRef->getDecl())
|
||||
return nullptr;
|
||||
|
||||
auto ThenName = ThenRef->getDecl()->getFullName();
|
||||
auto ElseName = ElseRef->getDecl()->getFullName();
|
||||
|
||||
if (ThenName.compare(ElseName) != 0)
|
||||
return nullptr;
|
||||
|
||||
return Then->getDest();
|
||||
}
|
||||
case ExprKind::Tuple: {
|
||||
auto ThenTuple = dyn_cast<TupleExpr>(Then->getDest());
|
||||
auto ElseTuple = dyn_cast<TupleExpr>(Else->getDest());
|
||||
|
||||
if (!ThenTuple || !ElseTuple)
|
||||
return nullptr;
|
||||
|
||||
auto ThenNames = ThenTuple->getElementNames();
|
||||
auto ElseNames = ElseTuple->getElementNames();
|
||||
|
||||
if (!ThenNames.equals(ElseNames))
|
||||
return nullptr;
|
||||
|
||||
return ThenTuple;
|
||||
}
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
Expr *ThenSrc() {
|
||||
if (!Then)
|
||||
return nullptr;
|
||||
return Then->getSrc();
|
||||
}
|
||||
|
||||
Expr *ElseSrc() {
|
||||
if (!Else)
|
||||
return nullptr;
|
||||
return Else->getSrc();
|
||||
}
|
||||
|
||||
bool isValid() {
|
||||
if (!Cond || !AssignDest() || !ThenSrc() || !ElseSrc()
|
||||
|| !IfRange.isValid())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
PatternBindingDecl *Binding = nullptr; //optional
|
||||
|
||||
Expr *Cond = nullptr; //required
|
||||
AssignExpr *Then = nullptr; //required
|
||||
AssignExpr *Else = nullptr; //required
|
||||
SourceRange IfRange;
|
||||
};
|
||||
|
||||
ConvertToTernaryExprInfo
|
||||
findConvertToTernaryExpression(ResolvedRangeInfo Info) {
|
||||
|
||||
auto notFound = ConvertToTernaryExprInfo();
|
||||
|
||||
if (Info.Kind != RangeKind::SingleStatement
|
||||
&& Info.Kind != RangeKind::MultiStatement)
|
||||
return notFound;
|
||||
|
||||
if (Info.ContainedNodes.size() == 0)
|
||||
return notFound;
|
||||
|
||||
struct AssignExprFinder: public SourceEntityWalker {
|
||||
|
||||
AssignExpr *Assign = nullptr;
|
||||
|
||||
AssignExprFinder(Stmt* S) {
|
||||
if (S)
|
||||
walk(S);
|
||||
}
|
||||
|
||||
virtual bool walkToExprPre(Expr *E) {
|
||||
Assign = dyn_cast<AssignExpr>(E);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
ConvertToTernaryExprInfo Target;
|
||||
|
||||
IfStmt *If = nullptr;
|
||||
|
||||
if (Info.ContainedNodes.size() == 1) {
|
||||
if (auto S = Info.ContainedNodes[0].dyn_cast<Stmt*>())
|
||||
If = dyn_cast<IfStmt>(S);
|
||||
}
|
||||
|
||||
if (Info.ContainedNodes.size() == 2) {
|
||||
if (auto D = Info.ContainedNodes[0].dyn_cast<Decl*>())
|
||||
Target.Binding = dyn_cast<PatternBindingDecl>(D);
|
||||
if (auto S = Info.ContainedNodes[1].dyn_cast<Stmt*>())
|
||||
If = dyn_cast<IfStmt>(S);
|
||||
}
|
||||
|
||||
if (!If)
|
||||
return notFound;
|
||||
|
||||
auto CondList = If->getCond();
|
||||
|
||||
if (CondList.size() != 1)
|
||||
return notFound;
|
||||
|
||||
Target.Cond = CondList[0].getBooleanOrNull();
|
||||
Target.IfRange = If->getSourceRange();
|
||||
|
||||
Target.Then = AssignExprFinder(If->getThenStmt()).Assign;
|
||||
Target.Else = AssignExprFinder(If->getElseStmt()).Assign;
|
||||
|
||||
return Target;
|
||||
}
|
||||
|
||||
bool RefactoringActionConvertToTernaryExpr::
|
||||
isApplicable(ResolvedRangeInfo Info, DiagnosticEngine &Diag) {
|
||||
return findConvertToTernaryExpression(Info).isValid();
|
||||
}
|
||||
|
||||
bool RefactoringActionConvertToTernaryExpr::performChange() {
|
||||
auto Target = findConvertToTernaryExpression(RangeInfo);
|
||||
|
||||
if (!Target.isValid())
|
||||
return true; //abort
|
||||
|
||||
llvm::SmallString<64> DeclBuffer;
|
||||
llvm::raw_svector_ostream OS(DeclBuffer);
|
||||
|
||||
llvm::StringRef Space = " ";
|
||||
|
||||
auto IfRange = Target.IfRange;
|
||||
auto ReplaceRange = Lexer::getCharSourceRangeFromSourceRange(SM, IfRange);
|
||||
|
||||
auto CondRange = Target.Cond->getSourceRange();
|
||||
auto CondCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, CondRange);
|
||||
|
||||
auto ThenRange = Target.ThenSrc()->getSourceRange();
|
||||
auto ThenCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, ThenRange);
|
||||
|
||||
auto ElseRange = Target.ElseSrc()->getSourceRange();
|
||||
auto ElseCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, ElseRange);
|
||||
|
||||
CharSourceRange DestCharRange;
|
||||
|
||||
if (Target.Binding) {
|
||||
auto DestRange = Target.Binding->getSourceRange();
|
||||
DestCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, DestRange);
|
||||
ReplaceRange.widen(DestCharRange);
|
||||
} else {
|
||||
auto DestRange = Target.AssignDest()->getSourceRange();
|
||||
DestCharRange = Lexer::getCharSourceRangeFromSourceRange(SM, DestRange);
|
||||
}
|
||||
|
||||
OS << DestCharRange.str() << Space << tok::equal << Space;
|
||||
OS << CondCharRange.str() << Space << tok::question_postfix << Space;
|
||||
OS << ThenCharRange.str() << Space << tok::colon << Space;
|
||||
OS << ElseCharRange.str();
|
||||
|
||||
EditConsumer.accept(SM, ReplaceRange, DeclBuffer.str());
|
||||
|
||||
return false; //don't abort
|
||||
}
|
||||
|
||||
/// The helper class analyzes a given nominal decl or an extension decl to
|
||||
/// decide whether stubs are required to filled in and the context in which
|
||||
/// these stubs should be filled.
|
||||
|
||||
Reference in New Issue
Block a user