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] Allow extending specialized types #39307

Closed
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -1886,9 +1886,8 @@ ERROR(extension_access_with_conformances,none,
"protocol conformances", (DeclAttribute))
ERROR(extension_metatype,none,
"cannot extend a metatype %0", (Type))
ERROR(extension_specialization,none,
"constrained extension must be declared on the unspecialized generic "
"type %0 with constraints specified by a 'where' clause", (Identifier))
ERROR(extension_placeholder,none,
"cannot extend a type that contains placeholders", ())
ERROR(extension_stored_property,none,
"extensions must not contain stored properties", ())
NOTE(extension_stored_property_fixit,none,
Expand Down
97 changes: 8 additions & 89 deletions lib/Sema/TypeCheckDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2661,67 +2661,6 @@ AllMembersRequest::evaluate(
return evaluateMembersRequest(idc, MembersRequestKind::All);
}

bool TypeChecker::isPassThroughTypealias(TypeAliasDecl *typealias,
Type underlyingType,
NominalTypeDecl *nominal) {
// Pass-through only makes sense when the typealias refers to a nominal
// type.
if (!nominal) return false;

// Check that the nominal type and the typealias are either both generic
// at this level or neither are.
if (nominal->isGeneric() != typealias->isGeneric())
return false;

// Make sure either both have generic signatures or neither do.
auto nominalSig = nominal->getGenericSignature();
auto typealiasSig = typealias->getGenericSignature();
if (static_cast<bool>(nominalSig) != static_cast<bool>(typealiasSig))
return false;

// If neither is generic, we're done: it's a pass-through alias.
if (!nominalSig) return true;

// Check that the type parameters are the same the whole way through.
auto nominalGenericParams = nominalSig.getGenericParams();
auto typealiasGenericParams = typealiasSig.getGenericParams();
if (nominalGenericParams.size() != typealiasGenericParams.size())
return false;
if (!std::equal(nominalGenericParams.begin(), nominalGenericParams.end(),
typealiasGenericParams.begin(),
[](GenericTypeParamType *gp1, GenericTypeParamType *gp2) {
return gp1->isEqual(gp2);
}))
return false;

// If neither is generic at this level, we have a pass-through typealias.
if (!typealias->isGeneric()) return true;

auto boundGenericType = underlyingType->getAs<BoundGenericType>();
if (!boundGenericType) return false;

// If our arguments line up with our innermost generic parameters, it's
// a passthrough typealias.
auto innermostGenericParams = typealiasSig.getInnermostGenericParams();
auto boundArgs = boundGenericType->getGenericArgs();
if (boundArgs.size() != innermostGenericParams.size())
return false;

return std::equal(boundArgs.begin(), boundArgs.end(),
innermostGenericParams.begin(),
[](Type arg, GenericTypeParamType *gp) {
return arg->isEqual(gp);
});
}

static bool isNonGenericTypeAliasType(Type type) {
// A non-generic typealias can extend a specialized type.
if (auto *aliasType = dyn_cast<TypeAliasType>(type.getPointer()))
return aliasType->getDecl()->getGenericContextDepth() == (unsigned)-1;

return false;
}

Type
ExtendedTypeRequest::evaluate(Evaluator &eval, ExtensionDecl *ext) const {
auto error = [&ext]() {
Expand Down Expand Up @@ -2756,28 +2695,11 @@ ExtendedTypeRequest::evaluate(Evaluator &eval, ExtensionDecl *ext) const {
if (extendedType->hasError())
return error();

// Hack to allow extending a generic typealias.
if (auto *unboundGeneric = extendedType->getAs<UnboundGenericType>()) {
if (auto *aliasDecl = dyn_cast<TypeAliasDecl>(unboundGeneric->getDecl())) {
// Nested Hack to break cycles if this is called before validation has
// finished.
if (aliasDecl->hasInterfaceType()) {
auto extendedNominal =
aliasDecl->getDeclaredInterfaceType()->getAnyNominal();
if (extendedNominal)
return TypeChecker::isPassThroughTypealias(
aliasDecl, aliasDecl->getUnderlyingType(), extendedNominal)
? extendedType
: extendedNominal->getDeclaredType();
} else {
if (auto ty = aliasDecl->getStructuralType()
->getAs<NominalOrBoundGenericNominalType>())
return TypeChecker::isPassThroughTypealias(aliasDecl, ty,
ty->getDecl())
? extendedType
: ty->getDecl()->getDeclaredType();
}
}
// If we're an unbound generic typealias, we may not have a nominal at this
// time, so go ahead and return the extended type. This will fail the
// non-nominal check even though we're a typealias.
if (extendedType->is<UnboundGenericType>()) {
return extendedType;
}

auto &diags = ext->getASTContext().Diags;
Expand All @@ -2796,12 +2718,9 @@ ExtendedTypeRequest::evaluate(Evaluator &eval, ExtensionDecl *ext) const {
return error();
}

// Cannot extend a bound generic type, unless it's referenced via a
// non-generic typealias type.
if (extendedType->isSpecialized() &&
!isNonGenericTypeAliasType(extendedType)) {
diags.diagnose(ext->getLoc(), diag::extension_specialization,
extendedType->getAnyNominal()->getName())
// Cannot extend types who contain placeholders.
if (extendedType->hasPlaceholder()) {
diags.diagnose(ext->getLoc(), diag::extension_placeholder)
.highlight(extendedRepr->getSourceRange());
return error();
}
Expand Down
8 changes: 3 additions & 5 deletions lib/Sema/TypeCheckGeneric.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -587,8 +587,8 @@ static Type formExtensionInterfaceType(
auto nominal = dyn_cast<NominalTypeDecl>(genericDecl);
auto typealias = dyn_cast<TypeAliasDecl>(genericDecl);
if (!nominal) {
Type underlyingType = typealias->getUnderlyingType();
nominal = underlyingType->getNominalOrBoundGenericNominal();
type = typealias->getUnderlyingType();
nominal = type->getNominalOrBoundGenericNominal();
}

// Form the result.
Expand Down Expand Up @@ -618,8 +618,7 @@ static Type formExtensionInterfaceType(
}

// If we have a typealias, try to form type sugar.
if (typealias && TypeChecker::isPassThroughTypealias(
Azoy marked this conversation as resolved.
Show resolved Hide resolved
typealias, typealias->getUnderlyingType(), nominal)) {
if (typealias) {
auto typealiasSig = typealias->getGenericSignature();
SubstitutionMap subMap;
if (typealiasSig) {
Expand All @@ -631,7 +630,6 @@ static Type formExtensionInterfaceType(
resultType = TypeAliasType::get(typealias, parentType, subMap, resultType);
}


return resultType;
}

Expand Down
21 changes: 0 additions & 21 deletions lib/Sema/TypeChecker.h
Original file line number Diff line number Diff line change
Expand Up @@ -254,27 +254,6 @@ Expr *resolveDeclRefExpr(UnresolvedDeclRefExpr *UDRE, DeclContext *Context,
Type substMemberTypeWithBase(ModuleDecl *module, TypeDecl *member, Type baseTy,
bool useArchetypes = true);

/// Determine whether this is a "pass-through" typealias, which has the
/// same type parameters as the nominal type it references and specializes
/// the underlying nominal type with exactly those type parameters.
/// For example, the following typealias \c GX is a pass-through typealias:
///
/// \code
/// struct X<T, U> { }
/// typealias GX<A, B> = X<A, B>
/// \endcode
///
/// whereas \c GX2 and \c GX3 are not pass-through because \c GX2 has
/// different type parameters and \c GX3 doesn't pass its type parameters
/// directly through.
///
/// \code
/// typealias GX2<A> = X<A, A>
/// typealias GX3<A, B> = X<B, A>
/// \endcode
bool isPassThroughTypealias(TypeAliasDecl *typealias, Type underlyingType,
NominalTypeDecl *nominal);

/// Determine whether one type is a subtype of another.
///
/// \param t1 The potential subtype.
Expand Down
17 changes: 16 additions & 1 deletion test/Generics/requirement_inference.swift
Original file line number Diff line number Diff line change
Expand Up @@ -504,10 +504,25 @@ extension X1WithP2Changed {

extension X1WithP2MoreArgs {
func bad2() {
_ = X5<T>() // expected-error{{type 'T' does not conform to protocol 'P2'}}
_ = X5<T>()
}
}

protocol ReallyBadProto1 {}
protocol ReallyBadProto2 {}

struct ReallyBadStruct1<T: ReallyBadProto1> {}
typealias ReallyBadTypealias1<T: ReallyBadProto1> = ReallyBadStruct1<T> where T: ReallyBadProto2

extension ReallyBadTypealias1 { // expected-note {{where 'T' = 'ReallyBadProto1Conformer'}}
func hello() {}
}

struct ReallyBadProto1Conformer: ReallyBadProto1 {}

let x = ReallyBadStruct1<ReallyBadProto1Conformer>()
x.hello() // expected-error {{referencing instance method 'hello()' on 'ReallyBadStruct1' requires that 'ReallyBadProto1Conformer' conform to 'ReallyBadProto2'}}

// Inference from protocol inheritance clauses is not allowed.
typealias ExistentialP4WithP2Assoc<T: P4> = P4 where T.P4Assoc : P2

Expand Down
7 changes: 7 additions & 0 deletions test/SILGen/mangling_generic_extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,10 @@ extension Bar {
// CHECK-LABEL: $s27mangling_generic_extensions3BarV4bar1yyqd__AA8RuncibleRd__AaE5SpoonRp_lF
func bar1<V: Runcible>(_: V) where U.Spoon: Runcible { }
}

// Extending specialized types should use the same mangling as same type constraints

extension Foo<Int> {
// CHECK-LABEL: sil hidden [ossa] @$s27mangling_generic_extensions3FooVAASiRszlE013someIntFuncOnD0yyF
func someIntFuncOnFoo() {}
}
3 changes: 2 additions & 1 deletion test/decl/ext/extensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,8 @@ extension Tree.LimbContent.Contents {
}

extension Tree.BoughPayload.Contents {
// expected-error@-1 {{constrained extension must be declared on the unspecialized generic type 'Nest'}}
// expected-error@-1 {{extension of type 'Tree.BoughPayload.Contents' (aka 'Nest<Int>') must be declared as an extension of 'Nest<Int>'}}
// expected-note@-2 {{did you mean to extend 'Nest<Int>' instead?}}
}

// SR-10466 Check 'where' clause when referencing type defined inside extension
Expand Down
37 changes: 35 additions & 2 deletions test/decl/ext/generic.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@ extension Double : P2 {
}

extension X<Int, Double, String> {
// expected-error@-1{{constrained extension must be declared on the unspecialized generic type 'X' with constraints specified by a 'where' clause}}
let x = 0
// expected-error@-1 {{extensions must not contain stored properties}}
static let x = 0
// expected-error@-1 {{static stored properties not supported in generic types}}

func f() -> Int {}
class C<T> {}
}
Expand Down Expand Up @@ -224,3 +223,37 @@ extension NewGeneric {
return NewGeneric()
}
}

// Extending specialized types

extension Array<Int> {
func someIntFuncOnArray() {}
}

let _ = [0, 1, 2].someIntFuncOnArray()

extension [Character] {
func makeString() -> String { fatalError() }
}

let _ = ["a", "b", "c"].makeString()

extension Set<_> {} // expected-error {{cannot extend a type that contains placeholders}}

// https://bugs.swift.org/browse/SR-4875

struct Foo<T, U> {
var x: T
var y: U
}

typealias IntFoo<U> = Foo<Int, U>

extension IntFoo where U == Int {
func hello() {
print("hello")
}
}

Foo(x: "test", y: 1).hello() // expected-error {{cannot convert value of type 'String' to expected argument type 'Int'}}

4 changes: 2 additions & 2 deletions test/decl/typealias/generic.swift
Original file line number Diff line number Diff line change
Expand Up @@ -302,9 +302,9 @@ func takesSugaredType2(m: GenericClass<Int>.TA<Float>) {
extension A {}

extension A<T> {} // expected-error {{generic type 'A' specialized with too few type parameters (got 1, but expected 2)}}
extension A<Float,Int> {} // expected-error {{constrained extension must be declared on the unspecialized generic type 'MyType' with constraints specified by a 'where' clause}}
extension A<Float,Int> {} // ok
extension C<T> {} // expected-error {{cannot find type 'T' in scope}}
extension C<Int> {} // expected-error {{constrained extension must be declared on the unspecialized generic type 'MyType' with constraints specified by a 'where' clause}}
extension C<Int> {} // ok


protocol ErrorQ {
Expand Down