Skip to content

Commit

Permalink
Merge pull request #60323 from simanerush/collection-literal-bug-fix
Browse files Browse the repository at this point in the history
Sema: Emit diagnostics when walking into collection literals with defaulted types
  • Loading branch information
AnthonyLatsis committed Aug 16, 2022
2 parents 72caba2 + f608802 commit 5bfef4f
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 29 deletions.
73 changes: 46 additions & 27 deletions lib/Sema/MiscDiagnostics.cpp
Expand Up @@ -109,13 +109,15 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC,
SmallPtrSet<DeclRefExpr*, 4> AlreadyDiagnosedBitCasts;

bool IsExprStmt;
bool HasReachedSemanticsProvidingExpr;

public:
ASTContext &Ctx;
const DeclContext *DC;

public:
DiagnoseWalker(const DeclContext *DC, bool isExprStmt)
: IsExprStmt(isExprStmt), Ctx(DC->getASTContext()), DC(DC) {}
: IsExprStmt(isExprStmt), HasReachedSemanticsProvidingExpr(false),
Ctx(DC->getASTContext()), DC(DC) {}

std::pair<bool, Pattern*> walkToPatternPre(Pattern *P) override {
return { false, P };
Expand All @@ -128,6 +130,17 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC,
bool shouldWalkIntoTapExpression() override { return false; }

std::pair<bool, Expr *> walkToExprPre(Expr *E) override {

if (auto collection = dyn_cast<CollectionExpr>(E)) {
if (collection->isTypeDefaulted()) {
// Diagnose type defaulted collection literals in subexpressions as
// warnings to preserve source compatibility.
diagnoseTypeDefaultedCollectionExpr(
collection, Ctx,
/*downgradeToWarning=*/HasReachedSemanticsProvidingExpr);
}
}

// See through implicit conversions of the expression. We want to be able
// to associate the parent of this expression with the ultimate callee.
auto Base = E;
Expand Down Expand Up @@ -328,6 +341,11 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC,
checkMoveExpr(moveExpr);
}

if (!HasReachedSemanticsProvidingExpr &&
E == E->getSemanticsProvidingExpr()) {
HasReachedSemanticsProvidingExpr = true;
}

return { true, E };
}

Expand Down Expand Up @@ -444,23 +462,34 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC,
return false;
}

/// We have a collection literal with a defaulted type, e.g. of [Any]. Emit
/// an error if it was inferred to this type in an invalid context, which is
/// one in which the parent expression is not itself a collection literal.
void checkTypeDefaultedCollectionExpr(CollectionExpr *c) {
// If the parent is a non-expression, or is not itself a literal, then
// produce an error with a fixit to add the type as an explicit
// annotation.
if (c->getNumElements() == 0)
Ctx.Diags.diagnose(c->getLoc(), diag::collection_literal_empty)
.highlight(c->getSourceRange());
else {
/// Diagnose a collection literal with a defaulted type such as \c [Any].
static void diagnoseTypeDefaultedCollectionExpr(CollectionExpr *c,
ASTContext &ctx,
bool downgradeToWarning) {
// Produce a diagnostic with a fixit to add the defaulted type as an
// explicit annotation.
auto &diags = ctx.Diags;

if (c->getNumElements() == 0) {
InFlightDiagnostic inFlight =
diags.diagnose(c->getLoc(), diag::collection_literal_empty);
inFlight.highlight(c->getSourceRange());

if (downgradeToWarning) {
inFlight.limitBehavior(DiagnosticBehavior::Warning);
}
} else {
assert(c->getType()->hasTypeRepr() &&
"a defaulted type should always be printable");
Ctx.Diags.diagnose(c->getLoc(), diag::collection_literal_heterogeneous,
c->getType())
.highlight(c->getSourceRange())
.fixItInsertAfter(c->getEndLoc(), " as " + c->getType()->getString());
InFlightDiagnostic inFlight = diags.diagnose(
c->getLoc(), diag::collection_literal_heterogeneous, c->getType());
inFlight.highlight(c->getSourceRange());
inFlight.fixItInsertAfter(c->getEndLoc(),
" as " + c->getType()->getString());

if (downgradeToWarning) {
inFlight.limitBehavior(DiagnosticBehavior::Warning);
}
}
}

Expand Down Expand Up @@ -1334,16 +1363,6 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC,

DiagnoseWalker Walker(DC, isExprStmt);
const_cast<Expr *>(E)->walk(Walker);

// Diagnose uses of collection literals with defaulted types at the top
// level.
if (auto collection
= dyn_cast<CollectionExpr>(E->getSemanticsProvidingExpr())) {
if (collection->isTypeDefaulted()) {
Walker.checkTypeDefaultedCollectionExpr(
const_cast<CollectionExpr *>(collection));
}
}
}


Expand Down
39 changes: 38 additions & 1 deletion test/Constraints/array_literal.swift
@@ -1,4 +1,4 @@
// RUN: %target-typecheck-verify-swift
// RUN: %target-typecheck-verify-swift -disable-availability-checking

struct IntList : ExpressibleByArrayLiteral {
typealias Element = Int
Expand Down Expand Up @@ -142,19 +142,56 @@ func defaultToAny(i: Int, s: String) {
// expected-error@-1{{empty collection literal requires an explicit type}}
let _: Int = a5 // expected-error{{value of type '[Any]'}}

let _: [Any] = []
let _: [Any] = [1, "a", 3.5]
let _: [Any] = [1, "a", [3.5, 3.7, 3.9]]
let _: [Any] = [1, "a", [3.5, "b", 3]]
// expected-warning@-1{{heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional}}
let _: [Any] = [1, [2, [3]]]
// expected-warning@-1{{heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional}}

func f1() -> [Any] {
[]
}

let _: [Any?] = [1, "a", nil, 3.5]
let _: [Any?] = [1, "a", nil, [3.5, 3.7, 3.9]]
let _: [Any?] = [1, "a", nil, [3.5, "b", nil]]
// expected-warning@-1{{heterogeneous collection literal could only be inferred to '[Any?]'; add explicit type annotation if this is intentional}}
let _: [Any?] = [1, [2, [3]]]
// expected-warning@-1{{heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional}}
let _: [Any?] = [1, nil, [2, nil, [3]]]
// expected-warning@-1{{heterogeneous collection literal could only be inferred to '[Any?]'; add explicit type annotation if this is intentional}}

let a6 = [B(), C()]
let _: Int = a6 // expected-error{{value of type '[A]'}}

let a7: some Collection = [1, "Swift"]
// expected-warning@-1{{heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional}} {{41-41= as [Any]}}
let _: (any Sequence)? = [1, "Swift"]
// expected-warning@-1{{heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional}}
let _: any Sequence = [1, nil, "Swift"]
// expected-warning@-1{{heterogeneous collection literal could only be inferred to '[Any?]'; add explicit type annotation if this is intentional}}
let _ = [1, true, ([], 1)]
// expected-error@-1 {{heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional}}
// expected-warning@-2 {{empty collection literal requires an explicit type}}
let _ = true ? [] : []
// expected-warning@-1{{empty collection literal requires an explicit type}}
let _ = (true, ([1, "Swift"]))
//expected-warning@-1{{heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional}}
let _ = ([1, true])
//expected-error@-1{{heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional}}

func f2<T>(_: [T]) {}

func f3<T>() -> [T]? {}

f2([])
// expected-warning@-1{{empty collection literal requires an explicit type}}
f2([1, nil, ""])
// expected-warning@-1{{heterogeneous collection literal could only be inferred to '[Any?]'; add explicit type annotation if this is intentional}}
_ = f3() ?? []
// expected-warning@-1{{empty collection literal requires an explicit type}}
}

func noInferAny(iob: inout B, ioc: inout C) {
Expand Down
1 change: 1 addition & 0 deletions test/Constraints/casts.swift
Expand Up @@ -337,6 +337,7 @@ func test_compatibility_coercions(_ arr: [Int], _ optArr: [Int]?, _ dict: [Strin

// The array can also be inferred to be [Any].
_ = ([] ?? []) as Array // expected-warning {{left side of nil coalescing operator '??' has non-optional type '[Any]', so the right side is never used}}
// expected-warning@-1{{empty collection literal requires an explicit type}}

// rdar://88334481 – Don't apply the compatibility logic for collection literals.
typealias Magic<T> = T
Expand Down
1 change: 1 addition & 0 deletions test/Constraints/casts_swift6.swift
Expand Up @@ -57,6 +57,7 @@ func test_compatibility_coercions(_ arr: [Int], _ optArr: [Int]?, _ dict: [Strin

// The array can also be inferred to be [Any].
_ = ([] ?? []) as Array // expected-warning {{left side of nil coalescing operator '??' has non-optional type '[Any]', so the right side is never used}}
// expected-warning@-1 {{empty collection literal requires an explicit type}}

// Cases from rdar://88334481
typealias Magic<T> = T
Expand Down
1 change: 1 addition & 0 deletions test/Constraints/construction.swift
Expand Up @@ -172,6 +172,7 @@ SR_5245(s: SR_5245.S(f: [.e1, .e2]))

// rdar://problem/34670592 - Compiler crash on heterogeneous collection literal
_ = Array([1, "hello"]) // Ok
// expected-warning@-1 {{heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional}}

func init_via_non_const_metatype(_ s1: S1.Type) {
_ = s1(i: 42) // expected-error {{initializing from a metatype value must reference 'init' explicitly}} {{9-9=.init}}
Expand Down
3 changes: 2 additions & 1 deletion test/Constraints/dictionary_literal.swift
Expand Up @@ -139,6 +139,7 @@ func testDefaultExistentials() {
"b": ["a", 2, 3.14159],
"c": ["a": 2, "b": 3.5]]
// expected-error@-3{{heterogeneous collection literal could only be inferred to '[String : Any]'; add explicit type annotation if this is intentional}}
// expected-warning@-3{{heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional}}

let d3 = ["b" : B(), "c" : C()]
let _: Int = d3 // expected-error{{value of type '[String : A]'}}
Expand Down Expand Up @@ -193,4 +194,4 @@ f59215(["", ""]) //expected-error{{dictionary of type '[String : String]' cannot
f59215(["", "", "", ""]) //expected-error{{dictionary of type '[String : String]' cannot be used with array literal}}
// expected-note@-1{{did you mean to use a dictionary literal instead?}} {{11-12=:}} {{19-20=:}}
f59215(["", "", "", ""]) //expected-error{{dictionary of type '[String : String]' cannot be used with array literal}}
// expected-note@-1{{did you mean to use a dictionary literal instead?}} {{11-12=:}} {{19-20=:}}
// expected-note@-1{{did you mean to use a dictionary literal instead?}} {{11-12=:}} {{19-20=:}}
1 change: 1 addition & 0 deletions test/Constraints/subscript.swift
Expand Up @@ -104,6 +104,7 @@ extension Int {
let _ = 1["1"] // expected-error {{ambiguous use of 'subscript(_:)'}}

let squares = [ 1, 2, 3 ].reduce([:]) { (dict, n) in
// expected-warning@-1 {{empty collection literal requires an explicit type}}
var dict = dict
dict[n] = n * n
return dict
Expand Down
10 changes: 10 additions & 0 deletions test/IDE/print_usrs_opaque_types.swift
Expand Up @@ -9,29 +9,34 @@
func testUnifyingGenericParams<T, U>(x: T) -> some Collection where T == U {
// expected-warning@-1 {{same-type requirement makes generic parameters 'U' and 'T' equivalent}}
return []
// expected-warning@-1 {{empty collection literal requires an explicit type}}
}

// CHECK: [[@LINE+1]]:{{[0-9]+}} s:14swift_ide_test0C22UnifyingGenericParams21xQrx_tSlRz7ElementQzRs_r0_lF
func testUnifyingGenericParams2<T, U>(x: T) -> some Collection where T: Collection, U == T.Element {
return []
// expected-warning@-1 {{empty collection literal requires an explicit type}}
}

// CHECK: [[@LINE+1]]:{{[0-9]+}} s:14swift_ide_test0C24ConcretizingGenericParam1xQrSi_tSiRszlF
func testConcretizingGenericParam<T>(x: T) -> some Collection where T == Int {
// expected-warning@-1 {{same-type requirement makes generic parameter 'T' non-generic}}
return []
// expected-warning@-1 {{empty collection literal requires an explicit type}}
}

struct GenericContext<T> {
// CHECK: [[@LINE+1]]:{{[0-9]+}} s:14swift_ide_test14GenericContextV0c8UnifyingD6Params1xQrx_tqd__RszlF
func testUnifyingGenericParams<U>(x: T) -> some Collection where T == U {
// expected-warning@-1 {{same-type requirement makes generic parameters 'U' and 'T' equivalent}}
return []
// expected-warning@-1 {{empty collection literal requires an explicit type}}
}

// CHECK: [[@LINE+1]]:{{[0-9]+}} s:14swift_ide_test14GenericContextV0c8UnifyingD7Params21xQrx_tSlRz7ElementQzRsd__lF
func testUnifyingGenericParams2<U>(x: T) -> some Collection where T: Collection, U == T.Element {
return []
// expected-warning@-1 {{empty collection literal requires an explicit type}}
}

// CHECK: [[@LINE+1]]:{{[0-9]+}} s:14swift_ide_test14GenericContextVyQrxcqd__Rszluip
Expand All @@ -40,6 +45,7 @@ struct GenericContext<T> {
// CHECK: [[@LINE+1]]:{{[0-9]+}} s:14swift_ide_test14GenericContextVyQrxcqd__Rszluig
get {
return []
// expected-warning@-1 {{empty collection literal requires an explicit type}}
}
}
}
Expand All @@ -48,6 +54,7 @@ extension GenericContext where T == Int {
// CHECK: [[@LINE+1]]:{{[0-9]+}} s:14swift_ide_test14GenericContextVAASiRszlE0c12ConcretizingD5Param1xQrSi_tF
func testConcretizingGenericParam(x: T) -> some Collection {
return []
// expected-warning@-1 {{empty collection literal requires an explicit type}}
}
}

Expand All @@ -58,19 +65,22 @@ extension TooGenericTooContext where T == U {
// CHECK: [[@LINE+1]]:{{[0-9]+}} s:14swift_ide_test010TooGenericD7ContextVAAq_RszrlE0c8UnifyingE6Params1xQrx_tF
func testUnifyingGenericParams(x: T) -> some Collection {
return []
// expected-warning@-1 {{empty collection literal requires an explicit type}}
}
}

extension TooGenericTooContext where T: Collection, U == T.Element {
// CHECK: [[@LINE+1]]:{{[0-9]+}} s:14swift_ide_test010TooGenericD7ContextVAASlRz7ElementQzRs_rlE0c8UnifyingE7Params21xQrx_tF
func testUnifyingGenericParams2(x: T) -> some Collection {
return []
// expected-warning@-1 {{empty collection literal requires an explicit type}}
}
}
extension TooGenericTooContext where T == Int {
// CHECK: [[@LINE+1]]:{{[0-9]+}} s:14swift_ide_test010TooGenericD7ContextVAASiRszrlE0c12ConcretizingE5Param1xQrSi_tF
func testConcretizingGenericParam(x: T) -> some Collection {
return []
// expected-warning@-1 {{empty collection literal requires an explicit type}}
}
}

4 changes: 4 additions & 0 deletions test/expr/cast/objc_coerce_array.swift
Expand Up @@ -7,9 +7,13 @@ var x = 1
_ = [x] as [NSNumber]

_ = ["x":["y":"z","a":1]] as [String : [String : AnyObject]]
// expected-warning@-1{{heterogeneous collection literal could only be inferred to '[String : AnyObject]'; add explicit type annotation if this is intentiona}}
_ = ["x":["z",1]] as [String : [AnyObject]]
// expected-warning@-1{{heterogeneous collection literal could only be inferred to '[AnyObject]'; add explicit type annotation if this is intentional}}
_ = [["y":"z","a":1]] as [[String : AnyObject]]
// expected-warning@-1{{heterogeneous collection literal could only be inferred to '[String : AnyObject]'; add explicit type annotation if this is intentional}}
_ = [["z",1]] as [[AnyObject]]
// expected-warning@-1{{heterogeneous collection literal could only be inferred to '[AnyObject]'; add explicit type annotation if this is intentional}}

var y: Any = 1

Expand Down

0 comments on commit 5bfef4f

Please sign in to comment.