Skip to content

Commit b2593ee

Browse files
authored
[CSDiagnostics] Optional closure diagnostic improvement (#60321)
* [NFC] Add a getOptionalityDepth method to return the number of optionals a type has * [NFC] Use getOptionalityDepth to remove existing code * [AST] Add a new diagnostic note for suggesting optional chaining * [CSDiagnostics] Tweak MissingOptionalUnwrapFailure to suggest using optional chaining on closure * [Test] Add a couple of test cases for perform_optional_chain_on_closure * [CSDiagnostics] Change DeclRefExpr casts to isa checks * [AST] Update diagnostic phrasing * [Sema] Use new diagnostic identifier and update comment * [Test] Add a new test case for function type * [Test] Update cfuncs_parse.swift tests to check for new diagnostic note
1 parent df2d938 commit b2593ee

File tree

7 files changed

+88
-20
lines changed

7 files changed

+88
-20
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1166,6 +1166,9 @@ NOTE(unwrap_iuo_initializer,none,
11661166
NOTE(unwrap_with_guard,none,
11671167
"short-circuit using 'guard' to exit this function early "
11681168
"if the optional value contains 'nil'", ())
1169+
NOTE(perform_optional_chain_on_function_type, none,
1170+
"use optional chaining to call this value of function type when optional "
1171+
"is non-'nil'", ())
11691172
ERROR(optional_base_not_unwrapped,none,
11701173
"value of optional type %0 must be unwrapped to refer to member %1 of "
11711174
"wrapped base type %2", (Type, DeclNameRef, Type))

include/swift/AST/Types.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1254,6 +1254,9 @@ class alignas(1 << TypeAlignInBits) TypeBase
12541254
/// types.
12551255
Type lookThroughAllOptionalTypes(SmallVectorImpl<Type> &optionals);
12561256

1257+
/// Return the number of optionals this type is wrapped with.
1258+
unsigned int getOptionalityDepth();
1259+
12571260
/// Remove concurrency-related types and constraints from the given
12581261
/// type
12591262
///

lib/AST/Type.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,12 @@ Type TypeBase::lookThroughAllOptionalTypes(SmallVectorImpl<Type> &optionals){
881881
return type;
882882
}
883883

884+
unsigned int TypeBase::getOptionalityDepth() {
885+
SmallVector<Type> types;
886+
lookThroughAllOptionalTypes(types);
887+
return types.size();
888+
}
889+
884890
Type TypeBase::stripConcurrency(bool recurse, bool dropGlobalActor) {
885891
// Look through optionals.
886892
if (Type optionalObject = getOptionalObjectType()) {

lib/SILGen/SILGenExpr.cpp

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4168,26 +4168,16 @@ RValue RValueEmitter::visitRebindSelfInConstructorExpr(
41684168
auto *otherCtor = E->getCalledConstructor(isChaining)->getDecl();
41694169
assert(otherCtor);
41704170

4171-
auto getOptionalityDepth = [](Type ty) {
4172-
unsigned level = 0;
4173-
Type objTy = ty->getOptionalObjectType();
4174-
while (objTy) {
4175-
++level;
4176-
objTy = objTy->getOptionalObjectType();
4177-
}
4178-
4179-
return level;
4180-
};
4181-
41824171
// The optionality depth of the 'new self' value. This can be '2' if the ctor
41834172
// we are delegating/chaining to is both throwing and failable, or more if
41844173
// 'self' is optional.
4185-
auto srcOptionalityDepth = getOptionalityDepth(E->getSubExpr()->getType());
4174+
auto srcOptionalityDepth = E->getSubExpr()->getType()->getOptionalityDepth();
41864175

41874176
// The optionality depth of the result type of the enclosing initializer in
41884177
// this context.
4189-
const auto destOptionalityDepth = getOptionalityDepth(
4190-
ctorDecl->mapTypeIntoContext(ctorDecl->getResultInterfaceType()));
4178+
const auto destOptionalityDepth =
4179+
ctorDecl->mapTypeIntoContext(ctorDecl->getResultInterfaceType())
4180+
->getOptionalityDepth();
41914181

41924182
// The subexpression consumes the current 'self' binding.
41934183
assert(SGF.SelfInitDelegationState == SILGenFunction::NormalSelf

lib/Sema/CSDiagnostics.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1566,6 +1566,26 @@ bool MissingOptionalUnwrapFailure::diagnoseAsError() {
15661566
emitDiagnosticAt(unwrappedExpr->getLoc(), diag::optional_not_unwrapped,
15671567
baseType, unwrappedType);
15681568

1569+
// If this is a function type, suggest using optional chaining to
1570+
// call it.
1571+
if (unwrappedType->lookThroughAllOptionalTypes()->is<FunctionType>()) {
1572+
bool isDeclRefExpr = false;
1573+
if (isa<DeclRefExpr>(unwrappedExpr)) {
1574+
isDeclRefExpr = true;
1575+
} else if (auto fve = dyn_cast<ForceValueExpr>(unwrappedExpr)) {
1576+
isDeclRefExpr = isa<DeclRefExpr>(fve->getSubExpr());
1577+
} else if (auto boe = dyn_cast<BindOptionalExpr>(unwrappedExpr)) {
1578+
isDeclRefExpr = isa<DeclRefExpr>(boe->getSubExpr());
1579+
}
1580+
if (isDeclRefExpr) {
1581+
auto depth = baseType->getOptionalityDepth();
1582+
auto diag = emitDiagnosticAt(unwrappedExpr->getLoc(),
1583+
diag::perform_optional_chain_on_function_type);
1584+
auto fixItString = std::string(depth, '?');
1585+
diag.fixItInsertAfter(unwrappedExpr->getEndLoc(), fixItString);
1586+
}
1587+
}
1588+
15691589
// If the expression we're unwrapping is the only reference to a
15701590
// local variable whose type isn't explicit in the source, then
15711591
// offer unwrapping fixits on the initializer as well.

test/ClangImporter/cfuncs_parse.swift

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,26 +24,29 @@ func test_cfunc2(_ i: Int) {
2424
func test_cfunc3_a() {
2525
let b = cfunc3( { (a : Double, b : Double) -> Double in a + b } )
2626
_ = b(1.5, 2.5) as Double // expected-error{{value of optional type 'double_bin_op_block?' (aka 'Optional<(Double, Double) -> Double>') must be unwrapped to a value of type 'double_bin_op_block' (aka '(Double, Double) -> Double')}}
27-
// expected-note@-1{{coalesce}}
28-
// expected-note@-2{{force-unwrap}}
27+
// expected-note@-1{{use optional chaining to call this value of function type when optional is non-'nil'}}
28+
// expected-note@-2{{coalesce}}
29+
// expected-note@-3{{force-unwrap}}
2930
_ = b!(1.5, 2.5) as Double
3031
_ = b as Double// expected-error{{cannot convert value of type 'double_bin_op_block?' (aka 'Optional<(Double, Double) -> Double>') to type 'Double' in coercion}}
3132
}
3233

3334
func test_cfunc3_b() {
3435
let b = cfunc3( { a, b in a + b } )
3536
_ = b(1.5, 2.5) as Double // expected-error{{value of optional type 'double_bin_op_block?' (aka 'Optional<(Double, Double) -> Double>') must be unwrapped to a value of type 'double_bin_op_block' (aka '(Double, Double) -> Double')}}
36-
// expected-note@-1{{coalesce}}
37-
// expected-note@-2{{force-unwrap}}
37+
// expected-note@-1{{use optional chaining to call this value of function type when optional is non-'nil'}}
38+
// expected-note@-2{{coalesce}}
39+
// expected-note@-3{{force-unwrap}}
3840
_ = b!(1.5, 2.5) as Double
3941
_ = b as Double// expected-error{{cannot convert value of type 'double_bin_op_block?' (aka 'Optional<(Double, Double) -> Double>') to type 'Double' in coercion}}
4042
}
4143

4244
func test_cfunc3_c() {
4345
let b = cfunc3({ $0 + $1 })
4446
_ = b(1.5, 2.5) as Double // expected-error{{value of optional type 'double_bin_op_block?' (aka 'Optional<(Double, Double) -> Double>') must be unwrapped to a value of type 'double_bin_op_block' (aka '(Double, Double) -> Double')}}
45-
// expected-note@-1{{coalesce}}
46-
// expected-note@-2{{force-unwrap}}
47+
// expected-note@-1{{use optional chaining to call this value of function type when optional is non-'nil'}}
48+
// expected-note@-2{{coalesce}}
49+
// expected-note@-3{{force-unwrap}}
4750
_ = b!(1.5, 2.5) as Double
4851
_ = b as Double// expected-error{{cannot convert value of type 'double_bin_op_block?' (aka 'Optional<(Double, Double) -> Double>') to type 'Double' in coercion}}
4952
}

test/Constraints/optional.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,3 +512,46 @@ func rdar85166519() {
512512

513513
// https://github.com/apple/swift/issues/58539
514514
if let x = nil {} // expected-error{{'nil' requires a contextual type}}
515+
516+
// https://github.com/apple/swift/issues/56699
517+
let singleOptionalClosure: (() -> Void)? = nil
518+
let doubleOptionalClosure: (() -> Void)?? = nil
519+
singleOptionalClosure()
520+
// expected-error@-1 {{value of optional type}}
521+
// expected-note@-2 {{use optional chaining to call this value of function type when optional is non-'nil'}} {{22-22=?}}
522+
// expected-note@-3 {{coalesce}}
523+
// expected-note@-4 {{force-unwrap}}
524+
525+
doubleOptionalClosure()
526+
// expected-error@-1 {{value of optional type}}
527+
// expected-note@-2 {{use optional chaining to call this value of function type when optional is non-'nil'}} {{22-22=??}}
528+
// expected-note@-3 {{coalesce}}
529+
// expected-note@-4 {{force-unwrap}}
530+
531+
doubleOptionalClosure?()
532+
// expected-error@-1 {{value of optional type}}
533+
// expected-note@-2 {{use optional chaining to call this value of function type when optional is non-'nil'}} {{23-23=?}}
534+
// expected-note@-3 {{coalesce}}
535+
// expected-note@-4 {{force-unwrap}}
536+
537+
doubleOptionalClosure!()
538+
// expected-error@-1 {{value of optional type}}
539+
// expected-note@-2 {{use optional chaining to call this value of function type when optional is non-'nil'}} {{23-23=?}}
540+
// expected-note@-3 {{coalesce}}
541+
// expected-note@-4 {{force-unwrap}}
542+
543+
struct FunctionContainer {
544+
func test() {}
545+
}
546+
547+
func testFunctionContainerMethodCall(container: FunctionContainer?) {
548+
let fn = container?.test
549+
// expected-note@-1 {{short-circuit}}
550+
// expected-note@-2 {{coalesce}}
551+
// expected-note@-3 {{force-unwrap}}
552+
fn()
553+
// expected-error@-1 {{value of optional type}}
554+
// expected-note@-2 {{use optional chaining to call this value of function type when optional is non-'nil'}} {{5-5=?}}
555+
// expected-note@-3 {{coalesce}}
556+
// expected-note@-4 {{force-unwrap}}
557+
}

0 commit comments

Comments
 (0)