Skip to content

[5.0][GSB] When adding same-type requirements pick representative based ... #20810

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 3 commits into from
Nov 28, 2018
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
143 changes: 113 additions & 30 deletions lib/AST/GenericSignatureBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "swift/Basic/Defer.h"
#include "swift/Basic/Statistic.h"
#include "llvm/ADT/GraphTraits.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallString.h"
Expand Down Expand Up @@ -4889,12 +4890,8 @@ GenericSignatureBuilder::addSameTypeRequirementBetweenTypeParameters(
auto T1 = OrigT1->getRepresentative();
auto T2 = OrigT2->getRepresentative();

// Decide which potential archetype is to be considered the representative.
// We prefer potential archetypes with lower nesting depths, because it
// prevents us from unnecessarily building deeply nested potential archetypes.
unsigned nestingDepth1 = T1->getNestingDepth();
unsigned nestingDepth2 = T2->getNestingDepth();
if (nestingDepth2 < nestingDepth1) {
// Pick representative based on the canonical ordering of the type parameters.
if (compareDependentTypes(depType2, depType1) < 0) {
std::swap(T1, T2);
std::swap(OrigT1, OrigT2);
std::swap(equivClass, equivClass2);
Expand All @@ -4906,6 +4903,8 @@ GenericSignatureBuilder::addSameTypeRequirementBetweenTypeParameters(
equivClass = T1->getOrCreateEquivalenceClass(*this);

// Record this same-type constraint.
// Let's keep type order in the new constraint the same as it's written
// in source, which makes it much easier to diagnose later.
equivClass->sameTypeConstraints.push_back({depType1, depType2, source});

// Determine the anchor types of the two equivalence classes.
Expand Down Expand Up @@ -6908,34 +6907,118 @@ void GenericSignatureBuilder::checkSameTypeConstraints(
// connect all of the components, or else we wouldn't have an equivalence
// class.
if (intercomponentEdges.size() > numComponents - 1) {
std::vector<bool> connected(numComponents, false);
const auto &firstEdge = intercomponentEdges.front();
for (const auto &edge : intercomponentEdges) {
// If both the source and target are already connected, this edge is
// not part of the spanning tree.
if (connected[edge.source] && connected[edge.target]) {
if (edge.constraint.source->shouldDiagnoseRedundancy(true) &&
firstEdge.constraint.source->shouldDiagnoseRedundancy(false)) {
Diags.diagnose(edge.constraint.source->getLoc(),
diag::redundant_same_type_constraint,
edge.constraint.getSubjectDependentType(
genericParams),
edge.constraint.value);

Diags.diagnose(firstEdge.constraint.source->getLoc(),
diag::previous_same_type_constraint,
firstEdge.constraint.source->classifyDiagKind(),
firstEdge.constraint.getSubjectDependentType(
genericParams),
firstEdge.constraint.value);
// First let's order all of the intercomponent edges
// as written in source, this helps us to diagnose
// all of the duplicate constraints in correct order.
std::vector<IntercomponentEdge *> sourceOrderedEdges;
for (auto &edge : intercomponentEdges)
sourceOrderedEdges.push_back(&edge);

llvm::array_pod_sort(
sourceOrderedEdges.begin(), sourceOrderedEdges.end(),
[](IntercomponentEdge *const *a, IntercomponentEdge *const *b) -> int {
auto &sourceMgr = (*a)->constraint.value->getASTContext().SourceMgr;

auto locA = (*a)->constraint.source->getLoc();
auto locB = (*b)->constraint.source->getLoc();

auto bufferA = sourceMgr.findBufferContainingLoc(locA);
auto bufferB = sourceMgr.findBufferContainingLoc(locB);

if (bufferA != bufferB)
return bufferA < bufferB ? -1 : 1;

auto offsetA = sourceMgr.getLocOffsetInBuffer(locA, bufferA);
auto offsetB = sourceMgr.getLocOffsetInBuffer(locB, bufferB);

return offsetA < offsetB ? -1 : (offsetA == offsetB ? 0 : 1);
});

auto isDiagnosable = [](const IntercomponentEdge &edge, bool isPrimary) {
return edge.constraint.source->shouldDiagnoseRedundancy(isPrimary);
};

using EquivClass = llvm::DenseMap<Type, unsigned>;
llvm::DenseMap<Type, EquivClass> equivalences;
// The idea here is to form an equivalence class per representative
// (picked from each edge constraint in type parameter order) and
// propagate all new equivalent types up the chain until duplicate
// entry is found, that entry is going to point to previous
// declaration and is going to mark current edge as a duplicate of
// such entry.
for (auto edgeIdx : indices(sourceOrderedEdges)) {
const auto &edge = *sourceOrderedEdges[edgeIdx];

Type lhs = edge.constraint.getSubjectDependentType(genericParams);
Type rhs = edge.constraint.value;

// Make sure that representative for equivalence class is picked
// in canonical type parameter order.
if (compareDependentTypes(rhs, lhs) < 0)
std::swap(lhs, rhs);

// Index of the previous declaration of the same-type constraint
// which current edge might be a duplicate of.
Optional<unsigned> previousIndex;

bool isDuplicate = false;
auto &representative = equivalences[lhs];
if (representative.insert({rhs, edgeIdx}).second) {
// Since this is a new equivalence, and right-hand side might
// be a representative of some other equivalence class,
// its existing members have to be merged up.
auto RHSEquivClass = equivalences.find(rhs);
if (RHSEquivClass != equivalences.end()) {
auto &equivClass = RHSEquivClass->getSecond();
representative.insert(equivClass.begin(), equivClass.end());
}

continue;
// If left-hand side is involved in any other equivalences
// let's propagate new information up the chain.
for (auto &e : equivalences) {
auto &repr = e.first;
auto &equivClass = e.second;

if (repr->isEqual(lhs) || !equivClass.count(lhs))
continue;

if (!equivClass.insert({rhs, edgeIdx}).second) {
// Even if "previous" edge is not diagnosable we
// still need to produce diagnostic about main duplicate.
isDuplicate = true;

auto prevIdx = equivClass[rhs];
if (!isDiagnosable(intercomponentEdges[prevIdx],
/*isPrimary=*/false))
continue;

// If there is a diagnosable duplicate equivalence,
// it means that we've found our previous declaration.
previousIndex = prevIdx;
break;
}
}
} else {
// Looks like this is a situation like T.A == T.B, ..., T.B == T.A
previousIndex = representative[rhs];
isDuplicate = true;
}

// Put the source and target into the spanning tree.
connected[edge.source] = true;
connected[edge.target] = true;
if (!isDuplicate || !isDiagnosable(edge, /*isPrimary=*/true))
continue;

Diags.diagnose(edge.constraint.source->getLoc(),
diag::redundant_same_type_constraint,
edge.constraint.getSubjectDependentType(genericParams),
edge.constraint.value);

if (previousIndex) {
auto &prevEquiv = sourceOrderedEdges[*previousIndex]->constraint;
Diags.diagnose(
prevEquiv.source->getLoc(), diag::previous_same_type_constraint,
prevEquiv.source->classifyDiagKind(),
prevEquiv.getSubjectDependentType(genericParams), prevEquiv.value);
}
}
}

Expand Down
17 changes: 17 additions & 0 deletions test/Generics/rdar45957015.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// RUN: %target-typecheck-verify-swift

protocol C {
associatedtype T : Collection where T.Element == Self
}

protocol V : C, RawRepresentable where RawValue == String {}

protocol P {
associatedtype A: V
}

extension P {
func foo<U: Collection>(_ args: U) -> String where U.Element == A {
return args.reduce("", { $1.rawValue }) // Ok
}
}
4 changes: 2 additions & 2 deletions test/Generics/same_type_constraints.swift
Original file line number Diff line number Diff line change
Expand Up @@ -353,9 +353,9 @@ func intercomponentSameComponents<T: P10>(_: T)
func intercomponentMoreThanSpanningTree<T: P10>(_: T)
where T.A == T.B,
T.B == T.C,
T.D == T.E, // expected-note{{previous same-type constraint 'T.D' == 'T.E' written here}}
T.D == T.E, // expected-note {{previous same-type constraint 'T.D' == 'T.E' written here}}
T.D == T.B,
T.E == T.B // expected-warning{{redundant same-type constraint 'T.E' == 'T.B'}}
T.E == T.B // expected-warning {{redundant same-type constraint 'T.E' == 'T.B'}}
{ }

func trivialRedundancy<T: P10>(_: T) where T.A == T.A { } // expected-warning{{redundant same-type constraint 'T.A' == 'T.A'}}
Expand Down
6 changes: 3 additions & 3 deletions test/decl/protocol/req/associated_type_ambiguity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ protocol P1 {
}

protocol P2 {
associatedtype T // expected-note {{found this candidate}}
associatedtype T // expected-note 2 {{found this candidate}}
}

// FIXME: This extension's generic signature is still minimized differently from
Expand All @@ -36,7 +36,7 @@ extension P1 where Self : P2 {
// Same as above, but now we have two visible associated types with the same
// name.
protocol P3 {
associatedtype T
associatedtype T // expected-note {{found this candidate}}
}

// FIXME: This extension's generic signature is still minimized differently from
Expand All @@ -48,7 +48,7 @@ extension P2 where Self : P3, T == Int {
}

extension P2 where Self : P3 {
func takeT1(_: T) {}
func takeT1(_: T) {} // expected-error {{'T' is ambiguous for type lookup in this context}}
func takeT2(_: Self.T) {}
}

Expand Down