Skip to content

Commit f30ba30

Browse files
theblixguyslavapestov
authored andcommitted
[Sema] Add warning for ambiguous value assignment when using Optional (#21621)
* [sema] emit a diag if the enum case matches Optional<T>.none * [test] update tests * [sema] fix indent * [test] fix indent * [test] add more test cases * [test] add even more test cases * [sema] move the check to CSApply * [diag] update diagnostic message * [test] update tests * [test] fix conflicts * [diag] reflow lines * [sema] reindent using spaces * [test] adds new line * [diag] update diagnostic message * [sema] add support for structs as well * [test] add more test cases * [sema] check for enum assoc values * [test] add more test cases * [diag] add fixit notes * [sema] emit fix its * [diag] rename diag names * [sema] fit within 80 char line limit * [sema] use baseUnwrappedType's name directly * [test] adds nested generic enum tests * [test] fix indent * [test] adds fixit check * [test] re-indent some enums * [sema] [csapply] extract code into a separate function * [sema] [csapply] remove redundant vardecl check * [sema] [csapply] reindent * [sema] [csapply] removes extra line * [sema] [csapply] use cantype & check for extension on Optional * [diag] update diagnostic message * [sema] [csapply] fix ident * [test] update tests * [sema] [csapply] fix typo and remove redundant isOptional check * [sema] [csapply] update var name & comments * [sema] [csapply] bring back isOptional check * [test] add expected-note for fix-its * [sema] [csapply] fix a crash * [sema] [csapply] move isOptional check outside * [test] fix indent * [test] fix typo * [sema] [csapply] use baseTyUnwrapped for fixit * [test] fix columns for fixits * [test] update column numbers * [sema] [csapply] move code out of for loop
1 parent 810a7bc commit f30ba30

File tree

3 files changed

+195
-0
lines changed

3 files changed

+195
-0
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1391,6 +1391,14 @@ ERROR(pattern_binds_no_variables,none,
13911391
"variables",
13921392
(unsigned))
13931393

1394+
WARNING(optional_ambiguous_case_ref,none,
1395+
"assuming you mean '%0.%2'; did you mean '%1.%2' instead?",
1396+
(StringRef, StringRef, StringRef))
1397+
NOTE(optional_fixit_ambiguous_case_ref,none,
1398+
"explicitly specify 'Optional' to silence this warning", ())
1399+
NOTE(type_fixit_optional_ambiguous_case_ref,none,
1400+
"use '%0.%1' instead", (StringRef, StringRef))
1401+
13941402
ERROR(nscoding_unstable_mangled_name,none,
13951403
"%select{private|fileprivate|nested|local}0 class %1 has an "
13961404
"unstable name when archiving via 'NSCoding'",

lib/Sema/CSApply.cpp

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2514,10 +2514,94 @@ namespace {
25142514
/*implicit=*/expr->isImplicit(), Type(), getType);
25152515
result = finishApply(apply, Type(), cs.getConstraintLocator(expr));
25162516
}
2517+
2518+
// Check for ambiguous member if the base is an Optional
2519+
if (baseTy->getOptionalObjectType()) {
2520+
diagnoseAmbiguousNominalMember(baseTy, result);
2521+
}
25172522

25182523
return coerceToType(result, resultTy, cs.getConstraintLocator(expr));
25192524
}
25202525

2526+
/// Diagnose if the base type is optional, we're referring to a nominal
2527+
/// type member via the dot syntax and the member name matches
2528+
/// Optional<T>.{member name}
2529+
void diagnoseAmbiguousNominalMember(Type baseTy, Expr *result) {
2530+
if (auto baseTyUnwrapped = baseTy->lookThroughAllOptionalTypes()) {
2531+
// Return if the base type doesn't have a nominal type decl
2532+
if (!baseTyUnwrapped->getNominalOrBoundGenericNominal()) {
2533+
return;
2534+
}
2535+
2536+
// Otherwise, continue digging
2537+
if (auto DSCE = dyn_cast<DotSyntaxCallExpr>(result)) {
2538+
auto calledValue = DSCE->getCalledValue();
2539+
auto isOptional = false;
2540+
Identifier memberName;
2541+
2542+
// Try cast the assigned value to an enum case
2543+
//
2544+
// This will always succeed if the base is Optional<T> & the
2545+
// assigned case comes from Optional<T>
2546+
if (auto EED = dyn_cast<EnumElementDecl>(calledValue)) {
2547+
isOptional = EED->getParentEnum()->isOptionalDecl();
2548+
memberName = EED->getBaseName().getIdentifier();
2549+
}
2550+
2551+
// Return if the enum case doesn't come from Optional<T>
2552+
if (!isOptional) {
2553+
return;
2554+
}
2555+
2556+
// Look up the enum case in the unwrapped type to check if it exists
2557+
// as a member
2558+
auto baseTyNominalDecl = baseTyUnwrapped
2559+
->getNominalOrBoundGenericNominal();
2560+
auto &tc = cs.getTypeChecker();
2561+
auto results = tc.lookupMember(baseTyNominalDecl->getModuleContext(),
2562+
baseTyUnwrapped,
2563+
memberName,
2564+
defaultMemberLookupOptions);
2565+
2566+
// Lookup didn't find anything, so return
2567+
if (results.empty()) {
2568+
return;
2569+
}
2570+
2571+
if (auto member = results.front().getValueDecl()) {
2572+
// Lookup returned a member that is an instance member,
2573+
// so return
2574+
if (member->isInstanceMember()) {
2575+
return;
2576+
}
2577+
2578+
// Return if the member is an enum case w/ assoc values, as we only
2579+
// care (for now) about cases with no assoc values (like none)
2580+
if (auto EED = dyn_cast<EnumElementDecl>(member)) {
2581+
if (EED->hasAssociatedValues()) {
2582+
return;
2583+
}
2584+
}
2585+
2586+
// Emit a diagnostic with some fixits
2587+
auto baseTyName = baseTy->getCanonicalType().getString();
2588+
auto baseTyUnwrappedName = baseTyUnwrapped->getString();
2589+
auto loc = DSCE->getLoc();
2590+
auto startLoc = DSCE->getStartLoc();
2591+
2592+
tc.diagnose(loc, swift::diag::optional_ambiguous_case_ref,
2593+
baseTyName, baseTyUnwrappedName, memberName.str());
2594+
2595+
tc.diagnose(loc, swift::diag::optional_fixit_ambiguous_case_ref)
2596+
.fixItInsert(startLoc, "Optional");
2597+
tc.diagnose(loc, swift::diag::type_fixit_optional_ambiguous_case_ref,
2598+
baseTyUnwrappedName, memberName.str())
2599+
.fixItInsert(startLoc, baseTyUnwrappedName);
2600+
}
2601+
}
2602+
}
2603+
}
2604+
25212605
private:
25222606
/// A list of "suspicious" optional injections that come from
25232607
/// forced downcasts.

test/decl/enum/enumtest.swift

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,3 +333,106 @@ enum Lens<T> {
333333
enum HasVariadic {
334334
case variadic(x: Int...) // expected-error {{variadic enum cases are not supported}}
335335
}
336+
337+
// SR-2176
338+
enum Foo {
339+
case bar
340+
case none
341+
}
342+
343+
let _: Foo? = .none // expected-warning {{assuming you mean 'Optional<Foo>.none'; did you mean 'Foo.none' instead?}}
344+
// expected-note@-1 {{explicitly specify 'Optional' to silence this warning}} {{15-15=Optional}}
345+
// expected-note@-2 {{use 'Foo.none' instead}} {{15-15=Foo}}
346+
let _: Foo?? = .none // expected-warning {{assuming you mean 'Optional<Optional<Foo>>.none'; did you mean 'Foo.none' instead?}}
347+
// expected-note@-1 {{explicitly specify 'Optional' to silence this warning}} {{16-16=Optional}}
348+
// expected-note@-2 {{use 'Foo.none' instead}} {{16-16=Foo}}
349+
350+
let _: Foo = .none // ok
351+
let _: Foo = .bar // ok
352+
let _: Foo? = .bar // ok
353+
let _: Foo?? = .bar // ok
354+
let _: Foo = Foo.bar // ok
355+
let _: Foo = Foo.none // ok
356+
let _: Foo? = Foo.none // ok
357+
let _: Foo?? = Foo.none // ok
358+
359+
func baz(_: Foo?) {}
360+
baz(.none) // expected-warning {{assuming you mean 'Optional<Foo>.none'; did you mean 'Foo.none' instead?}}
361+
// expected-note@-1 {{explicitly specify 'Optional' to silence this warning}} {{5-5=Optional}}
362+
// expected-note@-2 {{use 'Foo.none' instead}} {{5-5=Foo}}
363+
364+
let test: Foo? = .none // expected-warning {{assuming you mean 'Optional<Foo>.none'; did you mean 'Foo.none' instead?}}
365+
// expected-note@-1 {{explicitly specify 'Optional' to silence this warning}} {{18-18=Optional}}
366+
// expected-note@-2 {{use 'Foo.none' instead}} {{18-18=Foo}}
367+
let answer = test == .none // expected-warning {{assuming you mean 'Optional<Foo>.none'; did you mean 'Foo.none' instead?}}
368+
// expected-note@-1 {{explicitly specify 'Optional' to silence this warning}} {{22-22=Optional}}
369+
// expected-note@-2 {{use 'Foo.none' instead}} {{22-22=Foo}}
370+
371+
enum Bar {
372+
case baz
373+
}
374+
375+
let _: Bar? = .none // ok
376+
let _: Bar?? = .none // ok
377+
let _: Bar? = .baz // ok
378+
let _: Bar?? = .baz // ok
379+
let _: Bar = .baz // ok
380+
381+
enum AnotherFoo {
382+
case none(Any)
383+
}
384+
385+
let _: AnotherFoo? = .none // ok
386+
let _: AnotherFoo? = .none(0) // ok
387+
388+
struct FooStruct {
389+
static let none = FooStruct()
390+
static let one = FooStruct()
391+
}
392+
393+
let _: FooStruct? = .none // expected-warning {{assuming you mean 'Optional<FooStruct>.none'; did you mean 'FooStruct.none' instead?}}
394+
// expected-note@-1 {{explicitly specify 'Optional' to silence this warning}} {{21-21=Optional}}
395+
// expected-note@-2 {{use 'FooStruct.none' instead}} {{21-21=FooStruct}}
396+
let _: FooStruct?? = .none // expected-warning {{assuming you mean 'Optional<Optional<FooStruct>>.none'; did you mean 'FooStruct.none' instead?}}
397+
// expected-note@-1 {{explicitly specify 'Optional' to silence this warning}} {{22-22=Optional}}
398+
// expected-note@-2 {{use 'FooStruct.none' instead}} {{22-22=FooStruct}}
399+
let _: FooStruct = .none // ok
400+
let _: FooStruct = .one // ok
401+
let _: FooStruct? = .one // ok
402+
let _: FooStruct?? = .one // ok
403+
404+
struct NestedBazEnum {
405+
enum Baz {
406+
case one
407+
case none
408+
}
409+
}
410+
411+
let _: NestedBazEnum.Baz? = .none // expected-warning {{assuming you mean 'Optional<NestedBazEnum.Baz>.none'; did you mean 'NestedBazEnum.Baz.none' instead?}}
412+
// expected-note@-1 {{explicitly specify 'Optional' to silence this warning}} {{29-29=Optional}}
413+
// expected-note@-2 {{use 'NestedBazEnum.Baz.none' instead}} {{29-29=NestedBazEnum.Baz}}
414+
let _: NestedBazEnum.Baz?? = .none // expected-warning {{assuming you mean 'Optional<Optional<NestedBazEnum.Baz>>.none'; did you mean 'NestedBazEnum.Baz.none' instead?}}
415+
// expected-note@-1 {{explicitly specify 'Optional' to silence this warning}} {{30-30=Optional}}
416+
// expected-note@-2 {{use 'NestedBazEnum.Baz.none' instead}} {{30-30=NestedBazEnum.Baz}}
417+
let _: NestedBazEnum.Baz = .none // ok
418+
let _: NestedBazEnum.Baz = .one // ok
419+
let _: NestedBazEnum.Baz? = .one // ok
420+
let _: NestedBazEnum.Baz?? = .one // ok
421+
422+
struct NestedBazEnumGeneric {
423+
enum Baz<T> {
424+
case one
425+
case none
426+
}
427+
}
428+
429+
let _: NestedBazEnumGeneric.Baz<Int>? = .none // expected-warning {{assuming you mean 'Optional<NestedBazEnumGeneric.Baz<Int>>.none'; did you mean 'NestedBazEnumGeneric.Baz<Int>.none' instead?}}
430+
// expected-note@-1 {{explicitly specify 'Optional' to silence this warning}} {{41-41=Optional}}
431+
// expected-note@-2 {{use 'NestedBazEnumGeneric.Baz<Int>.none' instead}} {{41-41=NestedBazEnumGeneric.Baz<Int>}}
432+
let _: NestedBazEnumGeneric.Baz<Int>?? = .none // expected-warning {{assuming you mean 'Optional<Optional<NestedBazEnumGeneric.Baz<Int>>>.none'; did you mean 'NestedBazEnumGeneric.Baz<Int>.none' instead?}}
433+
// expected-note@-1 {{explicitly specify 'Optional' to silence this warning}} {{42-42=Optional}}
434+
// expected-note@-2 {{use 'NestedBazEnumGeneric.Baz<Int>.none' instead}} {{42-42=NestedBazEnumGeneric.Baz<Int>}}
435+
let _: NestedBazEnumGeneric.Baz<Int> = .none // ok
436+
let _: NestedBazEnumGeneric.Baz<Int> = .one // ok
437+
let _: NestedBazEnumGeneric.Baz<Int>? = .one // ok
438+
let _: NestedBazEnumGeneric.Baz<Int>?? = .one // ok

0 commit comments

Comments
 (0)