Skip to content

[4.1] Associated type fixes for extraneous recursion, crashes, inference failure #14159

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
32 changes: 18 additions & 14 deletions lib/AST/ProtocolConformance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -530,8 +530,9 @@ bool NormalProtocolConformance::hasTypeWitness(AssociatedTypeDecl *assocType,
if (Loader)
resolveLazyInfo();

if (TypeWitnesses.find(assocType) != TypeWitnesses.end()) {
return true;
auto found = TypeWitnesses.find(assocType);
if (found != TypeWitnesses.end()) {
return !found->getSecond().first.isNull();
}
if (resolver) {
PrettyStackTraceRequirement trace("resolving", this, assocType);
Expand All @@ -556,24 +557,25 @@ NormalProtocolConformance::getTypeWitnessAndDecl(AssociatedTypeDecl *assocType,
if (known != TypeWitnesses.end())
return known->second;

// If this conformance is in a state where it is inferring type witnesses,
// check tentative witnesses.
if (getState() == ProtocolConformanceState::CheckingTypeWitnesses) {
// If there is a tentative-type-witness function, use it.
if (options.getTentativeTypeWitness) {
if (Type witnessType =
Type(options.getTentativeTypeWitness(this, assocType)))
return { witnessType, nullptr };
}
// If there is a tentative-type-witness function, use it.
if (options.getTentativeTypeWitness) {
if (Type witnessType =
Type(options.getTentativeTypeWitness(this, assocType)))
return { witnessType, nullptr };
}

// Otherwise, we fail; this is the only case in which we can return a
// null type.
// If this conformance is in a state where it is inferring type witnesses but
// we didn't find anything, fail.
if (getState() == ProtocolConformanceState::CheckingTypeWitnesses) {
return { Type(), nullptr };
}

// Otherwise, resolve the type witness.
PrettyStackTraceRequirement trace("resolving", this, assocType);
assert(resolver && "Unable to resolve type witness");

// Block recursive resolution of this type witness.
TypeWitnesses[assocType] = { Type(), nullptr };
resolver->resolveTypeWitness(this, assocType);

known = TypeWitnesses.find(assocType);
Expand All @@ -586,7 +588,9 @@ void NormalProtocolConformance::setTypeWitness(AssociatedTypeDecl *assocType,
TypeDecl *typeDecl) const {
assert(getProtocol() == cast<ProtocolDecl>(assocType->getDeclContext()) &&
"associated type in wrong protocol");
assert(TypeWitnesses.count(assocType) == 0 && "Type witness already known");
assert((TypeWitnesses.count(assocType) == 0 ||
TypeWitnesses[assocType].first.isNull()) &&
"Type witness already known");
assert((!isComplete() || isInvalid()) && "Conformance already complete?");
assert(!type->hasArchetype() && "type witnesses must be interface types");
TypeWitnesses[assocType] = std::make_pair(type, typeDecl);
Expand Down
21 changes: 18 additions & 3 deletions lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1796,8 +1796,8 @@ void ConformanceChecker::recordTypeWitness(AssociatedTypeDecl *assocType,

// If we already recoded this type witness, there's nothing to do.
if (Conformance->hasTypeWitness(assocType)) {
assert(Conformance->getTypeWitness(assocType, nullptr)
->isEqual(type) && "Conflicting type witness deductions");
assert(Conformance->getTypeWitness(assocType, nullptr)->isEqual(type) &&
"Conflicting type witness deductions");
return;
}

Expand Down Expand Up @@ -2656,8 +2656,12 @@ CheckTypeWitnessResult swift::checkTypeWitness(TypeChecker &tc, DeclContext *dc,
auto *depTy = DependentMemberType::get(proto->getSelfInterfaceType(),
assocType);

if (type->hasError())
return ErrorType::get(tc.Context);

Type contextType = type->hasTypeParameter() ? dc->mapTypeIntoContext(type)
: type;

if (auto superclass = genericSig->getSuperclassBound(depTy)) {
if (!superclass->isExactSuperclassOf(contextType))
return superclass;
Expand Down Expand Up @@ -2747,6 +2751,16 @@ ResolveWitnessResult ConformanceChecker::resolveTypeWitnessViaLookup(
}
}

// If there are no viable witnesses, and all nonviable candidates came from
// protocol extensions, treat this as "missing".
if (viable.empty() &&
std::find_if(nonViable.begin(), nonViable.end(),
[](const std::pair<TypeDecl *, CheckTypeWitnessResult> &x) {
return x.first->getDeclContext()
->getAsProtocolOrProtocolExtensionContext() == nullptr;
}) == nonViable.end())
return ResolveWitnessResult::Missing;

// If there is a single viable candidate, form a substitution for it.
if (viable.size() == 1) {
auto interfaceType = viable.front().second;
Expand Down Expand Up @@ -2781,7 +2795,8 @@ ResolveWitnessResult ConformanceChecker::resolveTypeWitnessViaLookup(
[nonViable](NormalProtocolConformance *conformance) {
auto &diags = conformance->getDeclContext()->getASTContext().Diags;
for (auto candidate : nonViable) {
if (candidate.first->getDeclaredInterfaceType()->hasError())
if (candidate.first->getDeclaredInterfaceType()->hasError() ||
candidate.second.isError())
continue;

diags.diagnose(
Expand Down
4 changes: 3 additions & 1 deletion lib/Sema/TypeCheckProtocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,9 @@ class CheckTypeWitnessResult {
bool isConformanceRequirement() const {
return Requirement->isExistentialType();
}

bool isError() const {
return Requirement->is<ErrorType>();
}
explicit operator bool() const { return !Requirement.isNull(); }
};

Expand Down
5 changes: 4 additions & 1 deletion lib/Sema/TypeCheckProtocolInference.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -818,7 +818,7 @@ Type AssociatedTypeInference::computeDefaultTypeWitness(

if (auto failed = checkTypeWitness(tc, dc, proto, assocType, defaultType)) {
// Record the failure, if we haven't seen one already.
if (!failedDefaultedAssocType) {
if (!failedDefaultedAssocType && !failed.isError()) {
failedDefaultedAssocType = defaultedAssocType;
failedDefaultedWitness = defaultType;
failedDefaultedResult = failed;
Expand Down Expand Up @@ -1680,6 +1680,9 @@ bool AssociatedTypeInference::diagnoseNoSolutions(
diags.diagnose(assocType, diag::bad_associated_type_deduction,
assocType->getFullName(), proto->getFullName());
for (const auto &failed : failedSet) {
if (failed.Result.isError())
continue;

diags.diagnose(failed.Witness,
diag::associated_type_deduction_witness_failed,
failed.Requirement->getFullName(),
Expand Down
4 changes: 2 additions & 2 deletions test/Constraints/same_types.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// RUN: %target-typecheck-verify-swift -verify-ignore-unknown

protocol Fooable {
associatedtype Foo
associatedtype Foo // expected-note{{protocol requires nested type 'Foo'; do you want to add it?}}

var foo: Foo { get }
}
Expand Down Expand Up @@ -148,7 +148,7 @@ rdar19137463(1)

struct Brunch<U : Fooable> where U.Foo == X {}

struct BadFooable : Fooable {
struct BadFooable : Fooable { // expected-error{{type 'BadFooable' does not conform to protocol 'Fooable'}}
typealias Foo = DoesNotExist // expected-error{{use of undeclared type 'DoesNotExist'}}
var foo: Foo { while true {} }
}
Expand Down
4 changes: 2 additions & 2 deletions test/decl/nested/type_in_function.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func hasAClosure() {
}

protocol Racoon {
associatedtype Stripes
associatedtype Stripes // expected-note{{protocol requires nested type 'Stripes'; do you want to add it?}}
}

// Types inside generic functions -- not supported yet
Expand Down Expand Up @@ -117,7 +117,7 @@ struct OuterGenericStruct<A> {
}

func middleFunction() {
struct ConformingType : Racoon {
struct ConformingType : Racoon { // expected-error{{type 'ConformingType' does not conform to protocol 'Racoon'}}
// expected-error@-1 {{type 'ConformingType' cannot be nested in generic function 'middleFunction()'}}
typealias Stripes = A
}
Expand Down
31 changes: 31 additions & 0 deletions test/decl/protocol/req/associated_type_inference.swift
Original file line number Diff line number Diff line change
Expand Up @@ -469,3 +469,34 @@ protocol P17 {
protocol Q17 : P17 where T == Int { }

struct S17 : Q17 { }

// Typealiases from protocol extensions should not inhibit associated type
// inference.
protocol P18 {
associatedtype A
}

protocol P19 : P18 {
associatedtype B
}

extension P18 where Self: P19 {
typealias A = B
}

struct X18<A> : P18 { }

// rdar://problem/16316115
protocol HasAssoc {
associatedtype Assoc
}

struct DefaultAssoc {}

protocol RefinesAssocWithDefault: HasAssoc {
associatedtype Assoc = DefaultAssoc
}

struct Foo: RefinesAssocWithDefault {
}

13 changes: 13 additions & 0 deletions validation-test/compiler_crashers_2_fixed/0138-rdar36453271.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// RUN: %target-swift-frontend %s -emit-ir -o 0

extension Slice where Base == UnsafeBufferPointer<UInt16> {
var rebased: UnsafeBufferPointer<UInt16> {
return UnsafeBufferPointer(rebasing: self)
}
}

extension Slice where Base == UnsafeMutableBufferPointer<UInt16> {
var rebased: UnsafeMutableBufferPointer<UInt16> {
return UnsafeMutableBufferPointer(rebasing: self)
}
}
8 changes: 8 additions & 0 deletions validation-test/compiler_crashers_2_fixed/0140-sr6746.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// RUN: not %target-swift-frontend %s -typecheck
struct Foo: Strideable {
// typealias Stride = Int
let x: Int

func distance(to other: Foo) -> Foo.Stride { return abs(other.x - x) }
func advanced(by n: Foo.Stride) -> Foo { return Foo(x: x + n) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors

// REQUIRES: asserts
// RUN: not --crash %target-swift-frontend %s -emit-ir

// RUN: not %target-swift-frontend %s -emit-ir
protocol P{{}func a{}typealias e}struct A:P{typealias e:Self.a{}typealias e:Self.a{}typealias e:P
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors

// REQUIRES: asserts
// RUN: not --crash %target-swift-frontend %s -emit-ir

// RUN: not %target-swift-frontend %s -emit-ir
@objc protocol A
{
typealias a
Expand Down