Skip to content
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

[Don't merge] Use conditional conformances to implement Equatable for Optional, Array and Dictionary #12868

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
37 changes: 28 additions & 9 deletions lib/Sema/CSRanking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,34 @@ static bool isDeclAsSpecializedAs(TypeChecker &tc, DeclContext *dc,
cs.addConstraint(ConstraintKind::ConformsTo, selfTy1,
cast<ProtocolDecl>(outerDC2)->getDeclaredType(),
locator);

// The conditional requirements may be relevant for solving this system
// or for matching up the specialized-ness, but we're not actually
// entirely convinced this is necessary, since it seems like these are
// all guaranteed to be satisfied (since we already know we can call/use
// this decl).
for (auto requirement : conformance->getConditionalRequirements()) {
auto openedFirst =
cs.openType(requirement.getFirstType(), replacements);

Optional<Requirement> openedRequirement;
auto kind = requirement.getKind();
switch (kind) {
case RequirementKind::Conformance:
case RequirementKind::Superclass:
case RequirementKind::SameType: {
auto openedSecond =
cs.openType(requirement.getSecondType(), replacements);
openedRequirement = Requirement(kind, openedFirst, openedSecond);
break;
}
case RequirementKind::Layout:
openedRequirement = Requirement(kind, openedFirst,
requirement.getLayoutConstraint());
}
cs.addConstraint(*openedRequirement, locator);
}

break;

case SelfTypeRelationship::ConformedToBy:
Expand All @@ -563,15 +591,6 @@ static bool isDeclAsSpecializedAs(TypeChecker &tc, DeclContext *dc,
break;
}

if (conformance) {
assert(relationshipKind == SelfTypeRelationship::ConformsTo ||
relationshipKind == SelfTypeRelationship::ConformedToBy);

for (auto requirement : conformance->getConditionalRequirements()) {
cs.addConstraint(requirement, locator);
}
}

bool fewerEffectiveParameters = false;
switch (checkKind) {
case CheckAll:
Expand Down
19 changes: 14 additions & 5 deletions lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "ConstraintSystem.h"
#include "swift/AST/ExistentialLayout.h"
#include "swift/AST/ParameterList.h"
#include "swift/AST/ProtocolConformance.h"
#include "swift/Basic/StringExtras.h"
#include "swift/ClangImporter/ClangModule.h"
#include "llvm/Support/Compiler.h"
Expand Down Expand Up @@ -2606,11 +2607,19 @@ ConstraintSystem::SolutionKind ConstraintSystem::simplifyConformsToConstraint(
// This conformance may be conditional, in which case we need to consider
// those requirements as constraints too.
//
// FIXME: this doesn't seem to be right; the requirements are sometimes
// phrased in terms of the type's generic parameters, not type variables
// in this context.
for (auto req : conformance->getConditionalRequirements()) {
addConstraint(req, locator);
// If we've got a normal conformance, we must've started with `type` in
// the context that declares the conformance, and so these requirements
// are automatically satisfied, and thus we can skip adding them. (This
// hacks around some representational problems caused by requirements
// being stored as interface types and the normal conformance's type being
// a contextual type. Once the latter is fixed and everything is interface
// types, this shouldn't be necessary.)
if (conformance->isConcrete() &&
conformance->getConcrete()->getKind() !=
ProtocolConformanceKind::Normal) {
for (auto req : conformance->getConditionalRequirements()) {
addConstraint(req, locator);
}
}
return SolutionKind::Solved;
}
Expand Down
78 changes: 38 additions & 40 deletions stdlib/public/core/Arrays.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -2181,57 +2181,55 @@ extension _ArrayBufferProtocol {
// that are the same, e.g. Array<Int> and Array<Int>, not
// ArraySlice<Int> and Array<Int>.

/// Returns `true` if these arrays contain the same elements.
@_inlineable
public func == <Element : Equatable>(
lhs: ${Self}<Element>, rhs: ${Self}<Element>
) -> Bool {
let lhsCount = lhs.count
if lhsCount != rhs.count {
return false
}
extension ${Self} : Equatable where Element : Equatable {
/// Returns `true` if these arrays contain the same elements.
@_inlineable
public static func ==(lhs: ${Self}<Element>, rhs: ${Self}<Element>) -> Bool {
let lhsCount = lhs.count
if lhsCount != rhs.count {
return false
}

// Test referential equality.
if lhsCount == 0 || lhs._buffer.identity == rhs._buffer.identity {
return true
}
// Test referential equality.
if lhsCount == 0 || lhs._buffer.identity == rhs._buffer.identity {
return true
}

%if Self == 'ArraySlice':
%if Self == 'ArraySlice':

var streamLHS = lhs.makeIterator()
var streamRHS = rhs.makeIterator()
var streamLHS = lhs.makeIterator()
var streamRHS = rhs.makeIterator()

var nextLHS = streamLHS.next()
while nextLHS != nil {
let nextRHS = streamRHS.next()
if nextLHS != nextRHS {
return false
var nextLHS = streamLHS.next()
while nextLHS != nil {
let nextRHS = streamRHS.next()
if nextLHS != nextRHS {
return false
}
nextLHS = streamLHS.next()
}
nextLHS = streamLHS.next()
}

%else:
%else:

_sanityCheck(lhs.startIndex == 0 && rhs.startIndex == 0)
_sanityCheck(lhs.endIndex == lhsCount && rhs.endIndex == lhsCount)
_sanityCheck(lhs.startIndex == 0 && rhs.startIndex == 0)
_sanityCheck(lhs.endIndex == lhsCount && rhs.endIndex == lhsCount)

// We know that lhs.count == rhs.count, compare element wise.
for idx in 0..<lhsCount {
if lhs[idx] != rhs[idx] {
return false
// We know that lhs.count == rhs.count, compare element wise.
for idx in 0..<lhsCount {
if lhs[idx] != rhs[idx] {
return false
}
}
}
%end
%end

return true
}
return true
}

/// Returns `true` if the arrays do not contain the same elements.
@_inlineable
public func != <Element : Equatable>(
lhs: ${Self}<Element>, rhs: ${Self}<Element>
) -> Bool {
return !(lhs == rhs)
/// Returns `true` if the arrays do not contain the same elements.
@_inlineable
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@airspeedswift We no longer need to define operator!= do we?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless its implementation is more efficient than !(==).

public static func !=(lhs: ${Self}<Element>, rhs: ${Self}<Element>) -> Bool {
return !(lhs == rhs)
}
}

extension ${Self} {
Expand Down
2 changes: 1 addition & 1 deletion stdlib/public/core/HashedCollections.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -2708,7 +2708,7 @@ extension Dictionary {
}
}

extension Dictionary where Value : Equatable {
extension Dictionary : Equatable where Value : Equatable {
@_inlineable // FIXME(sil-serialize-all)
public static func == (lhs: [Key : Value], rhs: [Key : Value]) -> Bool {
switch (lhs._variantBuffer, rhs._variantBuffer) {
Expand Down
184 changes: 93 additions & 91 deletions stdlib/public/core/Optional.swift
Original file line number Diff line number Diff line change
Expand Up @@ -313,98 +313,100 @@ func _diagnoseUnexpectedNilOptional(_filenameStart: Builtin.RawPointer,
line: UInt(_line))
}

/// Returns a Boolean value indicating whether two optional instances are
/// equal.
///
/// Use this equal-to operator (`==`) to compare any two optional instances of
/// a type that conforms to the `Equatable` protocol. The comparison returns
/// `true` if both arguments are `nil` or if the two arguments wrap values
/// that are equal. Conversely, the comparison returns `false` if only one of
/// the arguments is `nil` or if the two arguments wrap values that are not
/// equal.
///
/// let group1 = [1, 2, 3, 4, 5]
/// let group2 = [1, 3, 5, 7, 9]
/// if group1.first == group2.first {
/// print("The two groups start the same.")
/// }
/// // Prints "The two groups start the same."
///
/// You can also use this operator to compare a non-optional value to an
/// optional that wraps the same type. The non-optional value is wrapped as an
/// optional before the comparison is made. In the following example, the
/// `numberToMatch` constant is wrapped as an optional before comparing to the
/// optional `numberFromString`:
///
/// let numberToFind: Int = 23
/// let numberFromString: Int? = Int("23") // Optional(23)
/// if numberToFind == numberFromString {
/// print("It's a match!")
/// }
/// // Prints "It's a match!"
///
/// An instance that is expressed as a literal can also be used with this
/// operator. In the next example, an integer literal is compared with the
/// optional integer `numberFromString`. The literal `23` is inferred as an
/// `Int` instance and then wrapped as an optional before the comparison is
/// performed.
///
/// if 23 == numberFromString {
/// print("It's a match!")
/// }
/// // Prints "It's a match!"
///
/// - Parameters:
/// - lhs: An optional value to compare.
/// - rhs: Another optional value to compare.
@_inlineable
public func == <T: Equatable>(lhs: T?, rhs: T?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l == r
case (nil, nil):
return true
default:
return false
extension Optional : Equatable where Wrapped : Equatable {
/// Returns a Boolean value indicating whether two optional instances are
/// equal.
///
/// Use this equal-to operator (`==`) to compare any two optional instances of
/// a type that conforms to the `Equatable` protocol. The comparison returns
/// `true` if both arguments are `nil` or if the two arguments wrap values
/// that are equal. Conversely, the comparison returns `false` if only one of
/// the arguments is `nil` or if the two arguments wrap values that are not
/// equal.
///
/// let group1 = [1, 2, 3, 4, 5]
/// let group2 = [1, 3, 5, 7, 9]
/// if group1.first == group2.first {
/// print("The two groups start the same.")
/// }
/// // Prints "The two groups start the same."
///
/// You can also use this operator to compare a non-optional value to an
/// optional that wraps the same type. The non-optional value is wrapped as an
/// optional before the comparison is made. In the following example, the
/// `numberToMatch` constant is wrapped as an optional before comparing to the
/// optional `numberFromString`:
///
/// let numberToFind: Int = 23
/// let numberFromString: Int? = Int("23") // Optional(23)
/// if numberToFind == numberFromString {
/// print("It's a match!")
/// }
/// // Prints "It's a match!"
///
/// An instance that is expressed as a literal can also be used with this
/// operator. In the next example, an integer literal is compared with the
/// optional integer `numberFromString`. The literal `23` is inferred as an
/// `Int` instance and then wrapped as an optional before the comparison is
/// performed.
///
/// if 23 == numberFromString {
/// print("It's a match!")
/// }
/// // Prints "It's a match!"
///
/// - Parameters:
/// - lhs: An optional value to compare.
/// - rhs: Another optional value to compare.
@_inlineable
public static func ==(lhs: Wrapped?, rhs: Wrapped?) -> Bool {
switch (lhs, rhs) {
case let (l?, r?):
return l == r
case (nil, nil):
return true
default:
return false
}
}

/// Returns a Boolean value indicating whether two optional instances are not
/// equal.
///
/// Use this not-equal-to operator (`!=`) to compare any two optional instances
/// of a type that conforms to the `Equatable` protocol. The comparison
/// returns `true` if only one of the arguments is `nil` or if the two
/// arguments wrap values that are not equal. The comparison returns `false`
/// if both arguments are `nil` or if the two arguments wrap values that are
/// equal.
///
/// let group1 = [2, 4, 6, 8, 10]
/// let group2 = [1, 3, 5, 7, 9]
/// if group1.first != group2.first {
/// print("The two groups start differently.")
/// }
/// // Prints "The two groups start differently."
///
/// You can also use this operator to compare a non-optional value to an
/// optional that wraps the same type. The non-optional value is wrapped as an
/// optional before the comparison is made. In this example, the
/// `numberToMatch` constant is wrapped as an optional before comparing to the
/// optional `numberFromString`:
///
/// let numberToFind: Int = 23
/// let numberFromString: Int? = Int("not-a-number") // nil
/// if numberToFind != numberFromString {
/// print("No match.")
/// }
/// // Prints "No match."
///
/// - Parameters:
/// - lhs: An optional value to compare.
/// - rhs: Another optional value to compare.
@_inlineable
public static func !=(lhs: Wrapped?, rhs: Wrapped?) -> Bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto

return !(lhs == rhs)
}
}

/// Returns a Boolean value indicating whether two optional instances are not
/// equal.
///
/// Use this not-equal-to operator (`!=`) to compare any two optional instances
/// of a type that conforms to the `Equatable` protocol. The comparison
/// returns `true` if only one of the arguments is `nil` or if the two
/// arguments wrap values that are not equal. The comparison returns `false`
/// if both arguments are `nil` or if the two arguments wrap values that are
/// equal.
///
/// let group1 = [2, 4, 6, 8, 10]
/// let group2 = [1, 3, 5, 7, 9]
/// if group1.first != group2.first {
/// print("The two groups start differently.")
/// }
/// // Prints "The two groups start differently."
///
/// You can also use this operator to compare a non-optional value to an
/// optional that wraps the same type. The non-optional value is wrapped as an
/// optional before the comparison is made. In this example, the
/// `numberToMatch` constant is wrapped as an optional before comparing to the
/// optional `numberFromString`:
///
/// let numberToFind: Int = 23
/// let numberFromString: Int? = Int("not-a-number") // nil
/// if numberToFind != numberFromString {
/// print("No match.")
/// }
/// // Prints "No match."
///
/// - Parameters:
/// - lhs: An optional value to compare.
/// - rhs: Another optional value to compare.
@_inlineable
public func != <T : Equatable>(lhs: T?, rhs: T?) -> Bool {
return !(lhs == rhs)
}

// Enable pattern matching against the nil literal, even if the element type
Expand Down
17 changes: 17 additions & 0 deletions test/Generics/conditional_conformances_operators.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// RUN: %target-typecheck-verify-swift -typecheck %s -verify

// rdar://problem/35480952

postfix operator %%%
protocol P {
static postfix func %%%(lhs: Self)
}
protocol Q {}
struct Foo<T> {}
extension Foo: P where T : Q {
static postfix func %%%(lhs: Foo<T>) {}
}

func useIt<T: Q>(_: T.Type) {
Foo<T>()%%%
}