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

[Sema] Pare down operator candidates during protocol type checking #18831

Merged
merged 1 commit into from Aug 29, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
42 changes: 27 additions & 15 deletions lib/Sema/TypeCheckDecl.cpp
Expand Up @@ -3554,38 +3554,44 @@ void bindFuncDeclToOperator(TypeChecker &TC, FuncDecl *FD) {
FD->setOperatorDecl(op);
}

void checkMemberOperator(TypeChecker &TC, FuncDecl *FD) {
bool swift::isMemberOperator(FuncDecl *decl, Type type) {
// Check that member operators reference the type of 'Self'.
if (FD->isInvalid()) return;
if (decl->isInvalid())
return true;

auto *DC = FD->getDeclContext();
auto *DC = decl->getDeclContext();
auto selfNominal = DC->getSelfNominalTypeDecl();
if (!selfNominal) return;

// Check the parameters for a reference to 'Self'.
bool isProtocol = isa<ProtocolDecl>(selfNominal);
for (auto param : *FD->getParameters()) {
bool isProtocol = selfNominal && isa<ProtocolDecl>(selfNominal);
for (auto param : *decl->getParameters()) {
auto paramType = param->getInterfaceType();
if (!paramType) break;

// Look through a metatype reference, if there is one.
paramType = paramType->getMetatypeInstanceType();

// Is it the same nominal type?
if (paramType->getAnyNominal() == selfNominal) return;
auto nominal = paramType->getAnyNominal();
if (type.isNull()) {
// Is it the same nominal type?
if (selfNominal && nominal == selfNominal)
return true;
} else {
// Is it the same nominal type? Or a generic (which may or may not match)?
if (paramType->is<GenericTypeParamType>() ||
nominal == type->getAnyNominal())
return true;
}

if (isProtocol) {
// For a protocol, is it the 'Self' type parameter?
if (auto genericParam = paramType->getAs<GenericTypeParamType>())
if (genericParam->isEqual(DC->getSelfInterfaceType()))
return;
return true;
}
}

// We did not find 'Self'. Complain.
TC.diagnose(FD, diag::operator_in_unrelated_type,
FD->getDeclContext()->getDeclaredInterfaceType(),
isProtocol, FD->getFullName());
return false;
}

bool checkDynamicSelfReturn(FuncDecl *func,
Expand Down Expand Up @@ -4174,8 +4180,14 @@ void TypeChecker::validateDecl(ValueDecl *D) {

// Member functions need some special validation logic.
if (FD->getDeclContext()->isTypeContext()) {
if (FD->isOperator())
checkMemberOperator(*this, FD);
if (FD->isOperator() && !isMemberOperator(FD, nullptr)) {
auto selfNominal = FD->getDeclContext()->getSelfNominalTypeDecl();
auto isProtocol = selfNominal && isa<ProtocolDecl>(selfNominal);
// We did not find 'Self'. Complain.
diagnose(FD, diag::operator_in_unrelated_type,
FD->getDeclContext()->getDeclaredInterfaceType(), isProtocol,
FD->getFullName());
}

auto accessor = dyn_cast<AccessorDecl>(FD);

Expand Down
7 changes: 5 additions & 2 deletions lib/Sema/TypeCheckProtocol.cpp
Expand Up @@ -930,7 +930,7 @@ WitnessChecker::lookupValueWitnessesViaImplementsAttr(
}
}

SmallVector<ValueDecl *, 4>
SmallVector<ValueDecl *, 4>
WitnessChecker::lookupValueWitnesses(ValueDecl *req, bool *ignoringNames) {
assert(!isa<AssociatedTypeDecl>(req) && "Not for lookup for type witnesses*");

Expand All @@ -950,7 +950,10 @@ WitnessChecker::lookupValueWitnesses(ValueDecl *req, bool *ignoringNames) {
SourceLoc(),
lookupOptions);
for (auto candidate : lookup) {
witnesses.push_back(candidate.getValueDecl());
auto decl = candidate.getValueDecl();
if (swift::isMemberOperator(cast<FuncDecl>(decl), Adoptee)) {
witnesses.push_back(decl);
}
}
} else {
// Variable/function/subscript requirements.
Expand Down
3 changes: 3 additions & 0 deletions lib/Sema/TypeCheckProtocol.h
Expand Up @@ -33,6 +33,7 @@ class AccessScope;
class AssociatedTypeDecl;
class AvailabilityContext;
class DeclContext;
class FuncDecl;
class NormalProtocolConformance;
class ProtocolDecl;
class TypeChecker;
Expand Down Expand Up @@ -452,6 +453,8 @@ class WitnessChecker {
WitnessChecker(TypeChecker &tc, ProtocolDecl *proto,
Type adoptee, DeclContext *dc);

bool isMemberOperator(FuncDecl *decl, Type type);

/// Gather the value witnesses for the given requirement.
///
/// \param ignoringNames If non-null and there are no value
Expand Down
6 changes: 3 additions & 3 deletions lib/Sema/TypeCheckProtocolInference.cpp
Expand Up @@ -230,8 +230,8 @@ AssociatedTypeInference::inferTypeWitnessesViaValueWitnesses(
auto typeInContext =
conformance->getDeclContext()->mapTypeIntoContext(conformance->getType());

for (auto witness : checker.lookupValueWitnesses(req,
/*ignoringNames=*/nullptr)) {
for (auto witness :
checker.lookupValueWitnesses(req, /*ignoringNames=*/nullptr)) {
LLVM_DEBUG(llvm::dbgs() << "Inferring associated types from decl:\n";
witness->dump(llvm::dbgs()));

Expand Down Expand Up @@ -399,7 +399,7 @@ AssociatedTypeInference::inferTypeWitnessesViaValueWitnesses(

result.push_back(std::move(witnessResult));
next_witness:;
}
}

return result;
}
Expand Down
5 changes: 5 additions & 0 deletions lib/Sema/TypeChecker.h
Expand Up @@ -2209,6 +2209,11 @@ Type getMemberTypeForComparison(ASTContext &ctx, ValueDecl *member,
bool isOverrideBasedOnType(ValueDecl *decl, Type declTy,
ValueDecl *parentDecl, Type parentDeclTy);

/// Determine whether the given declaration is an operator defined in a
/// protocol. If \p type is not null, check specifically whether \p decl
/// could fulfill a protocol requirement for it.
bool isMemberOperator(FuncDecl *decl, Type type);

} // end namespace swift

#endif
4 changes: 0 additions & 4 deletions test/Compatibility/accessibility.swift
Expand Up @@ -641,12 +641,10 @@ fileprivate struct EquatablishOuter {
internal struct Inner : Equatablish {}
}
private func ==(lhs: EquatablishOuter.Inner, rhs: EquatablishOuter.Inner) {}
// expected-note@-1 {{candidate has non-matching type}}

fileprivate struct EquatablishOuter2 {
internal struct Inner : Equatablish {
fileprivate static func ==(lhs: Inner, rhs: Inner) {}
// expected-note@-1 {{candidate has non-matching type}}
}
}

Expand All @@ -660,7 +658,6 @@ internal struct EquatablishOuterProblem2 {
public struct Inner : Equatablish {
fileprivate static func ==(lhs: Inner, rhs: Inner) {} // expected-error {{method '==' must be as accessible as its enclosing type because it matches a requirement in protocol 'Equatablish'}} {{none}}
// expected-note@-1 {{mark the operator function as 'internal' to satisfy the requirement}} {{5-16=internal}}
// expected-note@-2 {{candidate has non-matching type}}
}
}

Expand All @@ -671,7 +668,6 @@ internal struct EquatablishOuterProblem3 {
}
private func ==(lhs: EquatablishOuterProblem3.Inner, rhs: EquatablishOuterProblem3.Inner) {}
// expected-note@-1 {{mark the operator function as 'internal' to satisfy the requirement}} {{1-8=internal}}
// expected-note@-2 {{candidate has non-matching type}}


public protocol AssocTypeProto {
Expand Down
4 changes: 0 additions & 4 deletions test/Sema/accessibility.swift
Expand Up @@ -650,12 +650,10 @@ fileprivate struct EquatablishOuter {
internal struct Inner : Equatablish {}
}
private func ==(lhs: EquatablishOuter.Inner, rhs: EquatablishOuter.Inner) {}
// expected-note@-1 {{candidate has non-matching type}}

fileprivate struct EquatablishOuter2 {
internal struct Inner : Equatablish {
fileprivate static func ==(lhs: Inner, rhs: Inner) {}
// expected-note@-1 {{candidate has non-matching type}}
}
}

Expand All @@ -669,7 +667,6 @@ internal struct EquatablishOuterProblem2 {
public struct Inner : Equatablish {
fileprivate static func ==(lhs: Inner, rhs: Inner) {} // expected-error {{method '==' must be as accessible as its enclosing type because it matches a requirement in protocol 'Equatablish'}} {{none}}
// expected-note@-1 {{mark the operator function as 'internal' to satisfy the requirement}} {{5-16=internal}}
// expected-note@-2 {{candidate has non-matching type}}
}
}

Expand All @@ -679,7 +676,6 @@ internal struct EquatablishOuterProblem3 {
}
private func ==(lhs: EquatablishOuterProblem3.Inner, rhs: EquatablishOuterProblem3.Inner) {}
// expected-note@-1 {{mark the operator function as 'internal' to satisfy the requirement}} {{1-8=internal}}
// expected-note@-2 {{candidate has non-matching type}}


public protocol AssocTypeProto {
Expand Down
8 changes: 4 additions & 4 deletions test/Sema/enum_conformance_synthesis.swift
Expand Up @@ -45,7 +45,7 @@ enum CustomHashable {

var hashValue: Int { return 0 }
}
func ==(x: CustomHashable, y: CustomHashable) -> Bool { // expected-note 4 {{non-matching type}}
func ==(x: CustomHashable, y: CustomHashable) -> Bool {
return true
}

Expand All @@ -63,7 +63,7 @@ enum InvalidCustomHashable {

var hashValue: String { return "" } // expected-note{{previously declared here}}
}
func ==(x: InvalidCustomHashable, y: InvalidCustomHashable) -> String { // expected-note 4 {{non-matching type}}
func ==(x: InvalidCustomHashable, y: InvalidCustomHashable) -> String {
return ""
}
func invalidCustomHashable() {
Expand Down Expand Up @@ -213,7 +213,7 @@ public enum Medicine {

extension Medicine : Equatable {}

public func ==(lhs: Medicine, rhs: Medicine) -> Bool { // expected-note 4 {{non-matching type}}
public func ==(lhs: Medicine, rhs: Medicine) -> Bool {
return true
}

Expand All @@ -236,7 +236,7 @@ extension NotExplicitlyHashableAndCannotDerive : CaseIterable {} // expected-err
// Verify that conformance (albeit manually implemented) can still be added to
// a type in a different file.
extension OtherFileNonconforming: Hashable {
static func ==(lhs: OtherFileNonconforming, rhs: OtherFileNonconforming) -> Bool { // expected-note 4 {{non-matching type}}
static func ==(lhs: OtherFileNonconforming, rhs: OtherFileNonconforming) -> Bool {
return true
}
var hashValue: Int { return 0 }
Expand Down
10 changes: 5 additions & 5 deletions test/Sema/struct_equatable_hashable.swift
Expand Up @@ -50,7 +50,7 @@ struct CustomHashValue: Hashable {

var hashValue: Int { return 0 }

static func ==(x: CustomHashValue, y: CustomHashValue) -> Bool { return true } // expected-note 3 {{non-matching type}}
static func ==(x: CustomHashValue, y: CustomHashValue) -> Bool { return true }
}

func customHashValue() {
Expand All @@ -72,7 +72,7 @@ struct CustomHashInto: Hashable {
hasher.combine(y)
}

static func ==(x: CustomHashInto, y: CustomHashInto) -> Bool { return true } // expected-note 3 {{non-matching type}}
static func ==(x: CustomHashInto, y: CustomHashInto) -> Bool { return true }
}

func customHashInto() {
Expand Down Expand Up @@ -172,9 +172,9 @@ public struct StructConformsAndImplementsInExtension {
let v: Int
}
extension StructConformsAndImplementsInExtension : Equatable {
public static func ==(lhs: StructConformsAndImplementsInExtension, rhs: StructConformsAndImplementsInExtension) -> Bool { // expected-note 3 {{non-matching type}}
public static func ==(lhs: StructConformsAndImplementsInExtension, rhs: StructConformsAndImplementsInExtension) -> Bool {
return true
}
}
}

// No explicit conformance and it cannot be derived.
Expand All @@ -189,7 +189,7 @@ struct NoStoredProperties: Hashable {}
// Verify that conformance (albeit manually implemented) can still be added to
// a type in a different file.
extension OtherFileNonconforming: Hashable {
static func ==(lhs: OtherFileNonconforming, rhs: OtherFileNonconforming) -> Bool { // expected-note 3 {{non-matching type}}
static func ==(lhs: OtherFileNonconforming, rhs: OtherFileNonconforming) -> Bool {
return true
}
var hashValue: Int { return 0 }
Expand Down
15 changes: 11 additions & 4 deletions test/decl/protocol/req/func.swift
Expand Up @@ -96,7 +96,7 @@ struct X3a : P3 {
typealias Assoc = X1a
}

prefix func ~~(_: X3a) -> X1a {} // expected-note{{candidate has non-matching type '(X3a) -> X1a'}} expected-note{{candidate is prefix, not postfix as required}}
prefix func ~~(_: X3a) -> X1a {}

// FIXME: Add example with overloaded prefix/postfix

Expand All @@ -105,7 +105,7 @@ struct X3z : P3 { // expected-error{{type 'X3z' does not conform to protocol 'P3
typealias Assoc = X1a
}

postfix func ~~(_: X3z) -> X1a {} // expected-note{{candidate is postfix, not prefix as required}} expected-note{{candidate has non-matching type '(X3z) -> X1a'}}
postfix func ~~(_: X3z) -> X1a {} // expected-note{{candidate is postfix, not prefix as required}}

// Protocol with postfix unary function
postfix operator ~~
Expand All @@ -119,14 +119,14 @@ struct X4a : P4 {
typealias Assoc = X1a
}

postfix func ~~(_: X4a) -> X1a {} // expected-note{{candidate has non-matching type '(X4a) -> X1a'}} expected-note{{candidate is postfix, not prefix as required}}
postfix func ~~(_: X4a) -> X1a {}

// Prefix/postfix mismatch.
struct X4z : P4 { // expected-error{{type 'X4z' does not conform to protocol 'P4'}}
typealias Assoc = X1a
}

prefix func ~~(_: X4z) -> X1a {} // expected-note{{candidate has non-matching type '(X4z) -> X1a'}} expected-note{{candidate is prefix, not postfix as required}}
prefix func ~~(_: X4z) -> X1a {} // expected-note{{candidate is prefix, not postfix as required}}

// Objective-C protocol
@objc protocol P5 {
Expand Down Expand Up @@ -270,3 +270,10 @@ struct X12 : P12 { // expected-error{{type 'X12' does not conform to protocol 'P
}

func ==(x: X12.Index, y: X12.Index) -> Bool { return true }

protocol P13 {}
protocol P14 {
static prefix func %%%(_: Self.Type)
}
prefix func %%%<P: P13>(_: P.Type) { }
struct X13: P14, P13 { }