Skip to content

Sema: Handle protocol compositions containing type variables in matchTypes() [5.1] #25555

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
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
56 changes: 54 additions & 2 deletions lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1606,7 +1606,44 @@ ConstraintSystem::matchDeepEqualityTypes(Type type1, Type type2,
// Match up the replacement types of the respective substitution maps.
return matchDeepTypeArguments(*this, subflags, args1, args2, locator);
}


// Handle protocol compositions.
if (auto existential1 = type1->getAs<ProtocolCompositionType>()) {
if (auto existential2 = type2->getAs<ProtocolCompositionType>()) {
auto layout1 = existential1->getExistentialLayout();
auto layout2 = existential2->getExistentialLayout();

// Explicit AnyObject and protocols must match exactly.
if (layout1.hasExplicitAnyObject != layout2.hasExplicitAnyObject)
return getTypeMatchFailure(locator);

if (layout1.getProtocols().size() != layout2.getProtocols().size())
return getTypeMatchFailure(locator);

for (unsigned i: indices(layout1.getProtocols())) {
if (!layout1.getProtocols()[i]->isEqual(layout2.getProtocols()[i]))
return getTypeMatchFailure(locator);
}

// This is the only interesting case. We might have type variables
// on either side of the superclass constraint, so make sure we
// recursively call matchTypes() here.
if (layout1.explicitSuperclass || layout2.explicitSuperclass) {
if (!layout1.explicitSuperclass || !layout2.explicitSuperclass)
return getTypeMatchFailure(locator);

auto result = matchTypes(layout1.explicitSuperclass,
layout2.explicitSuperclass,
ConstraintKind::Bind, subflags,
locator.withPathElement(
ConstraintLocator::ExistentialSuperclassType));
if (result.isFailure())
return result;
}

return getTypeMatchSuccess();
}
}
// Handle nominal types that are not directly generic.
if (auto nominal1 = type1->getAs<NominalType>()) {
auto nominal2 = type2->castTo<NominalType>();
Expand Down Expand Up @@ -2533,7 +2570,22 @@ ConstraintSystem::matchTypes(Type type1, Type type2, ConstraintKind kind,
llvm_unreachable("Polymorphic function type should have been opened");

case TypeKind::ProtocolComposition:
// Existential types handled below.
switch (kind) {
case ConstraintKind::Equal:
case ConstraintKind::Bind:
case ConstraintKind::BindParam:
// If we are matching types for equality, we might still have
// type variables inside the protocol composition's superclass
// constraint.
conversionsOrFixes.push_back(ConversionRestrictionKind::DeepEquality);
break;

default:
// Subtype constraints where the RHS is an existential type are
// handled below.
break;
}

break;

case TypeKind::LValue:
Expand Down
5 changes: 5 additions & 0 deletions lib/Sema/ConstraintLocator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ void ConstraintLocator::Profile(llvm::FoldingSetNodeID &id, Expr *anchor,
case RValueAdjustment:
case ClosureResult:
case ParentType:
case ExistentialSuperclassType:
case InstanceType:
case SequenceElementType:
case AutoclosureResult:
Expand Down Expand Up @@ -308,6 +309,10 @@ void ConstraintLocator::dump(SourceManager *sm, raw_ostream &out) {
out << "parent type";
break;

case ExistentialSuperclassType:
out << "existential superclass type";
break;

case LValueConversion:
out << "@lvalue-to-inout conversion";
break;
Expand Down
4 changes: 4 additions & 0 deletions lib/Sema/ConstraintLocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ class ConstraintLocator : public llvm::FoldingSetNode {
ClosureResult,
/// The parent of a nested type.
ParentType,
/// The superclass of a protocol existential type.
ExistentialSuperclassType,
/// The instance of a metatype type.
InstanceType,
/// The element type of a sequence in a for ... in ... loop.
Expand Down Expand Up @@ -155,6 +157,7 @@ class ConstraintLocator : public llvm::FoldingSetNode {
case ClosureResult:
case ParentType:
case InstanceType:
case ExistentialSuperclassType:
case SequenceElementType:
case AutoclosureResult:
case Requirement:
Expand Down Expand Up @@ -212,6 +215,7 @@ class ConstraintLocator : public llvm::FoldingSetNode {
case MemberRefBase:
case UnresolvedMember:
case ParentType:
case ExistentialSuperclassType:
case LValueConversion:
case RValueAdjustment:
case SubscriptMember:
Expand Down
18 changes: 18 additions & 0 deletions test/type/subclass_composition.swift
Original file line number Diff line number Diff line change
Expand Up @@ -530,3 +530,21 @@ struct DerivedBox<T : Derived> {}

func takesBoxWithP3(_: DerivedBox<Derived & P3>) {}
// expected-error@-1 {{'DerivedBox' requires that 'Derived & P3' inherit from 'Derived'}}

// A bit of a tricky setup -- the real problem is that matchTypes() did the
// wrong thing when solving a Bind constraint where both sides were protocol
// compositions, but one of them had a superclass constraint containing type
// variables. We were checking type equality in this case, which is not
// correct; we have to do a 'deep equality' check, recursively matching the
// superclass types.
struct Generic<T> {
var _x: (Base<T> & P2)!

var x: (Base<T> & P2)? {
get { return _x }
set { _x = newValue }
_modify {
yield &_x
}
}
}