Skip to content

[Completion] Handle unbound generics in typealiases #80343

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

Merged
merged 1 commit into from
Apr 2, 2025
Merged
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
3 changes: 3 additions & 0 deletions include/swift/IDE/CompletionLookup.h
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,9 @@ class CompletionLookup final : public swift::VisibleDeclConsumer {
void addNominalTypeRef(const NominalTypeDecl *NTD, DeclVisibilityKind Reason,
DynamicLookupInfo dynamicLookupInfo);

Type getTypeAliasType(const TypeAliasDecl *TAD,
DynamicLookupInfo dynamicLookupInfo);

void addTypeAliasRef(const TypeAliasDecl *TAD, DeclVisibilityKind Reason,
DynamicLookupInfo dynamicLookupInfo);

Expand Down
52 changes: 40 additions & 12 deletions lib/IDE/CompletionLookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1755,6 +1755,45 @@ void CompletionLookup::addNominalTypeRef(const NominalTypeDecl *NTD,
Builder.setTypeContext(expectedTypeContext, CurrDeclContext);
}

Type CompletionLookup::getTypeAliasType(const TypeAliasDecl *TAD,
DynamicLookupInfo dynamicLookupInfo) {
// Substitute the base type for a nested typealias if needed.
auto ty = getTypeOfMember(TAD, dynamicLookupInfo);
auto *typeAliasTy = dyn_cast<TypeAliasType>(ty.getPointer());
if (!typeAliasTy)
return ty;

// If the underlying type has an error, prefer to print the full typealias,
// otherwise get the underlying type. We only want the direct underlying type,
// not the full desugared type, since that more faithfully reflects what's
// written in source.
Type underlyingTy = typeAliasTy->getSinglyDesugaredType();
if (underlyingTy->hasError())
return ty;

// The underlying type might be unbound for e.g:
//
// struct S<T> {}
// typealias X = S
//
// Introduce type parameters such that we print the underlying type as
// 'S<T>'. We only expect unbound generics at the top-level of a type-alias,
// they are rejected by type resolution in any other position.
//
// FIXME: This is a hack – using the declared interface type isn't correct
// since the generic parameters ought to be introduced at a higher depth,
// i.e we should be treating it as `typealias X<T> = S<T>`. Ideally this would
// be fixed by desugaring the unbound typealias during type resolution. For
// now this is fine though since we only use the resulting type for printing
// the type annotation; the type relation logic currently skips type
// parameters.
if (auto *UGT = underlyingTy->getAs<UnboundGenericType>())
underlyingTy = UGT->getDecl()->getDeclaredInterfaceType();

ASSERT(!underlyingTy->hasUnboundGenericType());
return underlyingTy;
}

void CompletionLookup::addTypeAliasRef(const TypeAliasDecl *TAD,
DeclVisibilityKind Reason,
DynamicLookupInfo dynamicLookupInfo) {
Expand All @@ -1764,18 +1803,7 @@ void CompletionLookup::addTypeAliasRef(const TypeAliasDecl *TAD,
Builder.setAssociatedDecl(TAD);
addLeadingDot(Builder);
addValueBaseName(Builder, TAD->getBaseName());

// Substitute the base type for a nested typealias if needed.
auto ty = getTypeOfMember(TAD, dynamicLookupInfo);

// If the underlying type has an error, prefer to print the full typealias,
// otherwise get the underlying type.
if (auto *TA = dyn_cast<TypeAliasType>(ty.getPointer())) {
auto underlyingTy = TA->getSinglyDesugaredType();
if (!underlyingTy->hasError())
ty = underlyingTy;
}
addTypeAnnotation(Builder, ty);
addTypeAnnotation(Builder, getTypeAliasType(TAD, dynamicLookupInfo));
}

void CompletionLookup::addGenericTypeParamRef(
Expand Down
36 changes: 36 additions & 0 deletions test/IDE/complete_rdar147789214.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// RUN: %batch-code-completion

// rdar://147789214 - Make sure we insert generic parameters for underlying type.

struct S<T> {}
typealias Foo = S
typealias Bar = Foo
typealias Baz<T> = S<T>
typealias Invalid = (S, S)

struct Q<T> {
struct S<U> {}
}
typealias Invalid2 = R<K>.S

let _: S = #^COMPLETE^#
// COMPLETE-DAG: Decl[TypeAlias]/CurrModule: Foo[#S<T>#]; name=Foo
// COMPLETE-DAG: Decl[TypeAlias]/CurrModule: Bar[#S<T>#]; name=Bar
// COMPLETE-DAG: Decl[TypeAlias]/CurrModule: Baz[#S<T>#]; name=Baz
// COMPLETE-DAG: Decl[TypeAlias]/CurrModule: Invalid[#Invalid#]; name=Invalid
// COMPLETE-DAG: Decl[TypeAlias]/CurrModule: Invalid2[#Invalid2#]; name=Invalid2

struct R<U> {
typealias X = S<U>
typealias Y = S
typealias Z<T> = S<T>

func foo() {
// TODO: Once we start comparing type relations for types with generic
// parameters, ideally 'Y' and 'Z' should be convertible, but not 'X'.
let _: S<Int> = #^COMPLETE_IN_TYPE^#
// COMPLETE_IN_TYPE-DAG: Decl[TypeAlias]/CurrNominal: X[#S<U>#]; name=X
// COMPLETE_IN_TYPE-DAG: Decl[TypeAlias]/CurrNominal: Y[#S<T>#]; name=Y
// COMPLETE_IN_TYPE-DAG: Decl[TypeAlias]/CurrNominal: Z[#S<T>#]; name=Z
}
}