From b1074a085a11437bb154e74f22bd20ad93230574 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 22 Sep 2025 16:42:54 -0700 Subject: [PATCH 1/2] Add default types to generic parameters Update ASTPrinter.cpp --- include/swift/AST/ASTBridging.h | 5 +- include/swift/AST/Decl.h | 41 +++++++++- include/swift/AST/DiagnosticsSema.def | 14 ++++ include/swift/AST/TypeCheckRequests.h | 21 +++++ include/swift/AST/TypeCheckerTypeIDZone.def | 2 + include/swift/Basic/Features.def | 3 + lib/AST/ASTDumper.cpp | 5 ++ lib/AST/ASTPrinter.cpp | 22 +++--- lib/AST/Bridging/GenericsBridging.cpp | 12 ++- lib/AST/Decl.cpp | 42 +++++++--- lib/AST/FeatureSet.cpp | 16 ++++ lib/AST/GenericParamList.cpp | 4 + lib/AST/TypeCheckRequests.cpp | 14 ++++ lib/ASTGen/Sources/ASTGen/Generics.swift | 1 + lib/ASTGen/Sources/ASTGen/SourceFile.swift | 1 + lib/Parse/ParseGeneric.cpp | 34 ++++++++- lib/Sema/TypeCheckDeclPrimary.cpp | 27 ++++++- lib/Sema/TypeCheckRequestFunctions.cpp | 18 +++++ lib/Sema/TypeCheckType.cpp | 15 +++- lib/Sema/TypeOfReference.cpp | 27 +++++++ lib/Serialization/Deserialization.cpp | 13 +++- lib/Serialization/ModuleFormat.h | 5 +- lib/Serialization/Serialization.cpp | 3 +- test/ASTGen/decls.swift | 10 +++ test/Sema/default_generics.swift | 85 +++++++++++++++++++++ test/Serialization/default_generics.swift | 17 +++++ 26 files changed, 422 insertions(+), 35 deletions(-) create mode 100644 test/Sema/default_generics.swift create mode 100644 test/Serialization/default_generics.swift diff --git a/include/swift/AST/ASTBridging.h b/include/swift/AST/ASTBridging.h index 99f9d05e0eef3..814129e10ee51 100644 --- a/include/swift/AST/ASTBridging.h +++ b/include/swift/AST/ASTBridging.h @@ -2893,12 +2893,13 @@ BridgedGenericParamList BridgedGenericParamList_createParsed( SWIFT_NAME( "BridgedGenericTypeParamDecl.createParsed(_:declContext:specifierLoc:" - "name:nameLoc:inheritedType:index:paramKind:)") + "name:nameLoc:inheritedType:defaultType:index:paramKind:)") BridgedGenericTypeParamDecl BridgedGenericTypeParamDecl_createParsed( BridgedASTContext cContext, BridgedDeclContext cDeclContext, swift::SourceLoc specifierLoc, swift::Identifier name, swift::SourceLoc nameLoc, BridgedNullableTypeRepr opaqueInheritedType, - size_t index, swift::GenericTypeParamKind paramKind); + BridgedNullableTypeRepr defaultType, size_t index, + swift::GenericTypeParamKind paramKind); SWIFT_NAME( "BridgedTrailingWhereClause.createParsed(_:whereKeywordLoc:requirements:)") diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index 92469f8d678df..a67349534dcec 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -625,7 +625,10 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated, public Swi ParamKind : 3, /// Whether this generic parameter represents an opaque type. - IsOpaqueType : 1 + IsOpaqueType : 1, + + /// Whether we have computed the default type. + IsDefaultTypeComputed : 1 ); SWIFT_INLINE_BITFIELD_FULL(AssociatedTypeDecl, TypeDecl, 1, @@ -3888,6 +3891,10 @@ class GenericTypeParamDecl final : public TypeDecl, private llvm::TrailingObjects { + // The default type. + TypeLoc DefaultType; + + friend class GenericTypeParamDeclDefaultTypeRequest; friend TrailingObjects; size_t numTrailingObjects(OverloadToken) const { @@ -3902,6 +3909,9 @@ class GenericTypeParamDecl final return 0; } + /// Set the computed default type. + void setDefaultType(Type ty); + /// Construct a new generic type parameter. /// /// \param dc The DeclContext in which the generic type parameter's owner @@ -3920,7 +3930,8 @@ class GenericTypeParamDecl final /// \param opaqueTypeRepr The TypeRepr of an opaque generic parameter. /// GenericTypeParamDecl(DeclContext *dc, Identifier name, SourceLoc nameLoc, - SourceLoc specifierLoc, unsigned depth, unsigned index, + SourceLoc specifierLoc, TypeLoc defaultType, + unsigned depth, unsigned index, GenericTypeParamKind paramKind, bool isOpaqueType, TypeRepr *opaqueTypeRepr); @@ -3943,6 +3954,7 @@ class GenericTypeParamDecl final /// static GenericTypeParamDecl *create(DeclContext *dc, Identifier name, SourceLoc nameLoc, SourceLoc specifierLoc, + TypeLoc defaultType, unsigned depth, unsigned index, GenericTypeParamKind paramKind, bool isOpaqueType, @@ -3956,8 +3968,8 @@ class GenericTypeParamDecl final GenericTypeParamDecl(DeclContext *dc, Identifier name, SourceLoc nameLoc, SourceLoc specifierLoc, unsigned depth, unsigned index, GenericTypeParamKind paramKind) - : GenericTypeParamDecl(dc, name, nameLoc, specifierLoc, depth, index, - paramKind, false, nullptr) { + : GenericTypeParamDecl(dc, name, nameLoc, specifierLoc, TypeLoc(), + depth, index, paramKind, false, nullptr) { } /// Construct a deserialized generic type parameter. @@ -3994,6 +4006,7 @@ class GenericTypeParamDecl final static GenericTypeParamDecl *createParsed(DeclContext *dc, Identifier name, SourceLoc nameLoc, SourceLoc specifierLoc, + TypeLoc defaultType, unsigned index, GenericTypeParamKind paramKind); @@ -4092,6 +4105,26 @@ class GenericTypeParamDecl final return *getTrailingObjects(); } + /// Check to see if we have a default type. + bool hasDefaultType() const { + // If we have a TypeRepr, return true immediately without kicking off + // a request. + return DefaultType.getTypeRepr() || getDefaultType(); + } + + /// Retrieve the default type as written in the source. + TypeRepr *getDefaultTypeRepr() const { + return DefaultType.getTypeRepr(); + } + + /// Retrieve the default type. + Type getDefaultType() const; + + /// Retrieve the default type if computed, `None` otherwise. + /// + /// \Note Should only be used for dumping. + std::optional getCachedDefaultType() const; + /// The index of this generic type parameter within its generic parameter /// list. /// diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index a87859a2cacae..e0fa3240b187e 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -8925,5 +8925,19 @@ ERROR(invalid_redecl_of_file_isolation,none, NOTE(invalid_redecl_of_file_isolation_prev,none, "default isolation was previously declared here", ()) +//===----------------------------------------------------------------------===// +// MARK: Default generics +//===----------------------------------------------------------------------===// + +ERROR(default_generic_not_trailing,none, + "generic parameter %0 with default type %1 must be trailing", + (const GenericTypeParamDecl *, Type)) +ERROR(default_generic_not_type,none, + "generic parameter %0 can only have a default type on type declarations", + (const GenericTypeParamDecl *)) +ERROR(default_generic_parameter_pack,none, + "declaration %0 cannot have both parameter packs and generic parameters with default types", + (const Decl *)) + #define UNDEFINE_DIAGNOSTIC_MACROS #include "DefineDiagnosticMacros.h" diff --git a/include/swift/AST/TypeCheckRequests.h b/include/swift/AST/TypeCheckRequests.h index e5e3cdd70475c..8908c6dd8feba 100644 --- a/include/swift/AST/TypeCheckRequests.h +++ b/include/swift/AST/TypeCheckRequests.h @@ -5386,6 +5386,27 @@ class GenericTypeParamDeclGetValueTypeRequest bool isCached() const { return true; } }; +/// Compute the default type of a generic type param. +class GenericTypeParamDeclDefaultTypeRequest + : public SimpleRequest { +public: + using SimpleRequest::SimpleRequest; + +private: + friend SimpleRequest; + + // Evaluation. + Type evaluate(Evaluator &evaluator, GenericTypeParamDecl *decl) const; + +public: + // Caching. + bool isCached() const { return true; } + std::optional getCachedResult() const; + void cacheResult(Type value) const; +}; + class CustomDerivativesRequest : public SimpleRequest diff --git a/include/swift/Basic/Features.def b/include/swift/Basic/Features.def index f71e9df63d222..aba10b656d735 100644 --- a/include/swift/Basic/Features.def +++ b/include/swift/Basic/Features.def @@ -548,6 +548,9 @@ EXPERIMENTAL_FEATURE(ImportMacroAliases, true) /// Enable borrow/mutate accessors EXPERIMENTAL_FEATURE(BorrowAndMutateAccessors, false) +/// Allow generics to have a default type, e.g. ``. +EXPERIMENTAL_FEATURE(DefaultGenerics, false) + #undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE #undef EXPERIMENTAL_FEATURE #undef UPCOMING_FEATURE diff --git a/lib/AST/ASTDumper.cpp b/lib/AST/ASTDumper.cpp index 2cf34f15ce68d..f95d7ac1f7886 100644 --- a/lib/AST/ASTDumper.cpp +++ b/lib/AST/ASTDumper.cpp @@ -2204,6 +2204,11 @@ namespace { printRec(decl->getValueType(), Label::always("value_type")); break; } + + if (decl->hasDefaultType()) { + printRec(decl->getDefaultType(), Label::always("default_type")); + } + printAttributes(decl); printFoot(); diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index ca672f8dee86b..d1dad55f0f292 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -2022,6 +2022,11 @@ void PrintAST::printSingleDepthOfGenericSignature( printType(param->getValueType()); } + if (GP->hasDefaultType()) { + Printer << " = "; + printType(GP->getDefaultType()); + } + Printer.printStructurePost(PrintStructureKind::GenericParameter, GP); } else { @@ -3660,6 +3665,11 @@ void PrintAST::visitGenericTypeParamDecl(GenericTypeParamDecl *decl) { }); printInherited(decl); + + if (decl->hasDefaultType()) { + Printer << " = "; + decl->getDefaultType().print(Printer, Options); + } } void PrintAST::visitAssociatedTypeDecl(AssociatedTypeDecl *decl) { @@ -8378,17 +8388,7 @@ void GenericParamList::print(ASTPrinter &Printer, interleave( *this, [&](const GenericTypeParamDecl *P) { - Printer << P->getName(); - if (!P->getInherited().empty()) { - Printer << " : "; - - auto loc = P->getInherited().getEntry(0); - if (willUseTypeReprPrinting(loc, nullptr, PO)) { - loc.getTypeRepr()->print(Printer, PO); - } else { - loc.getType()->print(Printer, PO); - } - } + P->print(Printer, PO); }, [&] { Printer << ", "; }); diff --git a/lib/AST/Bridging/GenericsBridging.cpp b/lib/AST/Bridging/GenericsBridging.cpp index 15766836fd79e..cfd9c94fbb604 100644 --- a/lib/AST/Bridging/GenericsBridging.cpp +++ b/lib/AST/Bridging/GenericsBridging.cpp @@ -45,10 +45,18 @@ BridgedGenericParamList BridgedGenericParamList_createParsed( BridgedGenericTypeParamDecl BridgedGenericTypeParamDecl_createParsed( BridgedASTContext cContext, BridgedDeclContext cDeclContext, SourceLoc specifierLoc, Identifier name, SourceLoc nameLoc, - BridgedNullableTypeRepr bridgedInheritedType, size_t index, + BridgedNullableTypeRepr bridgedInheritedType, + BridgedNullableTypeRepr bridgedDefaultType, size_t index, swift::GenericTypeParamKind paramKind) { + TypeLoc defaultType; + + if (auto *defaultTypeRepr = bridgedDefaultType.unbridged()) { + defaultType = TypeLoc(defaultTypeRepr); + } + auto *decl = GenericTypeParamDecl::createParsed( - cDeclContext.unbridged(), name, nameLoc, specifierLoc, index, paramKind); + cDeclContext.unbridged(), name, nameLoc, specifierLoc, defaultType, + index, paramKind); if (auto *inheritedType = bridgedInheritedType.unbridged()) { auto entry = InheritedEntry(inheritedType); diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index fff6a6e2a26a8..66ead2a7ffa1d 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -6088,11 +6088,13 @@ Type TypeAliasDecl::getStructuralType() const { GenericTypeParamDecl::GenericTypeParamDecl(DeclContext *dc, Identifier name, SourceLoc nameLoc, SourceLoc specifierLoc, + TypeLoc defaultType, unsigned depth, unsigned index, GenericTypeParamKind paramKind, bool isOpaqueType, TypeRepr *opaqueTypeRepr) - : TypeDecl(DeclKind::GenericTypeParam, dc, name, nameLoc, {}) { + : TypeDecl(DeclKind::GenericTypeParam, dc, name, nameLoc, {}), + DefaultType(defaultType) { ASSERT(!(specifierLoc && !(paramKind == GenericTypeParamKind::Pack || paramKind == GenericTypeParamKind::Value)) && "'each' or 'let' keyword imply a parameter pack or value generic parameter"); @@ -6104,6 +6106,7 @@ GenericTypeParamDecl::GenericTypeParamDecl(DeclContext *dc, Identifier name, assert(Bits.GenericTypeParamDecl.Index == index && "Truncation"); Bits.GenericTypeParamDecl.ParamKind = (uint8_t) paramKind; Bits.GenericTypeParamDecl.IsOpaqueType = isOpaqueType; + Bits.GenericTypeParamDecl.IsDefaultTypeComputed = false; if (this->isOpaqueType()) *getTrailingObjects() = opaqueTypeRepr; @@ -6117,8 +6120,9 @@ GenericTypeParamDecl::GenericTypeParamDecl(DeclContext *dc, Identifier name, GenericTypeParamDecl *GenericTypeParamDecl::create( DeclContext *dc, Identifier name, SourceLoc nameLoc, SourceLoc specifierLoc, - unsigned depth, unsigned index, GenericTypeParamKind paramKind, - bool isOpaqueType, TypeRepr *opaqueTypeRepr) { + TypeLoc defaultType, unsigned depth, unsigned index, + GenericTypeParamKind paramKind, bool isOpaqueType, + TypeRepr *opaqueTypeRepr) { auto &ctx = dc->getASTContext(); auto numTypeReprs = 0; @@ -6136,15 +6140,15 @@ GenericTypeParamDecl *GenericTypeParamDecl::create( numSourceLocs); auto mem = ctx.Allocate(allocSize, alignof(GenericTypeParamDecl)); return new (mem) - GenericTypeParamDecl(dc, name, nameLoc, specifierLoc, depth, index, - paramKind, isOpaqueType, opaqueTypeRepr); + GenericTypeParamDecl(dc, name, nameLoc, specifierLoc, defaultType, depth, + index, paramKind, isOpaqueType, opaqueTypeRepr); } GenericTypeParamDecl *GenericTypeParamDecl::createDeserialized( DeclContext *dc, Identifier name, unsigned depth, unsigned index, GenericTypeParamKind paramKind, bool isOpaqueType) { return GenericTypeParamDecl::create(dc, name, SourceLoc(), SourceLoc(), - depth, index, paramKind, + TypeLoc(), depth, index, paramKind, isOpaqueType, /*opaqueRepr*/ nullptr); } @@ -6152,14 +6156,15 @@ GenericTypeParamDecl *GenericTypeParamDecl::createDeserialized( GenericTypeParamDecl * GenericTypeParamDecl::createParsed(DeclContext *dc, Identifier name, SourceLoc nameLoc, SourceLoc specifierLoc, - unsigned index, + TypeLoc defaultType, unsigned index, GenericTypeParamKind paramKind) { // We always create generic type parameters with an invalid depth. // Semantic analysis fills in the depth when it processes the generic // parameter list. return GenericTypeParamDecl::create( - dc, name, nameLoc, specifierLoc, GenericTypeParamDecl::InvalidDepth, - index, paramKind, /*isOpaqueType*/ false, /*opaqueTypeRepr*/ nullptr); + dc, name, nameLoc, specifierLoc, defaultType, + GenericTypeParamDecl::InvalidDepth, index, paramKind, + /*isOpaqueType*/ false, /*opaqueTypeRepr*/ nullptr); } GenericTypeParamDecl *GenericTypeParamDecl::createImplicit( @@ -6167,6 +6172,7 @@ GenericTypeParamDecl *GenericTypeParamDecl::createImplicit( GenericTypeParamKind paramKind, TypeRepr *opaqueTypeRepr, SourceLoc nameLoc, SourceLoc specifierLoc) { auto *param = GenericTypeParamDecl::create(dc, name, nameLoc, specifierLoc, + TypeLoc(), depth, index, paramKind, (bool)opaqueTypeRepr, opaqueTypeRepr); @@ -6184,6 +6190,24 @@ Type GenericTypeParamDecl::getValueType() const { return ty ? ty : ErrorType::get(ctx); } +Type GenericTypeParamDecl::getDefaultType() const { + auto &ctx = getASTContext(); + GenericTypeParamDeclDefaultTypeRequest req(const_cast(this)); + return evaluateOrDefault(ctx.evaluator, req, Type()); +} + +std::optional GenericTypeParamDecl::getCachedDefaultType() const { + if (Bits.GenericTypeParamDecl.IsDefaultTypeComputed) + return DefaultType.getType(); + + return std::nullopt; +} + +void GenericTypeParamDecl::setDefaultType(Type ty) { + DefaultType.setType(ty); + Bits.GenericTypeParamDecl.IsDefaultTypeComputed = true; +} + SourceRange GenericTypeParamDecl::getSourceRange() const { auto startLoc = getNameLoc(); auto endLoc = getNameLoc(); diff --git a/lib/AST/FeatureSet.cpp b/lib/AST/FeatureSet.cpp index 87902abb0612b..29c5dc761cc5c 100644 --- a/lib/AST/FeatureSet.cpp +++ b/lib/AST/FeatureSet.cpp @@ -431,6 +431,22 @@ static bool usesFeatureDefaultIsolationPerFile(Decl *D) { return isa(D); } +static bool usesFeatureDefaultGenerics(Decl *decl) { + auto genericContext = decl->getAsGenericContext(); + + if (!genericContext || !genericContext->getGenericParams()) { + return false; + } + + for (auto arg : *genericContext->getGenericParams()) { + if (arg->hasDefaultType()) { + return true; + } + } + + return false; +} + UNINTERESTING_FEATURE(BuiltinSelect) UNINTERESTING_FEATURE(BuiltinInterleave) UNINTERESTING_FEATURE(BuiltinVectorsExternC) diff --git a/lib/AST/GenericParamList.cpp b/lib/AST/GenericParamList.cpp index 7ca5ffbb42f36..90716d2a462b3 100644 --- a/lib/AST/GenericParamList.cpp +++ b/lib/AST/GenericParamList.cpp @@ -85,6 +85,10 @@ GenericParamList::clone(DeclContext *dc) const { GenericTypeParamDeclGetValueTypeRequest{newParam}, param->getValueType()); + ctx.evaluator.cacheOutput( + GenericTypeParamDeclDefaultTypeRequest{newParam}, + param->getDefaultType()); + params.push_back(newParam); } diff --git a/lib/AST/TypeCheckRequests.cpp b/lib/AST/TypeCheckRequests.cpp index d84abcc283f11..e5e3c7a9c72bf 100644 --- a/lib/AST/TypeCheckRequests.cpp +++ b/lib/AST/TypeCheckRequests.cpp @@ -2887,3 +2887,17 @@ void IsCustomAvailabilityDomainPermanentlyEnabled::cacheResult( domain->flags.isPermanentlyEnabledComputed = true; domain->flags.isPermanentlyEnabled = isPermanentlyEnabled; } + +//----------------------------------------------------------------------------// +// GenericTypeParamDeclDefaultTypeRequest computation. +//----------------------------------------------------------------------------// + +std::optional GenericTypeParamDeclDefaultTypeRequest::getCachedResult() const { + auto *decl = std::get<0>(getStorage()); + return decl->getCachedDefaultType(); +} + +void GenericTypeParamDeclDefaultTypeRequest::cacheResult(Type value) const { + auto *decl = std::get<0>(getStorage()); + decl->setDefaultType(value); +} diff --git a/lib/ASTGen/Sources/ASTGen/Generics.swift b/lib/ASTGen/Sources/ASTGen/Generics.swift index b5a5d1326d90f..f827b7ba7fcd1 100644 --- a/lib/ASTGen/Sources/ASTGen/Generics.swift +++ b/lib/ASTGen/Sources/ASTGen/Generics.swift @@ -57,6 +57,7 @@ extension ASTGenVisitor { name: name, nameLoc: nameLoc, inheritedType: self.generate(type: node.inheritedType), + defaultType: self.generate(type: node.initializer?.value), index: genericParameterIndex, paramKind: paramKind ) diff --git a/lib/ASTGen/Sources/ASTGen/SourceFile.swift b/lib/ASTGen/Sources/ASTGen/SourceFile.swift index c9981bd320063..a4b6697338324 100644 --- a/lib/ASTGen/Sources/ASTGen/SourceFile.swift +++ b/lib/ASTGen/Sources/ASTGen/SourceFile.swift @@ -86,6 +86,7 @@ extension Parser.ExperimentalFeatures { mapFeature(.OldOwnershipOperatorSpellings, to: .oldOwnershipOperatorSpellings) mapFeature(.KeyPathWithMethodMembers, to: .keypathWithMethodMembers) mapFeature(.DefaultIsolationPerFile, to: .defaultIsolationPerFile) + mapFeature(.DefaultGenerics, to: .defaultGenerics) } } diff --git a/lib/Parse/ParseGeneric.cpp b/lib/Parse/ParseGeneric.cpp index ea0cfa8d9e9f5..8b8ef36c79850 100644 --- a/lib/Parse/ParseGeneric.cpp +++ b/lib/Parse/ParseGeneric.cpp @@ -34,6 +34,7 @@ using namespace swift; /// identifier /// identifier ':' type-identifier /// identifier ':' type-composition +/// identifier ':' type-identifier '=' type-identifier /// /// When parsing the generic parameters, this routine establishes a new scope /// and adds those parameters to the scope. @@ -129,6 +130,37 @@ Parser::parseGenericParametersBeforeWhere(SourceLoc LAngleLoc, Inherited.push_back({Ty.get()}); } + // Parse the '=' followed by a type. + TypeLoc DefaultType; + + if (Context.LangOpts.hasFeature(Feature::DefaultGenerics)) { + // TODO: Don't allow defaults for values or parameter packs at the moment. + if (Tok.is(tok::equal) && !EachLoc.isValid() && !LetLoc.isValid()) { + (void)consumeToken(); + ParserResult Ty; + + if (Tok.isAny(tok::identifier, tok::code_complete, tok::kw_protocol, + tok::kw_Any) || Tok.isTilde()) { + Ty = parseType(); + } else if (Tok.is(tok::kw_class)) { + diagnose(Tok, diag::unexpected_class_constraint); + diagnose(Tok, diag::suggest_anyobject) + .fixItReplace(Tok.getLoc(), "AnyObject"); + consumeToken(); + Result.setIsParseError(); + } else { + diagnose(Tok, diag::expected_generics_type_restriction, Name); + Result.setIsParseError(); + } + + if (Ty.hasCodeCompletion()) + return makeParserCodeCompletionStatus(); + + if (Ty.isNonNull()) + DefaultType = Ty.get(); + } + } + auto ParamKind = GenericTypeParamKind::Type; SourceLoc SpecifierLoc; @@ -141,7 +173,7 @@ Parser::parseGenericParametersBeforeWhere(SourceLoc LAngleLoc, } auto *Param = GenericTypeParamDecl::createParsed( - CurDeclContext, Name, NameLoc, SpecifierLoc, + CurDeclContext, Name, NameLoc, SpecifierLoc, DefaultType, /*index*/ GenericParams.size(), ParamKind); if (!Inherited.empty()) Param->setInherited(Context.AllocateCopy(Inherited)); diff --git a/lib/Sema/TypeCheckDeclPrimary.cpp b/lib/Sema/TypeCheckDeclPrimary.cpp index 21835411094e3..4b49b74108bf8 100644 --- a/lib/Sema/TypeCheckDeclPrimary.cpp +++ b/lib/Sema/TypeCheckDeclPrimary.cpp @@ -540,8 +540,13 @@ static void checkGenericParams(GenericContext *ownerCtx) { auto *decl = ownerCtx->getAsDecl(); auto &ctx = ownerCtx->getASTContext(); bool hasPack = false; + bool hasDefault = false; + bool hasSeenNonDefault = false; + + for (auto i : indices(genericParams->getParams())) { + // Walk backwards which makes it easier to flag non-trailing default types. + auto gp = genericParams->getParams()[genericParams->size() - 1 - i]; - for (auto gp : *genericParams) { // Diagnose generic types with a parameter packs if VariadicGenerics // is not enabled. if (gp->isParameterPack()) { @@ -580,10 +585,30 @@ static void checkGenericParams(GenericContext *ownerCtx) { } } + if (gp->hasDefaultType()) { + // Generic parameters with default types _must_ be trailing + if (hasSeenNonDefault) { + gp->diagnose(diag::default_generic_not_trailing, gp, gp->getDefaultType()); + } + + // Only allow default types on actual type declarations. + if (!isa(decl) && !isa(decl)) { + gp->diagnose(diag::default_generic_not_type, gp); + } + + hasDefault = true; + } else { + hasSeenNonDefault = true; + } + TypeChecker::checkDeclAttributes(gp); checkInheritanceClause(gp); } + if (hasPack && hasDefault) { + decl->diagnose(diag::default_generic_parameter_pack, decl); + } + // Force resolution of interface types written in requirements here. WhereClauseOwner(ownerCtx) .visitRequirements(TypeResolutionStage::Interface, diff --git a/lib/Sema/TypeCheckRequestFunctions.cpp b/lib/Sema/TypeCheckRequestFunctions.cpp index ec0ecf94003c2..689bf7220e430 100644 --- a/lib/Sema/TypeCheckRequestFunctions.cpp +++ b/lib/Sema/TypeCheckRequestFunctions.cpp @@ -509,6 +509,24 @@ Type GenericTypeParamDeclGetValueTypeRequest::evaluate( return inherited.getResolvedType(0, TypeResolutionStage::Structural); } +Type GenericTypeParamDeclDefaultTypeRequest::evaluate( + Evaluator &evaluator, GenericTypeParamDecl *decl) const { + auto defaultTyRepr = decl->getDefaultTypeRepr(); + + if (!defaultTyRepr) { + return Type(); + } + + return TypeResolution::forInterface(decl->getDeclContext(), + std::nullopt, + // Diagnose unbound generics and + // placeholders. + /*unboundTyOpener*/ nullptr, + /*placeholderHandler*/ nullptr, + /*packElementOpener*/ nullptr) + .resolveType(defaultTyRepr); +} + // Define request evaluation functions for each of the type checker requests. static AbstractRequestFunction *typeCheckerRequestFunctions[] = { #define SWIFT_REQUEST(Zone, Name, Sig, Caching, LocOptions) \ diff --git a/lib/Sema/TypeCheckType.cpp b/lib/Sema/TypeCheckType.cpp index a9c7815541f7e..7091c22c2f7ec 100644 --- a/lib/Sema/TypeCheckType.cpp +++ b/lib/Sema/TypeCheckType.cpp @@ -880,12 +880,25 @@ static bool resolveGenericArguments(ValueDecl *decl, args.push_back(substTy); } + // Implicitly add default generic args if there are any. + if (!hasParameterPack && args.size() < genericParams->size()) { + for (auto i = args.size(); i != genericParams->size(); i += 1) { + // If we don't have a default type, then bail out. We'll diagnose this + // shortly. + if (!genericParams->getParams()[i]->hasDefaultType()) { + break; + } + + args.push_back(genericParams->getParams()[i]->getDefaultType()); + } + } + // Make sure we have the right number of generic arguments. if (!hasParameterPack) { // For generic types without type parameter packs, we require // the number of declared generic parameters match the number of // arguments. - if (genericArgs.size() != genericParams->size()) { + if (args.size() != genericParams->size()) { if (!options.contains(TypeResolutionFlags::SilenceErrors)) { diagnoseInvalidGenericArguments( loc, decl, genericArgs.size(), genericParams->size(), diff --git a/lib/Sema/TypeOfReference.cpp b/lib/Sema/TypeOfReference.cpp index 07c812f4919f9..195ccde6a1eab 100644 --- a/lib/Sema/TypeOfReference.cpp +++ b/lib/Sema/TypeOfReference.cpp @@ -90,6 +90,33 @@ Type ConstraintSystem::openUnboundGenericType(GenericTypeDecl *decl, } } + // Add defaultable constraints for every generic parameter that has a default + // type. + // + // FIXME: This feels bad. There has to be a better way... + SmallVector params; + decl->forEachGenericContext([&](auto gpList) { + for (auto param : *gpList) { + if (!param->hasDefaultType()) { + continue; + } + + auto replacement = llvm::find_if(replacements, [&](auto pair) { + auto paramTy = param->getDeclaredInterfaceType()->getCanonicalType(); + auto replacementTy = pair.first->getCanonicalType(); + + return replacementTy->isEqual(paramTy); + }); + + if (replacement == replacements.end()) { + continue; + } + + addConstraint(ConstraintKind::Defaultable, replacement->second, + param->getDefaultType(), locator); + } + }); + // Map the generic parameters to their corresponding type variables. llvm::SmallVector arguments; for (auto gp : decl->getInnermostGenericParamTypes()) { diff --git a/lib/Serialization/Deserialization.cpp b/lib/Serialization/Deserialization.cpp index 4646e9b0c5ef8..af4fed99c0e44 100644 --- a/lib/Serialization/Deserialization.cpp +++ b/lib/Serialization/Deserialization.cpp @@ -3619,9 +3619,11 @@ class DeclDeserializer { bool isImplicit; bool isOpaqueType; TypeID interfaceTypeID; + TypeID defaultTypeID; decls_block::GenericTypeParamDeclLayout::readRecord( - scratch, nameID, isImplicit, isOpaqueType, interfaceTypeID); + scratch, nameID, isImplicit, isOpaqueType, interfaceTypeID, + defaultTypeID); auto interfaceTy = MF.getTypeChecked(interfaceTypeID); if (!interfaceTy) @@ -3649,6 +3651,15 @@ class DeclDeserializer { paramTy->getValueType()); } + // If this generic parameter had a default type, inform the evaluator that + // this value is already computed. + auto defaultTy = MF.getTypeChecked(defaultTypeID); + if (defaultTy) { + ctx.evaluator.cacheOutput( + GenericTypeParamDeclDefaultTypeRequest{genericParam}, + std::move(defaultTy.get())); + } + return genericParam; } diff --git a/lib/Serialization/ModuleFormat.h b/lib/Serialization/ModuleFormat.h index bfa13c6540bd7..28af5a8db3239 100644 --- a/lib/Serialization/ModuleFormat.h +++ b/lib/Serialization/ModuleFormat.h @@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0; /// describe what change you made. The content of this comment isn't important; /// it just ensures a conflict if two people change the module format. /// Don't worry about adhering to the 80-column limit for this line. -const uint16_t SWIFTMODULE_VERSION_MINOR = 961; // borrow/mutate accessors +const uint16_t SWIFTMODULE_VERSION_MINOR = 962; // generic parameter default types /// A standard hash seed used for all string hashes in a serialized module. /// @@ -1589,7 +1589,8 @@ namespace decls_block { IdentifierIDField, // name BCFixed<1>, // implicit flag BCFixed<1>, // is opaque? - TypeIDField // interface type + TypeIDField, // interface type, + TypeIDField // default type? >; using AssociatedTypeDeclLayout = BCRecordLayout< diff --git a/lib/Serialization/Serialization.cpp b/lib/Serialization/Serialization.cpp index ba0174f1d6c51..e759664286535 100644 --- a/lib/Serialization/Serialization.cpp +++ b/lib/Serialization/Serialization.cpp @@ -4442,7 +4442,8 @@ class Serializer::DeclSerializer : public DeclVisitor { S.addDeclBaseNameRef(genericParam->getName()), genericParam->isImplicit(), genericParam->isOpaqueType(), - S.addTypeRef(genericParam->getDeclaredInterfaceType()->getCanonicalType())); + S.addTypeRef(genericParam->getDeclaredInterfaceType()->getCanonicalType()), + S.addTypeRef(genericParam->getDefaultType())); } void visitAssociatedTypeDecl(const AssociatedTypeDecl *assocType) { diff --git a/test/ASTGen/decls.swift b/test/ASTGen/decls.swift index aebef050c773d..d721290cf81de 100644 --- a/test/ASTGen/decls.swift +++ b/test/ASTGen/decls.swift @@ -3,10 +3,12 @@ // RUN: %target-swift-frontend-dump-parse -disable-availability-checking -enable-experimental-move-only -enable-experimental-concurrency -enable-experimental-feature ParserASTGen \ // RUN: -enable-experimental-feature CoroutineAccessors \ // RUN: -enable-experimental-feature DefaultIsolationPerFile \ +// RUN: -enable-experimental-feature DefaultGenerics \ // RUN: | %sanitize-address > %t/astgen.ast // RUN: %target-swift-frontend-dump-parse -disable-availability-checking -enable-experimental-move-only -enable-experimental-concurrency \ // RUN: -enable-experimental-feature CoroutineAccessors \ // RUN: -enable-experimental-feature DefaultIsolationPerFile \ +// RUN: -enable-experimental-feature DefaultGenerics \ // RUN: | %sanitize-address > %t/cpp-parser.ast // RUN: %diff -u %t/astgen.ast %t/cpp-parser.ast @@ -18,6 +20,7 @@ // REQUIRES: swift_feature_ParserASTGen // REQUIRES: swift_feature_CoroutineAccessors // REQUIRES: swift_feature_DefaultIsolationPerFile +// REQUIRES: swift_feature_DefaultGenerics // rdar://116686158 // UNSUPPORTED: asan @@ -361,3 +364,10 @@ func testBuilder() { } struct ExpansionRequirementTest where repeat each T: Comparable {} + +struct SystemAllocator: Proto1, Proto2 {} +struct Vec {} +enum DefaultGenericEnum {} +class DefaultGenericClass {} +typealias DefaultGenericTypealias = Vec +func defaultGenericFunc() {} diff --git a/test/Sema/default_generics.swift b/test/Sema/default_generics.swift new file mode 100644 index 0000000000000..f6a7f118ab4dc --- /dev/null +++ b/test/Sema/default_generics.swift @@ -0,0 +1,85 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-feature DefaultGenerics -disable-availability-checking + +// REQUIRES: swift_feature_DefaultGenerics + +struct A {} // expected-note{{generic struct 'A' declared here}} +enum B { + case foo +} +class C {} +typealias D = A +actor E {} + +protocol F {} // expected-error{{an associated type named 'T' must be declared in the protocol 'F' or a protocol it inherits}} + // expected-error@-1{{expected '>' to complete primary associated type list}} + // expected-note@-2{{to match this opening '<'}} + +protocol G { // expected-error{{expected '>' to complete primary associated type list}} + // expected-note@-1{{to match this opening '<'}} + associatedtype T +} + +func h(_: T) {} // expected-error{{generic parameter 'T' can only have a default type on type declarations}} + +struct I { + subscript(_: T) -> Int { 123 } // expected-error{{generic parameter 'T' can only have a default type on type declarations}} +} + +struct J {} // expected-error{{declaration 'J' cannot have both parameter packs and generic parameters with default types}} + +struct K {} // OK + +struct L {} // expected-error{{generic parameter 'T' with default type 'Int' must be trailing}} + +protocol Allocator {} + +struct SystemAllocator: Allocator {} +struct BumpAllocator: Allocator {} + +struct Vec {} // OK + // expected-note@-1{{arguments to generic parameter 'Alloc' ('BumpAllocator' and 'SystemAllocator') are expected to be equal}} +// FIXME: This needs to be diagnosed somehow... +struct Vec2 {} + +let m = A() // OK +let n = B.foo // OK +let o = C() // OK +let p = D() // OK +let q = E() // OK + +struct R { + struct S {} +} + +let t = R.S() // OK +let u = R.S() // OK +let v = R.S() // OK + +let w = Vec() // OK + +// FIXME: We need to diagnose the declaration with an incorrect default type +let x = Vec2() // expected-error{{type 'Int' does not conform to protocol 'Allocator'}} + +func y(_: Vec) {} // OK + +// TODO: Perhaps we can eliminate generic arguments from being printed if they are the default? +y(Vec()) // expected-error{{cannot convert value of type 'Vec' to expected argument type 'Vec'}} + +// FIXME: We need to disallow this. +struct Z {} + +// FIXME: We should probably allow this. +func aa(_: A) {} // expected-error{{reference to generic type 'A' requires arguments in <...>}} + +func ab(_: A< >) {} // OK + +struct MyArray {} // OK + +extension MyArray: ExpressibleByArrayLiteral { + init(arrayLiteral: Element...) {} +} + +func ac(_: MyArray) {} + +let ad: MyArray = [1, 2, 3] // OK +ac(ad) // OK diff --git a/test/Serialization/default_generics.swift b/test/Serialization/default_generics.swift new file mode 100644 index 0000000000000..40e104eacfb40 --- /dev/null +++ b/test/Serialization/default_generics.swift @@ -0,0 +1,17 @@ +// RUN: %empty-directory(%t) +// RUN: %target-swift-frontend %s -emit-module -enable-experimental-feature DefaultGenerics -disable-availability-checking -parse-as-library -o %t +// RUN: %target-sil-opt -enable-sil-verify-all %t/default_generics.swiftmodule -o - | %FileCheck %s + +// REQUIRES: swift_feature_DefaultGenerics + +// CHECK: struct A { +struct A {} + +protocol Allocator {} +struct SystemAllocator: Allocator {} + +// CHECK: struct Vec where Alloc : Allocator, Element : ~Copyable { +struct Vec {} + +// CHECK: func foo(_: Vec) +func foo(_: Vec) {} From c2eab86e31199901d0214a7efb3a851dbbf1aaca Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 23 Sep 2025 11:28:51 -0700 Subject: [PATCH 2/2] Simplify parsing and fix astgen failures --- lib/AST/ASTPrinter.cpp | 23 ++++++++++++++++++- lib/Parse/ParseGeneric.cpp | 47 ++++++++++++-------------------------- test/ASTGen/decls.swift | 3 +-- 3 files changed, 38 insertions(+), 35 deletions(-) diff --git a/lib/AST/ASTPrinter.cpp b/lib/AST/ASTPrinter.cpp index d1dad55f0f292..e3b79f163d7f7 100644 --- a/lib/AST/ASTPrinter.cpp +++ b/lib/AST/ASTPrinter.cpp @@ -8388,7 +8388,28 @@ void GenericParamList::print(ASTPrinter &Printer, interleave( *this, [&](const GenericTypeParamDecl *P) { - P->print(Printer, PO); + Printer << P->getName(); + if (!P->getInherited().empty()) { + Printer << " : "; + + auto loc = P->getInherited().getEntry(0); + if (willUseTypeReprPrinting(loc, nullptr, PO)) { + loc.getTypeRepr()->print(Printer, PO); + } else { + loc.getType()->print(Printer, PO); + } + + if (P->hasDefaultType()) { + Printer << " = "; + + auto loc = P->getDefaultTypeRepr(); + if (loc && willUseTypeReprPrinting(loc, nullptr, PO)) { + loc->print(Printer, PO); + } else { + P->getDefaultType()->print(Printer, PO); + } + } + } }, [&] { Printer << ", "; }); diff --git a/lib/Parse/ParseGeneric.cpp b/lib/Parse/ParseGeneric.cpp index 8b8ef36c79850..dc6e821340e3c 100644 --- a/lib/Parse/ParseGeneric.cpp +++ b/lib/Parse/ParseGeneric.cpp @@ -130,37 +130,6 @@ Parser::parseGenericParametersBeforeWhere(SourceLoc LAngleLoc, Inherited.push_back({Ty.get()}); } - // Parse the '=' followed by a type. - TypeLoc DefaultType; - - if (Context.LangOpts.hasFeature(Feature::DefaultGenerics)) { - // TODO: Don't allow defaults for values or parameter packs at the moment. - if (Tok.is(tok::equal) && !EachLoc.isValid() && !LetLoc.isValid()) { - (void)consumeToken(); - ParserResult Ty; - - if (Tok.isAny(tok::identifier, tok::code_complete, tok::kw_protocol, - tok::kw_Any) || Tok.isTilde()) { - Ty = parseType(); - } else if (Tok.is(tok::kw_class)) { - diagnose(Tok, diag::unexpected_class_constraint); - diagnose(Tok, diag::suggest_anyobject) - .fixItReplace(Tok.getLoc(), "AnyObject"); - consumeToken(); - Result.setIsParseError(); - } else { - diagnose(Tok, diag::expected_generics_type_restriction, Name); - Result.setIsParseError(); - } - - if (Ty.hasCodeCompletion()) - return makeParserCodeCompletionStatus(); - - if (Ty.isNonNull()) - DefaultType = Ty.get(); - } - } - auto ParamKind = GenericTypeParamKind::Type; SourceLoc SpecifierLoc; @@ -172,8 +141,22 @@ Parser::parseGenericParametersBeforeWhere(SourceLoc LAngleLoc, SpecifierLoc = LetLoc; } + // Parse the '=' followed by a type. + ParserResult DefaultType; + + if (Context.LangOpts.hasFeature(Feature::DefaultGenerics)) { + // TODO: Don't allow defaults for values or parameter packs at the moment. + if (Tok.is(tok::equal) && ParamKind == GenericTypeParamKind::Type) { + consumeToken(tok::equal); + DefaultType = parseType(diag::expected_type); + Result |= DefaultType; + if (DefaultType.isNull()) + return Result; + } + } + auto *Param = GenericTypeParamDecl::createParsed( - CurDeclContext, Name, NameLoc, SpecifierLoc, DefaultType, + CurDeclContext, Name, NameLoc, SpecifierLoc, DefaultType.getPtrOrNull(), /*index*/ GenericParams.size(), ParamKind); if (!Inherited.empty()) Param->setInherited(Context.AllocateCopy(Inherited)); diff --git a/test/ASTGen/decls.swift b/test/ASTGen/decls.swift index d721290cf81de..a2b4ae9b52e4c 100644 --- a/test/ASTGen/decls.swift +++ b/test/ASTGen/decls.swift @@ -13,7 +13,7 @@ // RUN: %diff -u %t/astgen.ast %t/cpp-parser.ast -// RUN: %target-run-simple-swift(-Xfrontend -disable-availability-checking -Xfrontend -enable-experimental-concurrency -enable-experimental-feature CoroutineAccessors -enable-experimental-feature DefaultIsolationPerFile -enable-experimental-feature ParserASTGen) +// RUN: %target-run-simple-swift(-Xfrontend -disable-availability-checking -Xfrontend -enable-experimental-concurrency -enable-experimental-feature CoroutineAccessors -enable-experimental-feature DefaultIsolationPerFile -enable-experimental-feature DefaultGenerics -enable-experimental-feature ParserASTGen) // REQUIRES: executable_test // REQUIRES: swift_swift_parser @@ -370,4 +370,3 @@ struct Vec {} enum DefaultGenericEnum {} class DefaultGenericClass {} typealias DefaultGenericTypealias = Vec -func defaultGenericFunc() {}