Skip to content

Commit 78b7bb4

Browse files
committed
[Type checker]: Implement 'join' for optional types and a special case for 'nil'.
The join operation for optional types is straightforward to define/implement: join(T?, U) ::= join(T, U)? join(T, U?) ::= join(T, U)? join(T?, U?) ::= join(T, U)? As a special case in the constraint solver, handle the join of a 'nil' literal with a non-ExpressibleByNilLiteral-conforming concrete type 'T' to produce 'T?'. This allows us, e.g., infer [String?] for the expressions ["hello", nil] and true ? "hello" nil for example. Fixes rdar://problem/16326914.
1 parent 1b99f7f commit 78b7bb4

File tree

4 files changed

+88
-6
lines changed

4 files changed

+88
-6
lines changed

lib/AST/TypeJoinMeet.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,20 @@ Type Type::join(Type type1, Type type2) {
6464
return nullptr;
6565
}
6666

67+
// If one or both of the types are optional types, look at the underlying
68+
// object type.
69+
OptionalTypeKind otk1, otk2;
70+
Type objectType1 = type1->getAnyOptionalObjectType(otk1);
71+
Type objectType2 = type2->getAnyOptionalObjectType(otk2);
72+
if (otk1 == OTK_Optional || otk2 == OTK_Optional) {
73+
// Compute the join of the unwrapped type. If there is none, we're done.
74+
Type unwrappedJoin = join(objectType1 ? objectType1 : type1,
75+
objectType2 ? objectType2 : type2);
76+
if (!unwrappedJoin) return nullptr;
77+
78+
return OptionalType::get(unwrappedJoin);
79+
}
80+
6781
// The join can only be an existential.
6882
return nullptr;
6983
}

lib/Sema/CSSolver.cpp

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ STATISTIC(LargestSolutionAttemptNumber, "# of the largest solution attempt");
4747
///
4848
/// \returns the type to bind to, if the binding is okay.
4949
static Optional<Type> checkTypeOfBinding(ConstraintSystem &cs,
50-
TypeVariableType *typeVar, Type type) {
50+
TypeVariableType *typeVar, Type type,
51+
bool *isNilLiteral = nullptr) {
5152
if (!type)
5253
return None;
5354

@@ -63,8 +64,15 @@ static Optional<Type> checkTypeOfBinding(ConstraintSystem &cs,
6364
// If the type is a type variable itself, don't permit the binding.
6465
// FIXME: This is a hack. We need to be smarter about whether there's enough
6566
// structure in the type to produce an interesting binding, or not.
66-
if (type->getRValueType()->is<TypeVariableType>())
67+
if (auto bindingTypeVar = type->getRValueType()->getAs<TypeVariableType>()) {
68+
if (isNilLiteral &&
69+
bindingTypeVar->getImpl().literalConformanceProto &&
70+
bindingTypeVar->getImpl().literalConformanceProto->isSpecificProtocol(
71+
KnownProtocolKind::ExpressibleByNilLiteral))
72+
*isNilLiteral = true;
73+
6774
return None;
75+
}
6876

6977
// Okay, allow the binding (with the simplified type).
7078
return type;
@@ -709,14 +717,16 @@ static PotentialBindings getPotentialBindings(ConstraintSystem &cs,
709717

710718
// Local function to add a potential binding to the list of bindings,
711719
// coalescing supertype bounds when we are able to compute the meet.
712-
auto addPotentialBinding = [&](PotentialBinding binding) {
720+
auto addPotentialBinding = [&](PotentialBinding binding,
721+
bool allowJoinMeet = true) {
713722
// If this is a non-defaulted supertype binding, check whether we can
714723
// combine it with another supertype binding by computing the 'join' of the
715724
// types.
716725
if (binding.Kind == AllowedBindingKind::Supertypes &&
717726
!binding.BindingType->hasTypeVariable() &&
718727
!binding.DefaultedProtocol &&
719-
!binding.IsDefaultableBinding) {
728+
!binding.IsDefaultableBinding &&
729+
allowJoinMeet) {
720730
if (lastSupertypeIndex) {
721731
// Can we compute a join?
722732
auto &lastBinding = result.Bindings[*lastSupertypeIndex];
@@ -739,6 +749,7 @@ static PotentialBindings getPotentialBindings(ConstraintSystem &cs,
739749
llvm::SmallPtrSet<CanType, 4> exactTypes;
740750
llvm::SmallPtrSet<ProtocolDecl *, 4> literalProtocols;
741751
SmallVector<Constraint *, 2> defaultableConstraints;
752+
bool addOptionalSupertypeBindings = false;
742753
auto &tc = cs.getTypeChecker();
743754
for (auto constraint : constraints) {
744755
// Only visit each constraint once.
@@ -917,11 +928,18 @@ static PotentialBindings getPotentialBindings(ConstraintSystem &cs,
917928

918929
// Check whether we can perform this binding.
919930
// FIXME: this has a super-inefficient extraneous simplifyType() in it.
920-
if (auto boundType = checkTypeOfBinding(cs, typeVar, type)) {
931+
bool isNilLiteral = false;
932+
if (auto boundType = checkTypeOfBinding(cs, typeVar, type, &isNilLiteral)) {
921933
type = *boundType;
922934
if (type->hasTypeVariable())
923935
result.InvolvesTypeVariables = true;
924936
} else {
937+
// If the bound is a 'nil' literal type, add optional supertype bindings.
938+
if (isNilLiteral && kind == AllowedBindingKind::Supertypes) {
939+
addOptionalSupertypeBindings = true;
940+
continue;
941+
}
942+
925943
result.InvolvesTypeVariables = true;
926944
continue;
927945
}
@@ -980,7 +998,7 @@ static PotentialBindings getPotentialBindings(ConstraintSystem &cs,
980998
addPotentialBinding({type, kind, None});
981999
if (alternateType &&
9821000
exactTypes.insert(alternateType->getCanonicalType()).second)
983-
addPotentialBinding({alternateType, kind, None});
1001+
addPotentialBinding({alternateType, kind, None}, /*allowJoinMeet=*/false);
9841002
}
9851003

9861004
// If we have any literal constraints, check whether there is already a
@@ -1071,6 +1089,30 @@ static PotentialBindings getPotentialBindings(ConstraintSystem &cs,
10711089
binding.Kind == AllowedBindingKind::Subtypes;
10721090
});
10731091

1092+
// If we're supposed to add optional supertype bindings, do so now.
1093+
if (addOptionalSupertypeBindings) {
1094+
for (unsigned i : indices(result.Bindings)) {
1095+
// Only interested in supertype bindings.
1096+
auto &binding = result.Bindings[i];
1097+
if (binding.Kind != AllowedBindingKind::Supertypes) continue;
1098+
1099+
// If the type doesn't conform to ExpressibleByNilLiteral,
1100+
// produce an optional of that type as a potential binding. We
1101+
// overwrite the binding in place because the non-optional type
1102+
// will fail to type-check against the nil-literal conformance.
1103+
auto nominalBindingDecl = binding.BindingType->getAnyNominal();
1104+
if (!nominalBindingDecl) continue;
1105+
SmallVector<ProtocolConformance *, 2> conformances;
1106+
if (!nominalBindingDecl->lookupConformance(
1107+
cs.DC->getParentModule(),
1108+
cs.getASTContext().getProtocol(
1109+
KnownProtocolKind::ExpressibleByNilLiteral),
1110+
conformances)) {
1111+
binding.BindingType = OptionalType::get(binding.BindingType);
1112+
}
1113+
}
1114+
}
1115+
10741116
return result;
10751117
}
10761118

test/Constraints/array_literal.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,18 @@ func defaultToAny(i: Int, s: String) {
143143
let a4 = [B(), C()]
144144
let _: Int = a4 // expected-error{{value of type '[A]'}}
145145
}
146+
147+
/// Check handling of 'nil'.
148+
func joinWithNil(s: String) {
149+
let a1 = [s, nil]
150+
let _: Int = a1 // expected-error{{value of type '[String?]'}}
151+
152+
let a2 = [nil, s]
153+
let _: Int = a2 // expected-error{{value of type '[String?]'}}
154+
155+
let a3 = ["hello", nil]
156+
let _: Int = a3 // expected-error{{value of type '[String?]'}}
157+
158+
let a4 = [nil, "hello"]
159+
let _: Int = a4 // expected-error{{value of type '[String?]'}}
160+
}

test/Constraints/optional.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,14 @@ func testVoidOptional() {
119119
let optNoop: (()?) -> ()? = { return $0 }
120120
voidOptional(optNoop)
121121
}
122+
123+
func testTernaryWithNil(b: Bool, s: String, i: Int) {
124+
let t1 = b ? s : nil
125+
let _: Double = t1 // expected-error{{value of type 'String?'}}
126+
let t2 = b ? nil : i
127+
let _: Double = t2 // expected-error{{value of type 'Int?'}}
128+
let t3 = b ? "hello" : nil
129+
let _: Double = t3 // expected-error{{value of type 'String?'}}
130+
let t4 = b ? nil : 1
131+
let _: Double = t4 // expected-error{{value of type 'Int?'}}
132+
}

0 commit comments

Comments
 (0)