@@ -661,6 +661,70 @@ internal struct ParserResultsComposed {
661661 let set = typeName. set. map { SetType ( name: $0. name, elementTypeName: $0. elementTypeName, elementType: $0. elementType) }
662662 set? . name = aliased. name
663663
664+ // When the typealias is generic (e.g. `typealias Foo<T> = Bar<T, Never>`),
665+ // substitute the typealias definition's generic placeholders with the
666+ // concrete type arguments from the usage site.
667+ let resolvedGeneric : GenericType ?
668+ if let aliasTypealias = aliased. typealias,
669+ let aliasGeneric = aliasTypealias. typeName. generic,
670+ let usageSiteGeneric = typeName. generic {
671+ // Build a mapping from the typealias's placeholder names to the
672+ // concrete types at the usage site.
673+ // e.g. typealias CurrentValuePublisher<Value> = AnyPublisher<Value, Never>
674+ // usage: CurrentValuePublisher<[MyDataModel]?>
675+ // → mapping: ["Value": "[MyDataModel]?"]
676+ //
677+ // The alias name (e.g. "CurrentValuePublisher") has the same number
678+ // of generic parameters as the usage site. To find the placeholder
679+ // names we look at the alias definition's *own* generic parameter
680+ // list. Sourcery stores the alias `typeName` as the RHS
681+ // (e.g. `AnyPublisher<Value, Never>`), so the placeholder names
682+ // appear as type-parameter names in the RHS that match the LHS
683+ // parameter names. Since Sourcery does not store the LHS generic
684+ // parameter list on `Typealias`, we infer them: any type-parameter
685+ // in the RHS generic that is NOT a known type is a placeholder.
686+ //
687+ // A simpler and more reliable heuristic: pair up the usage-site
688+ // parameters with the alias name's parameters positionally —
689+ // both have the same arity.
690+ var placeholderMapping = [ String: TypeName] ( )
691+ for (index, usageParam) in usageSiteGeneric. typeParameters. enumerated ( ) {
692+ // The placeholder name at this position is inferred from the
693+ // alias RHS by checking which RHS parameters are *not* concrete
694+ // types. However the most robust approach is to note that the
695+ // LHS generic parameter at position `index` corresponds to the
696+ // usage-site parameter at the same position.
697+ //
698+ // We scan the RHS generic for parameters whose typeName matches
699+ // no known type and build the mapping.
700+ // For safety, just iterate and map positionally if within bounds.
701+ if index < aliasGeneric. typeParameters. count {
702+ let rhsParam = aliasGeneric. typeParameters [ index]
703+ let rhsName = rhsParam. typeName. unwrappedTypeName
704+ // Check if this RHS parameter is a placeholder (not a known type)
705+ if typeMap [ rhsName] == nil && resolvedTypealiases [ rhsName] == nil {
706+ placeholderMapping [ rhsName] = usageParam. typeName
707+ }
708+ }
709+ }
710+
711+ if !placeholderMapping. isEmpty {
712+ // Substitute placeholders in the RHS generic type parameters
713+ let substitutedParams = aliasGeneric. typeParameters. map { param -> GenericTypeParameter in
714+ let paramName = param. typeName. unwrappedTypeName
715+ if let concreteTypeName = placeholderMapping [ paramName] {
716+ return GenericTypeParameter ( typeName: concreteTypeName, type: param. type)
717+ }
718+ return param
719+ }
720+ resolvedGeneric = GenericType ( name: aliasGeneric. name, typeParameters: substitutedParams)
721+ } else {
722+ resolvedGeneric = aliasGeneric
723+ }
724+ } else {
725+ resolvedGeneric = aliased. typealias? . typeName. generic ?? generic
726+ }
727+
664728 return TypeName ( name: aliased. name,
665729 isOptional: typeName. isOptional,
666730 isImplicitlyUnwrappedOptional: typeName. isImplicitlyUnwrappedOptional,
@@ -669,7 +733,7 @@ internal struct ParserResultsComposed {
669733 dictionary: aliased. typealias? . typeName. dictionary ?? dictionary,
670734 closure: aliased. typealias? . typeName. closure ?? typeName. closure,
671735 set: aliased. typealias? . typeName. set ?? set,
672- generic: aliased . typealias ? . typeName . generic ?? generic
736+ generic: resolvedGeneric
673737 )
674738 }
675739
0 commit comments