-
Notifications
You must be signed in to change notification settings - Fork 10.5k
Retain type sugar for extension declarations that name generic typealiases #15450
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
Changes from all commits
23b8cd1
240e52e
6707214
0c9fb62
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8222,46 +8222,139 @@ void TypeChecker::validateAccessControl(ValueDecl *D) { | |
assert(D->hasAccess()); | ||
} | ||
|
||
bool swift::isPassThroughTypealias(TypeAliasDecl *typealias) { | ||
// Pass-through only makes sense when the typealias refers to a nominal | ||
// type. | ||
Type underlyingType = typealias->getUnderlyingTypeLoc().getType(); | ||
auto nominal = underlyingType->getAnyNominal(); | ||
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()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Checking the sizes match is redundant with the subsequent std::equal call. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately, it isn't, because There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh man. 🙄🙄 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, there's the variant that takes the second end-iterator too… |
||
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()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. likewise. |
||
return false; | ||
|
||
return std::equal(boundArgs.begin(), boundArgs.end(), | ||
innermostGenericParams.begin(), | ||
[](Type arg, GenericTypeParamType *gp) { | ||
return arg->isEqual(gp); | ||
}); | ||
} | ||
|
||
/// Form the interface type of an extension from the raw type and the | ||
/// extension's list of generic parameters. | ||
static Type formExtensionInterfaceType(Type type, | ||
GenericParamList *genericParams) { | ||
static Type formExtensionInterfaceType(TypeChecker &tc, ExtensionDecl *ext, | ||
Type type, | ||
GenericParamList *genericParams, | ||
bool &mustInferRequirements) { | ||
// Find the nominal type declaration and its parent type. | ||
Type parentType; | ||
NominalTypeDecl *nominal; | ||
GenericTypeDecl *genericDecl; | ||
if (auto unbound = type->getAs<UnboundGenericType>()) { | ||
parentType = unbound->getParent(); | ||
nominal = cast<NominalTypeDecl>(unbound->getDecl()); | ||
genericDecl = unbound->getDecl(); | ||
} else { | ||
if (type->is<ProtocolCompositionType>()) | ||
type = type->getCanonicalType(); | ||
auto nominalType = type->castTo<NominalType>(); | ||
parentType = nominalType->getParent(); | ||
nominal = nominalType->getDecl(); | ||
genericDecl = nominalType->getDecl(); | ||
} | ||
|
||
// Reconstruct the parent, if there is one. | ||
if (parentType) { | ||
// Build the nested extension type. | ||
auto parentGenericParams = nominal->getGenericParams() | ||
auto parentGenericParams = genericDecl->getGenericParams() | ||
? genericParams->getOuterParameters() | ||
: genericParams; | ||
parentType = formExtensionInterfaceType(parentType, parentGenericParams); | ||
parentType = | ||
formExtensionInterfaceType(tc, ext, parentType, parentGenericParams, | ||
mustInferRequirements); | ||
} | ||
|
||
// If we don't have generic parameters at this level, just build the result. | ||
if (!nominal->getGenericParams() || isa<ProtocolDecl>(nominal)) { | ||
return NominalType::get(nominal, parentType, | ||
nominal->getASTContext()); | ||
// Find the nominal type. | ||
auto nominal = dyn_cast<NominalTypeDecl>(genericDecl); | ||
auto typealias = dyn_cast<TypeAliasDecl>(genericDecl); | ||
if (!nominal) { | ||
Type underlyingType = typealias->getUnderlyingTypeLoc().getType(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you sure that genericDecl is either a typealias or a nominaltype decl? If it is possible for it to be neither of those, then this code will crash with a null dereference. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am, because the only forms of type declaration we have in the language are "nominal" and "alias". I suspect that distinction will last "forever", because any new kind of type we invent will either have a name and a runtime presence (nominal) or be some form of alias. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, sounds good. please add an assertion :) |
||
nominal = underlyingType->getNominalOrBoundGenericNominal(); | ||
} | ||
|
||
// Form the bound generic type with the type parameters provided. | ||
// Form the result. | ||
Type resultType; | ||
SmallVector<Type, 2> genericArgs; | ||
for (auto gp : *genericParams) { | ||
genericArgs.push_back(gp->getDeclaredInterfaceType()); | ||
if (!nominal->isGeneric() || isa<ProtocolDecl>(nominal)) { | ||
resultType = NominalType::get(nominal, parentType, | ||
nominal->getASTContext()); | ||
} else { | ||
// Form the bound generic type with the type parameters provided. | ||
for (auto gp : *genericParams) { | ||
genericArgs.push_back(gp->getDeclaredInterfaceType()); | ||
} | ||
|
||
resultType = BoundGenericType::get(nominal, parentType, genericArgs); | ||
} | ||
|
||
// If we have a typealias, try to form type sugar. | ||
if (typealias && isPassThroughTypealias(typealias)) { | ||
auto typealiasSig = typealias->getGenericSignature(); | ||
if (typealiasSig) { | ||
auto subMap = | ||
typealiasSig->getSubstitutionMap( | ||
[](SubstitutableType *type) -> Type { | ||
return Type(type); | ||
}, | ||
[](CanType dependentType, | ||
Type replacementType, | ||
ProtocolType *protoType) { | ||
auto proto = protoType->getDecl(); | ||
return ProtocolConformanceRef(proto); | ||
}); | ||
|
||
resultType = BoundNameAliasType::get(typealias, parentType, | ||
subMap, resultType); | ||
|
||
mustInferRequirements = true; | ||
} else { | ||
resultType = typealias->getDeclaredInterfaceType(); | ||
} | ||
} | ||
|
||
return BoundGenericType::get(nominal, parentType, genericArgs); | ||
return resultType; | ||
} | ||
|
||
/// Visit the given generic parameter lists from the outermost to the innermost, | ||
|
@@ -8283,7 +8376,10 @@ checkExtensionGenericParams(TypeChecker &tc, ExtensionDecl *ext, Type type, | |
assert(!ext->getGenericEnvironment()); | ||
|
||
// Form the interface type of the extension. | ||
Type extInterfaceType = formExtensionInterfaceType(type, genericParams); | ||
bool mustInferRequirements = false; | ||
Type extInterfaceType = | ||
formExtensionInterfaceType(tc, ext, type, genericParams, | ||
mustInferRequirements); | ||
|
||
// Prepare all of the generic parameter lists for generic signature | ||
// validation. | ||
|
@@ -8305,7 +8401,8 @@ checkExtensionGenericParams(TypeChecker &tc, ExtensionDecl *ext, Type type, | |
auto *env = tc.checkGenericEnvironment(genericParams, | ||
ext->getDeclContext(), nullptr, | ||
/*allowConcreteGenericParams=*/true, | ||
ext, inferExtendedTypeReqs); | ||
ext, inferExtendedTypeReqs, | ||
mustInferRequirements); | ||
|
||
// Validate the generic parameters for the last time, to splat down | ||
// actual archetypes. | ||
|
@@ -8344,7 +8441,14 @@ void TypeChecker::validateExtension(ExtensionDecl *ext) { | |
return; | ||
|
||
// Validate the nominal type declaration being extended. | ||
auto nominal = extendedType->getAnyNominal(); | ||
NominalTypeDecl *nominal = extendedType->getAnyNominal(); | ||
if (!nominal) { | ||
auto unbound = cast<UnboundGenericType>(extendedType.getPointer()); | ||
auto typealias = cast<TypeAliasDecl>(unbound->getDecl()); | ||
validateDecl(typealias); | ||
|
||
nominal = typealias->getUnderlyingTypeLoc().getType()->getAnyNominal(); | ||
} | ||
validateDecl(nominal); | ||
|
||
if (nominal->getGenericParamsOfContext()) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// RUN: %target-typecheck-verify-swift | ||
|
||
struct RequiresComparable<T: Comparable> { } | ||
|
||
extension CountableRange { // expected-warning{{'CountableRange' is deprecated: renamed to 'Range'}} | ||
// expected-note@-1{{use 'Range' instead}}{{11-25=Range}} | ||
func testComparable() { | ||
_ = RequiresComparable<Bound>() | ||
} | ||
} | ||
|
||
struct RequiresHashable<T: Hashable> { } | ||
|
||
extension DictionaryIndex { | ||
func testHashable() { | ||
_ = RequiresHashable<Key>() | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trivial style point, but a while(true) is arguably better than do/while(true) because you can know that it is an infinite loop reading the code top to bottom.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, sure.