Skip to content

RangeInfo: unbox switch statement to ensure all branches agree on return information. rdar://32289109 #9788

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 19, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 20 additions & 6 deletions include/swift/IDE/Utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -246,11 +246,24 @@ enum class OrphanKind : int8_t {
Continue,
};

typedef llvm::PointerIntPair<TypeBase*, 1, bool> ReturnTyAndWhetherExit;
enum class ExitState: int8_t {
Positive,
Negative,
Unsure,
};

struct ReturnInfo {
TypeBase* ReturnType;
ExitState Exit;
ReturnInfo(): ReturnInfo(nullptr, ExitState::Unsure) {}
ReturnInfo(TypeBase* ReturnType, ExitState Exit):
ReturnType(ReturnType), Exit(Exit) {}
ReturnInfo(ASTContext &Ctx, ArrayRef<ReturnInfo> Branches);
};

struct ResolvedRangeInfo {
RangeKind Kind;
ReturnTyAndWhetherExit ExitInfo;
ReturnInfo ExitInfo;
CharSourceRange Content;
bool HasSingleEntry;
bool ThrowingUnhandledError;
Expand All @@ -263,7 +276,7 @@ struct ResolvedRangeInfo {
DeclContext* RangeContext;
Expr* CommonExprParent;

ResolvedRangeInfo(RangeKind Kind, ReturnTyAndWhetherExit ExitInfo,
ResolvedRangeInfo(RangeKind Kind, ReturnInfo ExitInfo,
CharSourceRange Content, DeclContext* RangeContext,
Expr *CommonExprParent, bool HasSingleEntry,
bool ThrowingUnhandledError,
Expand All @@ -279,13 +292,14 @@ struct ResolvedRangeInfo {
RangeContext(RangeContext),
CommonExprParent(CommonExprParent) {}
ResolvedRangeInfo(CharSourceRange Content) :
ResolvedRangeInfo(RangeKind::Invalid, {nullptr, false}, Content,
ResolvedRangeInfo(RangeKind::Invalid, {nullptr, ExitState::Unsure}, Content,
nullptr, /*Commom Expr Parent*/nullptr,
/*Single entry*/true, /*unhandled error*/false,
OrphanKind::None, {}, {}, {}) {}
ResolvedRangeInfo(): ResolvedRangeInfo(CharSourceRange()) {}
void print(llvm::raw_ostream &OS);
bool exit() const { return ExitInfo.getInt(); }
Type getType() const { return ExitInfo.getPointer(); }
ExitState exit() const { return ExitInfo.Exit; }
Type getType() const { return ExitInfo.ReturnType; }
};

class RangeResolver : public SourceEntityWalker {
Expand Down
70 changes: 46 additions & 24 deletions lib/IDE/SwiftSourceDocInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,12 +229,14 @@ void ResolvedRangeInfo::print(llvm::raw_ostream &OS) {

OS << "<Content>" << Content.str() << "</Content>\n";

if (auto Ty = ExitInfo.getPointer()) {
if (auto Ty = getType()) {
OS << "<Type>";
Ty->print(OS);
OS << "</Type>";
if (ExitInfo.getInt()) {
OS << "<Exit>true</Exit>";
switch(exit()) {
case ExitState::Positive: OS << "<Exit>true</Exit>"; break;
case ExitState::Unsure: OS << "<Exit>unsure</Exit>"; break;
case ExitState::Negative: OS << "<Exit>false</Exit>"; break;
}
OS << "\n";
}
Expand Down Expand Up @@ -335,6 +337,23 @@ static bool hasUnhandledError(ArrayRef<ASTNode> Nodes) {
});
}

ReturnInfo::
ReturnInfo(ASTContext &Ctx, ArrayRef<ReturnInfo> Branches):
ReturnType(Ctx.TheErrorType.getPointer()), Exit(ExitState::Unsure) {
std::set<TypeBase*> AllTypes;
std::set<ExitState> AllExitStates;
for (auto I : Branches) {
AllTypes.insert(I.ReturnType);
AllExitStates.insert(I.Exit);
}
if (AllTypes.size() == 1) {
ReturnType = *AllTypes.begin();
}
if (AllExitStates.size() == 1) {
Exit = *AllExitStates.begin();
}
}

struct RangeResolver::Implementation {
SourceFile &File;
ASTContext &Ctx;
Expand Down Expand Up @@ -394,10 +413,10 @@ struct RangeResolver::Implementation {
std::vector<ASTNode> ContainedASTNodes;

/// Collect the type that an ASTNode should be evaluated to.
ReturnTyAndWhetherExit resolveNodeType(ASTNode N, RangeKind Kind) {
ReturnInfo resolveNodeType(ASTNode N, RangeKind Kind) {
auto *VoidTy = Ctx.getVoidDecl()->getDeclaredInterfaceType().getPointer();
if (N.isNull())
return {VoidTy, false};
return {VoidTy, ExitState::Negative};
switch(Kind) {
case RangeKind::Invalid:
case RangeKind::SingleDecl:
Expand All @@ -406,7 +425,7 @@ struct RangeResolver::Implementation {

// For a single expression, its type is apparent.
case RangeKind::SingleExpression:
return {N.get<Expr*>()->getType().getPointer(), false};
return {N.get<Expr*>()->getType().getPointer(), ExitState::Negative};

// For statements, we either resolve to the returning type or Void.
case RangeKind::SingleStatement:
Expand All @@ -415,9 +434,7 @@ struct RangeResolver::Implementation {
if (auto RS = dyn_cast<ReturnStmt>(N.get<Stmt*>())) {
return {
resolveNodeType(RS->hasResult() ? RS->getResult() : nullptr,
RangeKind::SingleExpression).getPointer(),
true
};
RangeKind::SingleExpression).ReturnType, ExitState::Positive };
}

// Unbox the brace statement to find its type.
Expand All @@ -430,22 +447,26 @@ struct RangeResolver::Implementation {

// Unbox the if statement to find its type.
if (auto *IS = dyn_cast<IfStmt>(N.get<Stmt*>())) {
auto ThenTy = resolveNodeType(IS->getThenStmt(),
RangeKind::SingleStatement);
auto ElseTy = resolveNodeType(IS->getElseStmt(),
RangeKind::SingleStatement);

// If two branches agree on the return type, return that type.
if (ThenTy.getPointer()->isEqual(ElseTy.getPointer()) &&
ThenTy.getInt() == ElseTy.getInt())
return ThenTy;

// Otherwise, return the error type.
return {Ctx.TheErrorType.getPointer(), false};
llvm::SmallVector<ReturnInfo, 2> Branches;
Branches.push_back(resolveNodeType(IS->getThenStmt(),
RangeKind::SingleStatement));
Branches.push_back(resolveNodeType(IS->getElseStmt(),
RangeKind::SingleStatement));
return {Ctx, Branches};
}

// Unbox switch statement to find return information.
if (auto *SWS = dyn_cast<SwitchStmt>(N.get<Stmt*>())) {
llvm::SmallVector<ReturnInfo, 4> Branches;
for (auto *CS : SWS->getCases()) {
Branches.push_back(resolveNodeType(CS->getBody(),
RangeKind::SingleStatement));
}
return {Ctx, Branches};
}
}
// For other statements, the type should be void.
return {VoidTy, false};
return {VoidTy, ExitState::Negative};
}
}
}
Expand Down Expand Up @@ -480,7 +501,8 @@ struct RangeResolver::Implementation {
llvm::makeArrayRef(ReferencedDecls));
else {
assert(Node.is<Decl*>());
return ResolvedRangeInfo(RangeKind::SingleDecl, {nullptr, false}, Content,
return ResolvedRangeInfo(RangeKind::SingleDecl,
ReturnInfo(), Content,
getImmediateContext(),
/*Common Parent Expr*/nullptr,
SingleEntry,
Expand Down Expand Up @@ -540,7 +562,7 @@ struct RangeResolver::Implementation {
if (!hasResult() && !Node.isImplicit() && nodeContainSelection(Node)) {
if (auto Parent = Node.is<Expr*>() ? Node.get<Expr*>() : nullptr) {
Result = {
RangeKind::PartOfExpression, {nullptr, false}, Content,
RangeKind::PartOfExpression, ReturnInfo(), Content,
getImmediateContext(),
Parent,
hasSingleEntryPoint(ContainedASTNodes),
Expand Down
40 changes: 40 additions & 0 deletions test/IDE/range_info_branches.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,47 @@ func foo1(_ a: Bool) {
}
}

enum MyEnum {
case Case1
case Case2
case Case3
}

func foo2(_ e : MyEnum) -> Int {
switch e {
case .Case1:
break
case .Case2:
break
case .Case3:
break
}
switch e {
case .Case1:
return 1
case .Case2:
return 2
case .Case3:
return 3
}
switch e {
case .Case1:
return 1
case .Case2:
return 2
case .Case3:
break
}
}

// RUN: %target-swift-ide-test -range -pos=2:1 -end-pos 5:4 -source-filename %s | %FileCheck %s -check-prefix=CHECK-ERR
// RUN: %target-swift-ide-test -range -pos=6:1 -end-pos 10:4 -source-filename %s | %FileCheck %s -check-prefix=CHECK-INT
// RUN: %target-swift-ide-test -range -pos=14:1 -end-pos 14:10 -source-filename %s | %FileCheck %s -check-prefix=CHECK-VOID-NO-RETURN
// RUN: %target-swift-ide-test -range -pos=15:1 -end-pos 16:10 -source-filename %s | %FileCheck %s -check-prefix=CHECK-VOID-NO-RETURN
// RUN: %target-swift-ide-test -range -pos=17:1 -end-pos 21:4 -source-filename %s | %FileCheck %s -check-prefix=CHECK-VOID-RETURN
// RUN: %target-swift-ide-test -range -pos=31:1 -end-pos 38:4 -source-filename %s | %FileCheck %s -check-prefix=CHECK-SWITCH1
// RUN: %target-swift-ide-test -range -pos=39:1 -end-pos 46:4 -source-filename %s | %FileCheck %s -check-prefix=CHECK-SWITCH2
// RUN: %target-swift-ide-test -range -pos=47:1 -end-pos 54:4 -source-filename %s | %FileCheck %s -check-prefix=CHECK-SWITCH3

// CHECK-ERR: <Type><<error type>></Type>
// CHECK-ERR-NOT: <Exit>true</Exit>
Expand All @@ -38,3 +74,7 @@ func foo1(_ a: Bool) {

// CHECK-VOID-RETURN: <Type>Void</Type>
// CHECK-VOID-RETURN: <Exit>true</Exit>

// CHECK-SWITCH1: <Type>Void</Type><Exit>false</Exit>
// CHECK-SWITCH2: <Type>Int</Type><Exit>true</Exit>
// CHECK-SWITCH3: <Type><<error type>></Type><Exit>unsure</Exit>
2 changes: 1 addition & 1 deletion tools/SourceKit/lib/SwiftLang/SwiftSourceDocInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1346,7 +1346,7 @@ static void resolveRange(SwiftLangSupport &Lang,
case RangeKind::SingleExpression: {
SmallString<64> SS;
llvm::raw_svector_ostream OS(SS);
Info.ExitInfo.getPointer()->print(OS);
Info.ExitInfo.ReturnType->print(OS);
Result.ExprType = OS.str();
Receiver(Result);
return;
Expand Down