Skip to content

[6.0][CodeCompletion] Fix completion for 'catch' pattern bound values #73189

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
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
8 changes: 7 additions & 1 deletion lib/Sema/TypeCheckDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2746,9 +2746,15 @@ NamingPatternRequest::evaluate(Evaluator &evaluator, VarDecl *VD) const {
// We have some other parent stmt. Type check it completely.
if (auto CS = dyn_cast<CaseStmt>(parentStmt))
parentStmt = CS->getParentStmt();

bool LeaveBodyUnchecked = true;
// type-checking 'catch' patterns depends on the type checked body.
if (isa<DoCatchStmt>(parentStmt))
LeaveBodyUnchecked = false;

ASTNode node(parentStmt);
TypeChecker::typeCheckASTNode(node, VD->getDeclContext(),
/*LeaveBodyUnchecked=*/true);
LeaveBodyUnchecked);
}
namingPattern = VD->getCanonicalVarDecl()->NamingPattern;
}
Expand Down
8 changes: 8 additions & 0 deletions lib/Sema/TypeCheckStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1669,6 +1669,14 @@ class StmtChecker : public StmtVisitor<StmtChecker, Stmt*> {
bool limitExhaustivityChecks = true;

Type caughtErrorType = TypeChecker::catchErrorType(DC, S);

// If there was no throwing expression in the body, let's pretend it can
// throw 'any Error' just for type checking the pattern. That avoids
// superfluous diagnostics. Note that we still diagnose unreachable 'catch'
// separately in TypeCheckEffects.
if (caughtErrorType->isNever())
caughtErrorType = Ctx.getErrorExistentialType();

auto catches = S->getCatches();
checkSiblingCaseStmts(catches.begin(), catches.end(),
CaseParentKind::DoCatch, limitExhaustivityChecks,
Expand Down
136 changes: 46 additions & 90 deletions test/IDE/complete_exception.swift
Original file line number Diff line number Diff line change
@@ -1,54 +1,5 @@
// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -code-completion -source-filename %s -code-completion-token=CATCH1 | %FileCheck %s -check-prefix=CATCH1
// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -code-completion -source-filename %s -code-completion-token=THROW1 > %t.throw1
// RUN: %FileCheck %s -check-prefix=THROW1 < %t.throw1
// RUN: %FileCheck %s -check-prefix=THROW1-LOCAL < %t.throw1
// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -code-completion -source-filename %s -code-completion-token=CATCH2 | %FileCheck %s -check-prefix=CATCH2
// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -code-completion -source-filename %s -code-completion-token=THROW2 | %FileCheck %s -check-prefix=THROW2
// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -code-completion -source-filename %s -code-completion-token=CATCH3 | %FileCheck %s -check-prefix=CATCH3
// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -code-completion -source-filename %s -code-completion-token=THROW3 | %FileCheck %s -check-prefix=THROW3
// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -code-completion -source-filename %s -code-completion-token=TOP_LEVEL_CATCH1 | %FileCheck %s -check-prefix=CATCH1
// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -code-completion -source-filename %s -code-completion-token=TOP_LEVEL_THROW1 | %FileCheck %s -check-prefix=THROW1

// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -code-completion -source-filename %s -code-completion-token=TOP_LEVEL_CATCH2 | %FileCheck %s -check-prefix=CATCH2
// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -code-completion -source-filename %s -code-completion-token=TOP_LEVEL_THROW2 | %FileCheck %s -check-prefix=THROW2
// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -code-completion -source-filename %s -code-completion-token=TOP_LEVEL_THROW3 | %FileCheck %s -check-prefix=THROW3

// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -code-completion -source-filename %s -code-completion-token=INSIDE_CATCH1 > %t.inside_catch1
// RUN: %FileCheck %s -check-prefix=STMT < %t.inside_catch1
// RUN: %FileCheck %s -check-prefix=IMPLICIT_ERROR < %t.inside_catch1

// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -code-completion -source-filename %s -code-completion-token=INSIDE_CATCH2 > %t.inside_catch2
// RUN: %FileCheck %s -check-prefix=STMT < %t.inside_catch2
// RUN: %FileCheck %s -check-prefix=EXPLICIT_ERROR_E < %t.inside_catch2

// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -code-completion -source-filename %s -code-completion-token=INSIDE_CATCH3 > %t.inside_catch3
// RUN: %FileCheck %s -check-prefix=STMT < %t.inside_catch3
// RUN: %FileCheck %s -check-prefix=EXPLICIT_NSERROR_E < %t.inside_catch3

// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -code-completion -source-filename %s -code-completion-token=INSIDE_CATCH4 > %t.inside_catch4
// RUN: %FileCheck %s -check-prefix=STMT < %t.inside_catch4
// RUN: %FileCheck %s -check-prefix=EXPLICIT_ERROR_PAYLOAD_I < %t.inside_catch4

// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -code-completion -source-filename %s -code-completion-token=INSIDE_CATCH5 > %t.inside_catch5
// RUN: %FileCheck %s -check-prefix=STMT < %t.inside_catch5
// RUN: %FileCheck %s -check-prefix=EXPLICIT_ERROR_E < %t.inside_catch5
// RUN: %FileCheck %s -check-prefix=NO_ERROR_AND_A < %t.inside_catch5

// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -code-completion -source-filename %s -code-completion-token=INSIDE_CATCH6 > %t.inside_catch6
// RUN: %FileCheck %s -check-prefix=STMT < %t.inside_catch6
// RUN: %FileCheck %s -check-prefix=NO_E < %t.inside_catch6
// RUN: %FileCheck %s -check-prefix=NO_ERROR_AND_A < %t.inside_catch6

// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -code-completion -source-filename %s -code-completion-token=INSIDE_CATCH_ERR_DOT1 | %FileCheck %s -check-prefix=ERROR_DOT
// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -code-completion -source-filename %s -code-completion-token=INSIDE_CATCH_ERR_DOT2 | %FileCheck %s -check-prefix=ERROR_DOT
// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -code-completion -source-filename %s -code-completion-token=INSIDE_CATCH_ERR_DOT3 | %FileCheck %s -check-prefix=NSERROR_DOT
// RUNFIXME: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -code-completion -source-filename %s -code-completion-token=INSIDE_CATCH_ERR_DOT4 | %FileCheck %s -check-prefix=INT_DOT

// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -code-completion -source-filename %s -code-completion-token=TOP_LEVEL_INSIDE_CATCH1 > %t.top_level_inside_catch1
// RUN: %FileCheck %s -check-prefix=STMT < %t.top_level_inside_catch1
// RUN: %FileCheck %s -check-prefix=IMPLICIT_ERROR < %t.top_level_inside_catch1

// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -code-completion -source-filename %s -code-completion-token=TOP_LEVEL_INSIDE_CATCH_ERR_DOT1 | %FileCheck %s -check-prefix=ERROR_DOT
// RUN: %empty-directory(%t/batch-code-completion)
// RUN: %target-swift-ide-test(mock-sdk: %clang-importer-sdk) -batch-code-completion -source-filename %s -filecheck %raw-FileCheck -completion-output-dir %t/batch-code-completion

// REQUIRES: objc_interop

Expand All @@ -71,10 +22,10 @@ func getNSError() -> NSError { return NSError(domain: "", code: 1, userInfo: [:]
func test001() {
do {} catch #^CATCH1^#

// CATCH1-DAG: Decl[Enum]/CurrModule: Error4[#Error4#]; name=Error4{{$}}
// CATCH1-DAG: Decl[Class]/CurrModule: Error3[#Error3#]; name=Error3{{$}}
// CATCH1-DAG: Decl[Class]/CurrModule: Error2[#Error2#]; name=Error2{{$}}
// CATCH1-DAG: Decl[Class]/CurrModule: Error1[#Error1#]; name=Error1{{$}}
// CATCH1-DAG: Decl[Enum]/CurrModule/TypeRelation[Convertible]: Error4[#Error4#]; name=Error4{{$}}
// CATCH1-DAG: Decl[Class]/CurrModule/TypeRelation[Convertible]: Error3[#Error3#]; name=Error3{{$}}
// CATCH1-DAG: Decl[Class]/CurrModule/TypeRelation[Convertible]: Error2[#Error2#]; name=Error2{{$}}
// CATCH1-DAG: Decl[Class]/CurrModule/TypeRelation[Convertible]: Error1[#Error1#]; name=Error1{{$}}
// CATCH1-DAG: Keyword[let]/None: let{{; name=.+$}}
// CATCH1-DAG: Decl[Class]/CurrModule: NoneError1[#NoneError1#]; name=NoneError1{{$}}
// CATCH1-DAG: Decl[Class]/OtherModule[Foundation]/IsSystem: NSError[#NSError#]{{; name=.+$}}
Expand All @@ -84,7 +35,7 @@ func test002() {
let text = "NonError"
let e1 = Error1()
let e2 = Error2()
throw #^THROW1^#
throw #^THROW1?check=THROW1,THROW1-LOCAL^#

// THROW1-DAG: Decl[Enum]/CurrModule/TypeRelation[Convertible]: Error4[#Error4#]; name=Error4{{$}}
// THROW1-DAG: Decl[Class]/CurrModule/TypeRelation[Convertible]: Error3[#Error3#]; name=Error3{{$}}
Expand All @@ -93,45 +44,42 @@ func test002() {
// THROW1-DAG: Decl[Protocol]/CurrModule/Flair[RareType]/TypeRelation[Convertible]: ErrorPro1[#ErrorPro1#]; name=ErrorPro1{{$}}
// THROW1-DAG: Decl[FreeFunction]/CurrModule/TypeRelation[Convertible]: getError1()[#Error1#]{{; name=.+$}}
// THROW1-DAG: Decl[FreeFunction]/CurrModule/TypeRelation[Convertible]: getNSError()[#NSError#]{{; name=.+$}}
// THROW1-DAG: Decl[Class]/CurrModule: NoneError1[#NoneError1#]; name=NoneError1{{$}}

// If we could prove that there is no way to get to an Error value by
// starting from these, we could remove them. But that may be infeasible in
// the presence of overloaded operators.
// THROW1-DAG: Decl[Class]/CurrModule: NoneError1[#NoneError1#]; name=NoneError1{{$}}
// THROW1-LOCAL-DAG: Decl[LocalVar]/Local: text[#String#]; name=text{{$}}
// THROW1-LOCAL-DAG: Decl[LocalVar]/Local/TypeRelation[Convertible]: e1[#Error1#]; name=e1{{$}}
// THROW1-LOCAL-DAG: Decl[LocalVar]/Local/TypeRelation[Convertible]: e2[#Error2#]; name=e2{{$}}
}

func test003() {
do {} catch Error4.#^CATCH2^#
// CATCH2: Decl[EnumElement]/CurrNominal: E1[#Error4#]{{; name=.+$}}
// CATCH2: Decl[EnumElement]/CurrNominal: E2({#Int32#})[#Error4#]{{; name=.+$}}
// CATCH2-DAG: Decl[EnumElement]/CurrNominal: E1[#Error4#]{{; name=.+$}}
// CATCH2-DAG: Decl[EnumElement]/CurrNominal: E2({#Int32#})[#Error4#]{{; name=.+$}}
}

func test004() {
throw Error4.#^THROW2^#
// THROW2: Decl[EnumElement]/CurrNominal/TypeRelation[Convertible]: E1[#Error4#]{{; name=.+$}}
// THROW2: Decl[EnumElement]/CurrNominal/TypeRelation[Convertible]: E2({#Int32#})[#Error4#]{{; name=.+$}}
// THROW2-DAG: Decl[EnumElement]/CurrNominal/TypeRelation[Convertible]: E1[#Error4#]{{; name=.+$}}
// THROW2-DAG: Decl[EnumElement]/CurrNominal/TypeRelation[Convertible]: E2({#Int32#})[#Error4#]{{; name=.+$}}
}

func test005() {
do {} catch Error4.E2#^CATCH3^#
// CATCH3: Pattern/CurrModule/Flair[ArgLabels]: ({#Int32#})[#Error4#]{{; name=.+$}}
// CATCH3-DAG: Pattern/CurrModule/Flair[ArgLabels]: ({#Int32#})[#Error4#]{{; name=.+$}}
}

func testInvalid() {
try throw Error4.#^THROW3^#
// THROW3: Decl[EnumElement]/CurrNominal/TypeRelation[Convertible]: E1[#Error4#]{{; name=.+$}}
// THROW3: Decl[EnumElement]/CurrNominal/TypeRelation[Convertible]: E2({#Int32#})[#Error4#]{{; name=.+$}}
// THROW3-DAG: Decl[EnumElement]/CurrNominal/TypeRelation[Convertible]: E1[#Error4#]{{; name=.+$}}
// THROW3-DAG: Decl[EnumElement]/CurrNominal/TypeRelation[Convertible]: E2({#Int32#})[#Error4#]{{; name=.+$}}
}

//===--- Top-level throw/catch
do {} catch #^TOP_LEVEL_CATCH1^# {}
throw #^TOP_LEVEL_THROW1^#
do {} catch Error4.#^TOP_LEVEL_CATCH2^# {}
throw Error4.#^TOP_LEVEL_THROW2^#
try throw Error4.#^TOP_LEVEL_THROW3^#
do {} catch #^TOP_LEVEL_CATCH1?check=CATCH1^# {}
throw #^TOP_LEVEL_THROW1?check=THROW1^#
do {} catch Error4.#^TOP_LEVEL_CATCH2?check=CATCH2^# {}
throw Error4.#^TOP_LEVEL_THROW2?check=THROW2^#
try throw Error4.#^TOP_LEVEL_THROW3?check=THROW3^#

//===--- Inside catch body

Expand All @@ -145,38 +93,37 @@ try throw Error4.#^TOP_LEVEL_THROW3^#
func test006() {
do {
} catch {
#^INSIDE_CATCH1^#
#^INSIDE_CATCH1?check=STMT,IMPLICIT_ERROR^#
}
// IMPLICIT_ERROR: Decl[LocalVar]/Local: error[#any Error#]; name=error
// IMPLICIT_ERROR-DAG: Decl[LocalVar]/Local: error[#any Error#]; name=error
}
func test007() {
do {
} catch let e {
#^INSIDE_CATCH2^#
#^INSIDE_CATCH2?check=STMT,EXPLICIT_ERROR_E^#
}
// EXPLICIT_ERROR_E: Decl[LocalVar]/Local: e[#any Error#]; name=e
// EXPLICIT_ERROR_E-DAG: Decl[LocalVar]/Local: e[#any Error#]; name=e
}
func test008() {
do {
} catch let e as NSError {
#^INSIDE_CATCH3^#
#^INSIDE_CATCH3?check=STMT,EXPLICIT_NSERROR_E^#
}
// EXPLICIT_NSERROR_E: Decl[LocalVar]/Local: e[#NSError#]; name=e
// EXPLICIT_NSERROR_E-DAG: Decl[LocalVar]/Local: e[#NSError#]; name=e
}
func test009() {
do {
} catch Error4.E2(let i) {
#^INSIDE_CATCH4^#
#^INSIDE_CATCH4?check=STMT,EXPLICIT_ERROR_PAYLOAD_I^#
}

// FIXME: we're getting parentheses around the type when it's unnamed...
// EXPLICIT_ERROR_PAYLOAD_I: Decl[LocalVar]/Local: i[#<<error type>>#]; name=i
// EXPLICIT_ERROR_PAYLOAD_I-DAG: Decl[LocalVar]/Local: i[#(Int32)#]; name=i
}
func test010() {
do {
} catch let awesomeError {
} catch let e {
#^INSIDE_CATCH5^#
#^INSIDE_CATCH5?check=STMT,EXPLICIT_ERROR_E;check=NO_ERROR_AND_A^#
} catch {}
// NO_ERROR_AND_A-NOT: awesomeError
// NO_ERROR_AND_A-NOT: Decl[LocalVar]/Local: error
Expand All @@ -186,26 +133,26 @@ func test011() {
} catch let awesomeError {
} catch let excellentError {
} catch {}
#^INSIDE_CATCH6^#
#^INSIDE_CATCH6?check=STMT;check=NO_ERROR_AND_A,NO_E^#
// NO_E-NOT: excellentError
}
func test012() {
do {
} catch {
error.#^INSIDE_CATCH_ERR_DOT1^#
error.#^INSIDE_CATCH_ERR_DOT1?check=ERROR_DOT^#
}
}
// ERROR_DOT: Keyword[self]/CurrNominal: self[#any Error#]; name=self
// ERROR_DOT-DAG: Keyword[self]/CurrNominal: self[#any Error#]; name=self
func test013() {
do {
} catch let e {
e.#^INSIDE_CATCH_ERR_DOT2^#
e.#^INSIDE_CATCH_ERR_DOT2?check=ERROR_DOT^#
}
}
func test014() {
do {
} catch let e as NSError {
e.#^INSIDE_CATCH_ERR_DOT3^#
e.#^INSIDE_CATCH_ERR_DOT3?check=NSERROR_DOT^#
}
// NSERROR_DOT-DAG: Decl[InstanceVar]/CurrNominal/IsSystem: domain[#String#]; name=domain
// NSERROR_DOT-DAG: Decl[InstanceVar]/CurrNominal/IsSystem: code[#Int#]; name=code
Expand All @@ -218,7 +165,7 @@ func test014() {
func test015() {
do {
} catch Error4.E2(let i) where i == 2 {
i.#^INSIDE_CATCH_ERR_DOT4^#
i.#^INSIDE_CATCH_ERR_DOT4?check=INT_DOT^#
}
}
// Check that we can complete on the bound value; Not exhaustive..
Expand All @@ -228,9 +175,18 @@ func test015() {
//===--- Inside catch body top-level
do {
} catch {
#^TOP_LEVEL_INSIDE_CATCH1^#
#^TOP_LEVEL_INSIDE_CATCH1?check=STMT,IMPLICIT_ERROR^#
}
do {
} catch {
error.#^TOP_LEVEL_INSIDE_CATCH_ERR_DOT1^#
error.#^TOP_LEVEL_INSIDE_CATCH_ERR_DOT1?check=ERROR_DOT^#
}

func canThrowError4() throws(Error4) {}
func test016() {
do {
try canThrowError4()
} catch .E2(let i) {
i.#^INSIDE_CATCH_TYPEDERR_DOT?check=INT_DOT^#
}
}
1 change: 0 additions & 1 deletion test/Sema/redeclaration-checking.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ func stmtTest() {
// expected-note@-1 {{'x' previously declared here}}
// expected-error@-2 {{invalid redeclaration of 'x'}}
// expected-warning@-3 {{unreachable}}
// expected-error@-4{{pattern of type 'MyError' cannot match 'Never'}}
}

func fullNameTest() {
Expand Down
2 changes: 1 addition & 1 deletion test/StringProcessing/Parse/forward-slash-regex.swift
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ default:
}

do {} catch /x/ {}
// expected-error@-1 {{expression pattern of type 'Regex<Substring>' cannot match values of type 'Never'}}
// expected-error@-1 {{expression pattern of type 'Regex<Substring>' cannot match values of type 'any Error'}}
// expected-warning@-2 {{'catch' block is unreachable because no errors are thrown in 'do' block}}

switch /x/ {
Expand Down
4 changes: 1 addition & 3 deletions test/stmt/typed_throws.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,8 @@ func testTryIncompatibleTyped(cond: Bool) throws(HomeworkError) {
}
} catch let error as Never {
// expected-warning@-1{{'catch' block is unreachable because no errors are thrown in 'do' block}}
// expected-warning@-2{{'as' test is always true}}
throw .forgot
}
} // expected-error {{thrown expression type 'any Error' cannot be converted to error type 'HomeworkError'}}
}

func doSomethingWithoutThrowing() { }
Expand All @@ -145,7 +144,6 @@ func testDoCatchWithoutThrowing() {
do {
try doSomethingWithoutThrowing() // expected-warning{{no calls to throwing functions occur within 'try' expression}}
} catch HomeworkError.forgot { // expected-warning{{'catch' block is unreachable because no errors are thrown in 'do' block}}
// expected-error@-1{{pattern of type 'HomeworkError' cannot match 'Never'}}
} catch {
}
}
Expand Down