Skip to content

Commit fe127a2

Browse files
authored
Merge pull request #4408 from DougGregor/optional-join-rdar-16326914
Implement 'join' for optional types and a special case for 'nil'.
2 parents a45bc71 + 78b7bb4 commit fe127a2

File tree

5 files changed

+108
-26
lines changed

5 files changed

+108
-26
lines changed

include/swift/AST/Type.h

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -184,11 +184,11 @@ class Type {
184184
/// Get the canonical type, or return null if the type is null.
185185
CanType getCanonicalTypeOrNull() const; // in Types.h
186186

187-
/// Computes the meet between two types.
187+
/// Computes the join between two types.
188188
///
189-
/// The meet of two types is the most specific type that is a supertype of
190-
/// both \c type1 and \c type2. For example, given a simple class hierarchy as
191-
/// follows:
189+
/// The join of two types is the most specific type that is a supertype of
190+
/// both \c type1 and \c type2, e.g., the least upper bound in the type
191+
/// lattice. For example, given a simple class hierarchy as follows:
192192
///
193193
/// \code
194194
/// class A { }
@@ -197,15 +197,15 @@ class Type {
197197
/// class D { }
198198
/// \endcode
199199
///
200-
/// The meet of B and C is A, the meet of A and B is A. However, there is no
201-
/// meet of D and A (or D and B, or D and C) because there is no common
200+
/// The join of B and C is A, the join of A and B is A. However, there is no
201+
/// join of D and A (or D and B, or D and C) because there is no common
202202
/// superclass. One would have to jump to an existential (e.g., \c AnyObject)
203203
/// to find a common type.
204204
///
205-
/// \returns the meet of the two types, if there is a concrete type that can
206-
/// express the meet, or a null type if the only meet would be a more-general
205+
/// \returns the join of the two types, if there is a concrete type that can
206+
/// express the join, or a null type if the only join would be a more-general
207207
/// existential type (e.g., \c Any).
208-
static Type meet(Type type1, Type type2);
208+
static Type join(Type type1, Type type2);
209209

210210
private:
211211
// Direct comparison is disabled for types, because they may not be canonical.

lib/AST/TypeJoinMeet.cpp

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//===--- TypeJoinMeet.cpp - Swift Type "Join" and "Meet" -----------------===//
1+
//===--- TypeJoinjoin.cpp - Swift Type "join" and "meet" -----------------===//
22
//
33
// This source file is part of the Swift.org open source project
44
//
@@ -10,8 +10,8 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212
//
13-
// This file implements the "meet" operation for types (and, eventually,
14-
// "join").
13+
// This file implements the "join" operation for types (and, eventually,
14+
// "meet").
1515
//
1616
//===----------------------------------------------------------------------===//
1717
#include "swift/AST/ASTContext.h"
@@ -20,15 +20,15 @@
2020
#include "llvm/ADT/SmallPtrSet.h"
2121
using namespace swift;
2222

23-
Type Type::meet(Type type1, Type type2) {
23+
Type Type::join(Type type1, Type type2) {
2424
assert(!type1->hasTypeVariable() && !type2->hasTypeVariable() &&
25-
"Cannot compute meet of types involving type variables");
25+
"Cannot compute join of types involving type variables");
2626

2727
// FIXME: This algorithm is woefully incomplete, and is only currently used
2828
// for optimizing away extra exploratory work in the constraint solver. It
2929
// should eventually encompass all of the subtyping rules of the language.
3030

31-
// If the types are equivalent, the meet is obvious.
31+
// If the types are equivalent, the join is obvious.
3232
if (type1->isEqual(type2))
3333
return type1;
3434

@@ -64,7 +64,21 @@ Type Type::meet(Type type1, Type type2) {
6464
return nullptr;
6565
}
6666

67-
// The meet can only be an existential.
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+
81+
// The join can only be an existential.
6882
return nullptr;
6983
}
7084

lib/Sema/CSSolver.cpp

Lines changed: 52 additions & 10 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,20 +717,22 @@ 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
714-
// combine it with another supertype binding by computing the 'meet' of the
723+
// 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) {
721-
// Can we compute a meet?
731+
// Can we compute a join?
722732
auto &lastBinding = result.Bindings[*lastSupertypeIndex];
723733
if (auto meet =
724-
Type::meet(lastBinding.BindingType, binding.BindingType)) {
725-
// Replace the last supertype binding with the meet. We're done.
734+
Type::join(lastBinding.BindingType, binding.BindingType)) {
735+
// Replace the last supertype binding with the join. We're done.
726736
lastBinding.BindingType = meet;
727737
return;
728738
}
@@ -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)