Forbid module selectors on dependent member types

In code like the following:

```
protocol P { associatedtype A: Hashable }
protocol Q { associatedtype A: Comparable }

func fn<T: P & Q>(_: T) where T.A == Int { … }
```

`T.A` is actually the union of `P.A` and `Q.A`—it satisfies both associated types and has both of their constraints. This means it doesn’t actually make sense to apply a module selector to `A`—even if `P` and `Q` are in different modules, `T.A` always represents both of the declarations, not one or the other. We therefore now ban module selectors in this position, since they don’t actually jibe with the nature of a generic signature.

This justification technically doesn’t hold for *every* member type of a generic parameter—a member type can refer to a concrete typealias in a protocol extension, for instance—but in those situations, you can disambiguate (and add module selectors) by writing `P.A` or `Q.A` instead of `T.A`, so we’re not really worried about this limitation.
This commit is contained in:
Becca Royal-Gordon
2025-09-23 16:57:39 -07:00
parent ec92be4955
commit e3495f5fe7
4 changed files with 33 additions and 2 deletions

View File

@@ -1146,6 +1146,10 @@ ERROR(no_module_type,none,
"no type named %0 in module %1", (DeclNameRef, Identifier))
ERROR(ambiguous_module_type,none,
"ambiguous type name %0 in module %1", (DeclNameRef, Identifier))
ERROR(module_selector_dependent_member_type_not_allowed,none,
"module selector is not allowed on generic member type; associated types "
"with the same name are merged instead of shadowing one another",
())
ERROR(use_nonmatching_operator,none,
"%0 is not a %select{binary|prefix unary|postfix unary}1 operator",
(DeclNameRef, unsigned))

View File

@@ -193,11 +193,21 @@ static unsigned getGenericRequirementKind(TypeResolutionOptions options) {
Type TypeResolution::resolveDependentMemberType(
Type baseTy, DeclContext *DC, SourceRange baseRange,
QualifiedIdentTypeRepr *repr) const {
// FIXME(ModQual): If module selector is present, check that it matches the
// protocol's module.
Identifier refIdentifier = repr->getNameRef().getBaseIdentifier();
ASTContext &ctx = DC->getASTContext();
if (repr->getNameRef().hasModuleSelector()) {
if (!this->getOptions().contains(TypeResolutionFlags::SilenceErrors)) {
ctx.Diags.diagnose(repr->getNameLoc().getModuleSelectorLoc(),
diag::module_selector_dependent_member_type_not_allowed)
.fixItRemoveChars(repr->getNameLoc().getModuleSelectorLoc(),
repr->getNameLoc().getBaseNameLoc());
// If we can check if `refIdentifier` is a protocol ext's concrete type:
// FIXME: Conditionally emit fix-it replacing base type with protocol
}
return ErrorType::get(baseTy);
}
switch (stage) {
case TypeResolutionStage::Structural:
return DependentMemberType::get(baseTy, refIdentifier);

View File

@@ -41,5 +41,10 @@ public struct MyBuilder {
public static func buildBlock()
}
public protocol ComparableIdentifiable {
associatedtype ID: Comparable
var id: ID { get }
}
@freestanding(expression) public macro ExprMacro() -> String = #file
@attached(peer) public macro PeerMacro() = #externalMacro(module: "Fnord", type: "PeerMacro")

View File

@@ -428,3 +428,15 @@ func concurrencyModuleLookups(
// expected-error@-1 {{'withTaskCancellationHandler' is not imported through module 'ModuleSelectorTestingKit'}}
// expected-note@-2 {{did you mean module 'Swift'?}} {{9-33=Swift}}
}
func dependentTypeLookup<T, U, V, W, X>(_: T, _: U, _: V, _: W, _: X) where
T: Identifiable, T: ComparableIdentifiable, T.ID == Int,
U: Identifiable, U: ComparableIdentifiable, U.Swift::ID == Int,
// expected-error@-1 {{module selector is not allowed on generic member type; associated types with the same name are merged instead of shadowing one another}} {{49-56=}}
V: Identifiable, V: ComparableIdentifiable, V.ModuleSelectorTestingKit::ID == Int,
// expected-error@-1 {{module selector is not allowed on generic member type; associated types with the same name are merged instead of shadowing one another}} {{49-75=}}
W: Identifiable, W: ComparableIdentifiable, W.ctypes::ID == Int,
// expected-error@-1 {{module selector is not allowed on generic member type; associated types with the same name are merged instead of shadowing one another}} {{49-57=}}
X: Identifiable, X: ComparableIdentifiable, X.NonexistentModule::ID == Int
// expected-error@-1 {{module selector is not allowed on generic member type; associated types with the same name are merged instead of shadowing one another}} {{49-68=}}
{}