Skip to content

Implement 'join' for optional types and a special case for 'nil'. #4408

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 19, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions include/swift/AST/Type.h
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,11 @@ class Type {
/// Get the canonical type, or return null if the type is null.
CanType getCanonicalTypeOrNull() const; // in Types.h

/// Computes the meet between two types.
/// Computes the join between two types.
///
/// The meet of two types is the most specific type that is a supertype of
/// both \c type1 and \c type2. For example, given a simple class hierarchy as
/// follows:
/// The join of two types is the most specific type that is a supertype of
/// both \c type1 and \c type2, e.g., the least upper bound in the type
/// lattice. For example, given a simple class hierarchy as follows:
///
/// \code
/// class A { }
Expand All @@ -197,15 +197,15 @@ class Type {
/// class D { }
/// \endcode
///
/// The meet of B and C is A, the meet of A and B is A. However, there is no
/// meet of D and A (or D and B, or D and C) because there is no common
/// The join of B and C is A, the join of A and B is A. However, there is no
/// join of D and A (or D and B, or D and C) because there is no common
/// superclass. One would have to jump to an existential (e.g., \c AnyObject)
/// to find a common type.
///
/// \returns the meet of the two types, if there is a concrete type that can
/// express the meet, or a null type if the only meet would be a more-general
/// \returns the join of the two types, if there is a concrete type that can
/// express the join, or a null type if the only join would be a more-general
/// existential type (e.g., \c Any).
static Type meet(Type type1, Type type2);
static Type join(Type type1, Type type2);

private:
// Direct comparison is disabled for types, because they may not be canonical.
Expand Down
28 changes: 21 additions & 7 deletions lib/AST/TypeJoinMeet.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//===--- TypeJoinMeet.cpp - Swift Type "Join" and "Meet" -----------------===//
//===--- TypeJoinjoin.cpp - Swift Type "join" and "meet" -----------------===//
//
// This source file is part of the Swift.org open source project
//
Expand All @@ -10,8 +10,8 @@
//
//===----------------------------------------------------------------------===//
//
// This file implements the "meet" operation for types (and, eventually,
// "join").
// This file implements the "join" operation for types (and, eventually,
// "meet").
//
//===----------------------------------------------------------------------===//
#include "swift/AST/ASTContext.h"
Expand All @@ -20,15 +20,15 @@
#include "llvm/ADT/SmallPtrSet.h"
using namespace swift;

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

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

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

Expand Down Expand Up @@ -64,7 +64,21 @@ Type Type::meet(Type type1, Type type2) {
return nullptr;
}

// The meet can only be an existential.
// If one or both of the types are optional types, look at the underlying
// object type.
OptionalTypeKind otk1, otk2;
Type objectType1 = type1->getAnyOptionalObjectType(otk1);
Type objectType2 = type2->getAnyOptionalObjectType(otk2);
if (otk1 == OTK_Optional || otk2 == OTK_Optional) {
// Compute the join of the unwrapped type. If there is none, we're done.
Type unwrappedJoin = join(objectType1 ? objectType1 : type1,
objectType2 ? objectType2 : type2);
if (!unwrappedJoin) return nullptr;

return OptionalType::get(unwrappedJoin);
}

// The join can only be an existential.
return nullptr;
}

62 changes: 52 additions & 10 deletions lib/Sema/CSSolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ STATISTIC(LargestSolutionAttemptNumber, "# of the largest solution attempt");
///
/// \returns the type to bind to, if the binding is okay.
static Optional<Type> checkTypeOfBinding(ConstraintSystem &cs,
TypeVariableType *typeVar, Type type) {
TypeVariableType *typeVar, Type type,
bool *isNilLiteral = nullptr) {
if (!type)
return None;

Expand All @@ -63,8 +64,15 @@ static Optional<Type> checkTypeOfBinding(ConstraintSystem &cs,
// If the type is a type variable itself, don't permit the binding.
// FIXME: This is a hack. We need to be smarter about whether there's enough
// structure in the type to produce an interesting binding, or not.
if (type->getRValueType()->is<TypeVariableType>())
if (auto bindingTypeVar = type->getRValueType()->getAs<TypeVariableType>()) {
if (isNilLiteral &&
bindingTypeVar->getImpl().literalConformanceProto &&
bindingTypeVar->getImpl().literalConformanceProto->isSpecificProtocol(
KnownProtocolKind::ExpressibleByNilLiteral))
*isNilLiteral = true;

return None;
}

// Okay, allow the binding (with the simplified type).
return type;
Expand Down Expand Up @@ -709,20 +717,22 @@ static PotentialBindings getPotentialBindings(ConstraintSystem &cs,

// Local function to add a potential binding to the list of bindings,
// coalescing supertype bounds when we are able to compute the meet.
auto addPotentialBinding = [&](PotentialBinding binding) {
auto addPotentialBinding = [&](PotentialBinding binding,
bool allowJoinMeet = true) {
// If this is a non-defaulted supertype binding, check whether we can
// combine it with another supertype binding by computing the 'meet' of the
// combine it with another supertype binding by computing the 'join' of the
// types.
if (binding.Kind == AllowedBindingKind::Supertypes &&
!binding.BindingType->hasTypeVariable() &&
!binding.DefaultedProtocol &&
!binding.IsDefaultableBinding) {
!binding.IsDefaultableBinding &&
allowJoinMeet) {
if (lastSupertypeIndex) {
// Can we compute a meet?
// Can we compute a join?
auto &lastBinding = result.Bindings[*lastSupertypeIndex];
if (auto meet =
Type::meet(lastBinding.BindingType, binding.BindingType)) {
// Replace the last supertype binding with the meet. We're done.
Type::join(lastBinding.BindingType, binding.BindingType)) {
// Replace the last supertype binding with the join. We're done.
lastBinding.BindingType = meet;
return;
}
Expand All @@ -739,6 +749,7 @@ static PotentialBindings getPotentialBindings(ConstraintSystem &cs,
llvm::SmallPtrSet<CanType, 4> exactTypes;
llvm::SmallPtrSet<ProtocolDecl *, 4> literalProtocols;
SmallVector<Constraint *, 2> defaultableConstraints;
bool addOptionalSupertypeBindings = false;
auto &tc = cs.getTypeChecker();
for (auto constraint : constraints) {
// Only visit each constraint once.
Expand Down Expand Up @@ -917,11 +928,18 @@ static PotentialBindings getPotentialBindings(ConstraintSystem &cs,

// Check whether we can perform this binding.
// FIXME: this has a super-inefficient extraneous simplifyType() in it.
if (auto boundType = checkTypeOfBinding(cs, typeVar, type)) {
bool isNilLiteral = false;
if (auto boundType = checkTypeOfBinding(cs, typeVar, type, &isNilLiteral)) {
type = *boundType;
if (type->hasTypeVariable())
result.InvolvesTypeVariables = true;
} else {
// If the bound is a 'nil' literal type, add optional supertype bindings.
if (isNilLiteral && kind == AllowedBindingKind::Supertypes) {
addOptionalSupertypeBindings = true;
continue;
}

result.InvolvesTypeVariables = true;
continue;
}
Expand Down Expand Up @@ -980,7 +998,7 @@ static PotentialBindings getPotentialBindings(ConstraintSystem &cs,
addPotentialBinding({type, kind, None});
if (alternateType &&
exactTypes.insert(alternateType->getCanonicalType()).second)
addPotentialBinding({alternateType, kind, None});
addPotentialBinding({alternateType, kind, None}, /*allowJoinMeet=*/false);
}

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

// If we're supposed to add optional supertype bindings, do so now.
if (addOptionalSupertypeBindings) {
for (unsigned i : indices(result.Bindings)) {
// Only interested in supertype bindings.
auto &binding = result.Bindings[i];
if (binding.Kind != AllowedBindingKind::Supertypes) continue;

// If the type doesn't conform to ExpressibleByNilLiteral,
// produce an optional of that type as a potential binding. We
// overwrite the binding in place because the non-optional type
// will fail to type-check against the nil-literal conformance.
auto nominalBindingDecl = binding.BindingType->getAnyNominal();
if (!nominalBindingDecl) continue;
SmallVector<ProtocolConformance *, 2> conformances;
if (!nominalBindingDecl->lookupConformance(
cs.DC->getParentModule(),
cs.getASTContext().getProtocol(
KnownProtocolKind::ExpressibleByNilLiteral),
conformances)) {
binding.BindingType = OptionalType::get(binding.BindingType);
}
}
}

return result;
}

Expand Down
15 changes: 15 additions & 0 deletions test/Constraints/array_literal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,18 @@ func defaultToAny(i: Int, s: String) {
let a4 = [B(), C()]
let _: Int = a4 // expected-error{{value of type '[A]'}}
}

/// Check handling of 'nil'.
func joinWithNil(s: String) {
let a1 = [s, nil]
let _: Int = a1 // expected-error{{value of type '[String?]'}}

let a2 = [nil, s]
let _: Int = a2 // expected-error{{value of type '[String?]'}}

let a3 = ["hello", nil]
let _: Int = a3 // expected-error{{value of type '[String?]'}}

let a4 = [nil, "hello"]
let _: Int = a4 // expected-error{{value of type '[String?]'}}
}
11 changes: 11 additions & 0 deletions test/Constraints/optional.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,14 @@ func testVoidOptional() {
let optNoop: (()?) -> ()? = { return $0 }
voidOptional(optNoop)
}

func testTernaryWithNil(b: Bool, s: String, i: Int) {
let t1 = b ? s : nil
let _: Double = t1 // expected-error{{value of type 'String?'}}
let t2 = b ? nil : i
let _: Double = t2 // expected-error{{value of type 'Int?'}}
let t3 = b ? "hello" : nil
let _: Double = t3 // expected-error{{value of type 'String?'}}
let t4 = b ? nil : 1
let _: Double = t4 // expected-error{{value of type 'Int?'}}
}