Skip to content

Commit 95f12af

Browse files
committed
[CodeCompletion] Improve context type analysis for dictionary literal
- Analyze the type of the literal in the context - If ':' is missing in the literal, treat the expression as a key expression - If the parent expression is TupleExpr, analyze the context type of the tuple first, then return the element type of the position rdar://problem/57096392
1 parent e4277da commit 95f12af

File tree

3 files changed

+73
-8
lines changed

3 files changed

+73
-8
lines changed

lib/IDE/ExprContextAnalysis.cpp

Lines changed: 55 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -602,9 +602,46 @@ class ExprContextAnalyzer {
602602
// Check context types of the array literal expression.
603603
ExprContextInfo arrayCtxtInfo(DC, Parent);
604604
for (auto arrayT : arrayCtxtInfo.getPossibleTypes()) {
605-
if (auto boundGenericT = arrayT->getAs<BoundGenericType>())
605+
if (auto boundGenericT = arrayT->getAs<BoundGenericType>()) {
606+
// let _: [Element] = [#HERE#]
607+
// In this case, 'Element' is the expected type.
606608
if (boundGenericT->getDecl() == Context.getArrayDecl())
607609
recordPossibleType(boundGenericT->getGenericArgs()[0]);
610+
611+
// let _: [Key : Value] = [#HERE#]
612+
// In this case, 'Key' is the expected type.
613+
if (boundGenericT->getDecl() == Context.getDictionaryDecl())
614+
recordPossibleType(boundGenericT->getGenericArgs()[0]);
615+
}
616+
}
617+
break;
618+
}
619+
case ExprKind::Dictionary: {
620+
// Check context types of the dictionary literal expression.
621+
ExprContextInfo dictCtxtInfo(DC, Parent);
622+
623+
for (auto dictT : dictCtxtInfo.getPossibleTypes()) {
624+
if (auto boundGenericT = dictT->getAs<BoundGenericType>()) {
625+
if (boundGenericT->getDecl() == Context.getDictionaryDecl()) {
626+
if (ParsedExpr->isImplicit() && isa<TupleExpr>(ParsedExpr)) {
627+
// let _: [Key : Value] = [#HERE#:]
628+
// let _: [Key : Value] = [#HERE#:val]
629+
// let _: [Key : Value] = [key:#HERE#]
630+
// In this case, this is called by 'ExprKind::Tuple' case. Return
631+
// '(Key,Value)' here, 'ExprKind::Tuple' branch can decide which
632+
// type in the tuple type is the exprected type.
633+
SmallVector<TupleTypeElt, 2> elts;
634+
for (auto genericArg : boundGenericT->getGenericArgs())
635+
elts.emplace_back(genericArg);
636+
recordPossibleType(TupleType::get(elts, DC->getASTContext()));
637+
} else {
638+
// let _: [Key : Value] = [key: val, #HERE#]
639+
// In this case, assume 'Key' is the expected type.
640+
if (boundGenericT->getDecl() == Context.getDictionaryDecl())
641+
recordPossibleType(boundGenericT->getGenericArgs()[0]);
642+
}
643+
}
644+
}
608645
}
609646
break;
610647
}
@@ -629,13 +666,25 @@ class ExprContextAnalyzer {
629666
break;
630667
}
631668
case ExprKind::Tuple: {
632-
if (!Parent->getType() || !Parent->getType()->is<TupleType>())
633-
return;
669+
TupleType *tupleT = nullptr;
670+
if (Parent->getType() && Parent->getType()->is<TupleType>()) {
671+
tupleT = Parent->getType()->castTo<TupleType>();
672+
} else {
673+
ExprContextInfo tupleCtxtInfo(DC, Parent);
674+
for (auto possibleT : tupleCtxtInfo.getPossibleTypes()) {
675+
if (auto possibleTupleT = possibleT->getAs<TupleType>()) {
676+
tupleT = possibleTupleT;
677+
break;
678+
}
679+
}
680+
if (!tupleT)
681+
return;
682+
}
683+
634684
unsigned Position = 0;
635685
bool HasName;
636686
if (getPositionInArgs(*DC, Parent, ParsedExpr, Position, HasName)) {
637-
recordPossibleType(
638-
Parent->getType()->castTo<TupleType>()->getElementType(Position));
687+
recordPossibleType(tupleT->getElementType(Position));
639688
}
640689
break;
641690
}
@@ -811,6 +860,7 @@ class ExprContextAnalyzer {
811860
case ExprKind::PrefixUnary:
812861
case ExprKind::Assign:
813862
case ExprKind::Array:
863+
case ExprKind::Dictionary:
814864
return true;
815865
case ExprKind::UnresolvedMember:
816866
return true;

lib/Parse/ParseExpr.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3513,6 +3513,11 @@ Parser::parseExprCollectionElement(Optional<bool> &isDictionary) {
35133513

35143514
// Parse the ':'.
35153515
if (!consumeIf(tok::colon)) {
3516+
if (Element.hasCodeCompletion()) {
3517+
// Return the completion expression itself so we can analyze the type
3518+
// later.
3519+
return Element;
3520+
}
35163521
diagnose(Tok, diag::expected_colon_in_dictionary_literal);
35173522
return ParserStatus(Element) | makeParserError();
35183523
}

test/IDE/complete_unresolved_members.swift

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,13 @@
4545
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_34 | %FileCheck %s -check-prefix=UNRESOLVED_3
4646
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_35 | %FileCheck %s -check-prefix=UNRESOLVED_1
4747
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_36 | %FileCheck %s -check-prefix=UNRESOLVED_3
48-
// RUN-FIXME: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_37 | %FileCheck %s -check-prefix=UNRESOLVED_3
49-
// RUN-FIXME: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_38 | %FileCheck %s -check-prefix=UNRESOLVED_3
48+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_37 | %FileCheck %s -check-prefix=UNRESOLVED_3
49+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_38 | %FileCheck %s -check-prefix=UNRESOLVED_3
50+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_39 | %FileCheck %s -check-prefix=UNRESOLVED_3
51+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_40 | %FileCheck %s -check-prefix=UNRESOLVED_3
52+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_41 | %FileCheck %s -check-prefix=UNRESOLVED_3
53+
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_42 | %FileCheck %s -check-prefix=UNRESOLVED_3
54+
// RUN-FIXME: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=UNRESOLVED_43 | %FileCheck %s -check-prefix=UNRESOLVED_3
5055

5156
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=ENUM_AVAIL_1 | %FileCheck %s -check-prefix=ENUM_AVAIL_1
5257
// RUN: %target-swift-ide-test -code-completion -source-filename %s -code-completion-token=OPTIONS_AVAIL_1 | %FileCheck %s -check-prefix=OPTIONS_AVAIL_1
@@ -119,7 +124,7 @@ enum SomeEnum2 {
119124
case West
120125
}
121126

122-
enum SomeEnum3 {
127+
enum SomeEnum3: Hashable {
123128
case Payload(SomeEnum1)
124129
}
125130

@@ -388,6 +393,11 @@ let _: [SomeEnum1:SomeOptions1] = [.South:.Option1, .South:.#^UNRESOLVED_35^#]
388393
let _: [SomeEnum1:SomeOptions1] = [.South:.Option1, .#^UNRESOLVED_36^#:.Option1]
389394
let _: [SomeEnum1:SomeOptions1] = [.South:.Option1, .#^UNRESOLVED_37^#]
390395
let _: [SomeEnum1:SomeOptions1] = [.South:.Option1, .#^UNRESOLVED_38^#:]
396+
let _: [SomeEnum1:SomeOptions1] = [.#^UNRESOLVED_39^#]
397+
let _: [SomeEnum1:SomeOptions1] = [.#^UNRESOLVED_40^#:]
398+
let _: [SomeEnum1:SomeEnum3] = [.South:.Payload(.South), .South: .Payload(.#^UNRESOLVED_41^#)]
399+
let _: [SomeEnum3:SomeEnum1] = [.Payload(.South):.South, .Payload(.#^UNRESOLVED_42^#):.South]
400+
let _: [SomeEnum3:SomeEnum1] = [.Payload(.South):.South, .Payload(.#^UNRESOLVED_43^#)]
391401

392402
func testAvail1(_ x: EnumAvail1) {
393403
testAvail1(.#^ENUM_AVAIL_1^#)

0 commit comments

Comments
 (0)