Skip to content

[4.1] Validate class constraints for generic arguments of types #14178

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
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
4 changes: 4 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -1405,6 +1405,10 @@ NOTE(candidate_types_inheritance_requirement,none,
(Type, Type, Type, Type, StringRef))
NOTE(types_not_equal_requirement,none,
"requirement specified as %0 == %1%2", (Type, Type, StringRef))
ERROR(type_is_not_a_class,none,
"%0 requires that %1 be a class type", (Type, Type, Type))
NOTE(anyobject_requirement,none,
"requirement specified as %0 : 'AnyObject'%2", (Type, Type, StringRef))
ERROR(non_class_cannot_conform_to_class_protocol,none,
"non-class type %0 cannot conform to class protocol %1",
(Type, Type))
Expand Down
10 changes: 10 additions & 0 deletions include/swift/AST/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,16 @@ class alignas(1 << TypeAlignInBits) TypeBase {
/// with a class type.
bool mayHaveSuperclass();

/// Determine whether this type satisfies a class layout constraint, written
/// `T: AnyObject` in the source.
///
/// A class layout constraint is satisfied when we have a single retainable
/// pointer as the representation, which includes:
/// - @objc existentials
/// - class constrained archetypes
/// - classes
bool satisfiesClassConstraint();

/// \brief Determine whether this type can be used as a base type for AST
/// name lookup, which is the case for nominal types, protocol compositions
/// and archetypes.
Expand Down
4 changes: 4 additions & 0 deletions lib/AST/Type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1445,6 +1445,10 @@ bool TypeBase::mayHaveSuperclass() {
return is<DynamicSelfType>();
}

bool TypeBase::satisfiesClassConstraint() {
return mayHaveSuperclass() || isObjCExistentialType();
}

Type TypeBase::getSuperclass() {
auto *nominalDecl = getAnyNominal();
auto *classDecl = dyn_cast_or_null<ClassDecl>(nominalDecl);
Expand Down
6 changes: 2 additions & 4 deletions lib/SIL/TypeLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2290,10 +2290,8 @@ TypeConverter::checkForABIDifferences(SILType type1, SILType type2) {
// Classes, class-constrained archetypes, and pure-ObjC existential types
// all have single retainable pointer representation; optionality change
// is allowed.
if ((type1.getSwiftRValueType()->mayHaveSuperclass() ||
type1.getSwiftRValueType()->isObjCExistentialType()) &&
(type2.getSwiftRValueType()->mayHaveSuperclass() ||
type2.getSwiftRValueType()->isObjCExistentialType()))
if (type1.getSwiftRValueType()->satisfiesClassConstraint() &&
type2.getSwiftRValueType()->satisfiesClassConstraint())
return ABIDifference::Trivial;

// Function parameters are ABI compatible if their differences are
Expand Down
9 changes: 1 addition & 8 deletions lib/Sema/CSSimplify.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1329,14 +1329,7 @@ ConstraintSystem::matchExistentialTypes(Type type1, Type type2,
if (auto layoutConstraint = layout.getLayoutConstraint()) {
if (layoutConstraint->isClass()) {
if (kind == ConstraintKind::ConformsTo) {
// Conformance to AnyObject is defined by having a single
// retainable pointer representation:
//
// - @objc existentials
// - class constrained archetypes
// - classes
if (!type1->isObjCExistentialType() &&
!type1->mayHaveSuperclass())
if (!type1->satisfiesClassConstraint())
return SolutionKind::Error;
} else {
// Subtype relation to AnyObject also allows class-bound
Expand Down
4 changes: 1 addition & 3 deletions lib/Sema/TypeCheckConstraints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2827,9 +2827,7 @@ bool TypeChecker::checkedCastMaySucceed(Type t1, Type t2, DeclContext *dc) {

bool TypeChecker::isSubstitutableFor(Type type, ArchetypeType *archetype,
DeclContext *dc) {
if (archetype->requiresClass() &&
!type->mayHaveSuperclass() &&
!type->isObjCExistentialType())
if (archetype->requiresClass() && !type->satisfiesClassConstraint())
return false;

if (auto superclass = archetype->getSuperclass()) {
Expand Down
18 changes: 12 additions & 6 deletions lib/Sema/TypeCheckGeneric.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,8 @@ TypeChecker::gatherGenericParamBindingsText(
TypeSubstitutionFn substitutions) {
llvm::SmallPtrSet<GenericTypeParamType *, 2> knownGenericParams;
for (auto type : types) {
if (type.isNull()) continue;

type.visit([&](Type type) {
if (auto gp = type->getAs<GenericTypeParamType>()) {
knownGenericParams.insert(
Expand Down Expand Up @@ -1373,12 +1375,16 @@ RequirementCheckResult TypeChecker::checkGenericArguments(
break;
}

case RequirementKind::Layout: {
// TODO: Statically check if a the first type
// conforms to the layout constraint, once we
// support such static checks.
continue;
}
case RequirementKind::Layout:
// TODO: Statically check other layout constraints, once they can
// be spelled in Swift.
if (req.getLayoutConstraint()->isClass() &&
!firstType->satisfiesClassConstraint()) {
diagnostic = diag::type_is_not_a_class;
diagnosticNote = diag::anyobject_requirement;
requirementFailure = true;
}
break;

case RequirementKind::Superclass:
// Superclass requirements.
Expand Down
8 changes: 3 additions & 5 deletions lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2692,11 +2692,9 @@ CheckTypeWitnessResult swift::checkTypeWitness(TypeChecker &tc, DeclContext *dc,
}
}

if (genericSig->requiresClass(depTy)) {
if (!contextType->isObjCExistentialType() &&
!contextType->mayHaveSuperclass())
return CheckTypeWitnessResult(tc.Context.getAnyObjectType());
}
if (genericSig->requiresClass(depTy) &&
!contextType->satisfiesClassConstraint())
return CheckTypeWitnessResult(tc.Context.getAnyObjectType());

// Success!
return CheckTypeWitnessResult();
Expand Down
3 changes: 1 addition & 2 deletions lib/Sema/TypeCheckType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,7 @@ TypeChecker::getDynamicBridgedThroughObjCClass(DeclContext *dc,
Type dynamicType,
Type valueType) {
// We can only bridge from class or Objective-C existential types.
if (!dynamicType->isObjCExistentialType() &&
!dynamicType->mayHaveSuperclass())
if (!dynamicType->satisfiesClassConstraint())
return Type();

// If the value type cannot be bridged, we're done.
Expand Down
4 changes: 2 additions & 2 deletions test/Constraints/diagnostics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,8 @@ String().asdf // expected-error {{value of type 'String' has no member 'asdf'}}

// <rdar://problem/21553065> Spurious diagnostic: '_' can only appear in a pattern or on the left side of an assignment
protocol r21553065Protocol {}
class r21553065Class<T : AnyObject> {}
_ = r21553065Class<r21553065Protocol>() // expected-error {{'r21553065Protocol' is not convertible to 'AnyObject'}}
class r21553065Class<T : AnyObject> {} // expected-note{{requirement specified as 'T' : 'AnyObject'}}
_ = r21553065Class<r21553065Protocol>() // expected-error {{'r21553065Class' requires that 'r21553065Protocol' be a class type}}

// Type variables not getting erased with nested closures
struct Toe {
Expand Down
6 changes: 4 additions & 2 deletions test/Constraints/generics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,9 @@ protocol SubProto: BaseProto {}
struct FullyGeneric<Foo> {} // expected-note 11 {{'Foo' declared as parameter to type 'FullyGeneric'}} expected-note 1 {{generic type 'FullyGeneric' declared here}}

struct AnyClassBound<Foo: AnyObject> {} // expected-note {{'Foo' declared as parameter to type 'AnyClassBound'}} expected-note {{generic type 'AnyClassBound' declared here}}
// expected-note@-1{{requirement specified as 'Foo' : 'AnyObject'}}
struct AnyClassBound2<Foo> where Foo: AnyObject {} // expected-note {{'Foo' declared as parameter to type 'AnyClassBound2'}}
// expected-note@-1{{requirement specified as 'Foo' : 'AnyObject' [with Foo = Any]}}

struct ProtoBound<Foo: SubProto> {} // expected-note {{'Foo' declared as parameter to type 'ProtoBound'}} expected-note {{generic type 'ProtoBound' declared here}}
struct ProtoBound2<Foo> where Foo: SubProto {} // expected-note {{'Foo' declared as parameter to type 'ProtoBound2'}}
Expand Down Expand Up @@ -301,11 +303,11 @@ func testFixIts() {
_ = FullyGeneric<Any>()

_ = AnyClassBound() // expected-error {{generic parameter 'Foo' could not be inferred}} expected-note {{explicitly specify the generic arguments to fix this issue}} {{20-20=<AnyObject>}}
_ = AnyClassBound<Any>() // expected-error {{'Any' is not convertible to 'AnyObject'}}
_ = AnyClassBound<Any>() // expected-error {{'AnyClassBound' requires that 'Any' be a class type}}
_ = AnyClassBound<AnyObject>()

_ = AnyClassBound2() // expected-error {{generic parameter 'Foo' could not be inferred}} expected-note {{explicitly specify the generic arguments to fix this issue}} {{21-21=<AnyObject>}}
_ = AnyClassBound2<Any>() // expected-error {{'Any' is not convertible to 'AnyObject'}}
_ = AnyClassBound2<Any>() // expected-error {{'AnyClassBound2' requires that 'Any' be a class type}}
_ = AnyClassBound2<AnyObject>()

_ = ProtoBound() // expected-error {{generic parameter 'Foo' could not be inferred}} expected-note {{explicitly specify the generic arguments to fix this issue}} {{17-17=<<#Foo: SubProto#>>}}
Expand Down
27 changes: 27 additions & 0 deletions test/Generics/class_constraint.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// RUN: %target-typecheck-verify-swift

struct X<T: AnyObject> { } // expected-note 4{{requirement specified as 'T' : 'AnyObject'}}

class C { }
struct S { }

protocol P { }

let okay0: X<C>

struct Y<T: AnyObject> {
let okay1: X<T>
}

struct Y2<T: C> {
let okay2: X<T>
}

let bad0: X<C & P> // expected-error{{'X' requires that 'C & P' be a class type}}
let bad1: X<P> // expected-error{{'X' requires that 'P' be a class type}}
let bad2: X<S> // expected-error{{'X' requires that 'S' be a class type}}

struct Z<U> {
let bad3: X<U> // expected-error{{'X' requires that 'U' be a class type}}
}

6 changes: 3 additions & 3 deletions test/Generics/existential_restrictions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,17 @@ class GP<T : P> {}
class GOP<T : OP> {}
class GCP<T : CP> {}
class GSP<T : SP> {}
class GAO<T : AnyObject> {}
class GAO<T : AnyObject> {} // expected-note 2{{requirement specified as 'T' : 'AnyObject'}}

func blackHole(_ t: Any) {}

func testBindExistential() {
blackHole(GP<P>()) // expected-error{{using 'P' as a concrete type conforming to protocol 'P' is not supported}}
blackHole(GOP<OP>())
blackHole(GCP<CP>()) // expected-error{{using 'CP' as a concrete type conforming to protocol 'CP' is not supported}}
blackHole(GAO<P>()) // expected-error{{'P' is not convertible to 'AnyObject'}}
blackHole(GAO<P>()) // expected-error{{'GAO' requires that 'P' be a class type}}
blackHole(GAO<OP>())
blackHole(GAO<CP>()) // expected-error{{'CP' is not convertible to 'AnyObject'}}
blackHole(GAO<CP>()) // expected-error{{'GAO' requires that 'CP' be a class type}}
blackHole(GSP<SP>()) // expected-error{{'SP' cannot be used as a type conforming to protocol 'SP' because 'SP' has static requirements}}
blackHole(GAO<AnyObject>())
}
Expand Down