Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
8 changes: 6 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6842,7 +6842,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return factory.createThisTypeNode();
}

if (!inTypeAlias && type.aliasSymbol && (context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope || isTypeSymbolAccessible(type.aliasSymbol, context.enclosingDeclaration))) {
// When in an object type literal context, expand type aliases to show resolved types
// for better clarity in hover information and quick info
Copy link

Copilot AI Oct 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment describes expanding type aliases but the actual condition checks for !(context.flags & NodeBuilderFlags.InObjectTypeLiteral) which means the expansion is avoided when in object type literal context. The comment should clarify that type aliases are expanded unless we're in an object type literal context, or the logic may be inverted from the intended behavior.

Suggested change
// When in an object type literal context, expand type aliases to show resolved types
// for better clarity in hover information and quick info
// Expand type aliases to show resolved types for better clarity in hover information and quick info,
// unless we're in an object type literal context.

Copilot uses AI. Check for mistakes.
if (!inTypeAlias && type.aliasSymbol && !(context.flags & NodeBuilderFlags.InObjectTypeLiteral) && (context.flags & NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope || isTypeSymbolAccessible(type.aliasSymbol, context.enclosingDeclaration))) {
if (!shouldExpandType(type, context, /*isAlias*/ true)) {
const typeArgumentNodes = mapToTypeNodes(type.aliasTypeArguments, context);
if (isReservedMemberName(type.aliasSymbol.escapedName) && !(type.aliasSymbol.flags & SymbolFlags.Class)) return factory.createTypeReferenceNode(factory.createIdentifier(""), typeArgumentNodes);
Expand Down Expand Up @@ -9028,7 +9030,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
let result;
const addUndefinedForParameter = declaration && (isParameter(declaration) || isJSDocParameterTag(declaration)) && requiresAddingImplicitUndefined(declaration, context.enclosingDeclaration);
const decl = declaration ?? symbol.valueDeclaration ?? getDeclarationWithTypeAnnotation(symbol) ?? symbol.declarations?.[0];
if (!canPossiblyExpandType(type, context) && decl) {
// When in an object type literal context, prefer the resolved type over the syntactic form
// to ensure indexed access types and generic type aliases are properly resolved
Copy link

Copilot AI Oct 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment states we prefer resolved types when in object type literal context, but the code condition !(context.flags & NodeBuilderFlags.InObjectTypeLiteral) means the syntactic form is avoided when we're NOT in object type literal context. The comment contradicts the code logic.

Suggested change
// When in an object type literal context, prefer the resolved type over the syntactic form
// to ensure indexed access types and generic type aliases are properly resolved
// When NOT in an object type literal context, prefer the syntactic form over the resolved type
// to preserve original type annotations where possible

Copilot uses AI. Check for mistakes.
if (!canPossiblyExpandType(type, context) && decl && !(context.flags & NodeBuilderFlags.InObjectTypeLiteral)) {
const restore = addSymbolTypeToContext(context, symbol, type);
if (isAccessor(decl)) {
result = syntacticNodeBuilder.serializeTypeOfAccessor(decl, symbol, context);
Expand Down
40 changes: 40 additions & 0 deletions tests/cases/fourslash/quickInfoIndexedAccessWithGeneric.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/// <reference path='fourslash.ts'/>

// @strict: true

// @Filename: /test.ts
////type Scalars = {
//// Number: number;
////};
////
////type Maybe<T> = T | null;
////
////type TypeA = {
//// a: Scalars["Number"] | null;
//// b: Maybe<number>;
//// c: Scalars["Number"] | null;
////};
////
////type TypeB = {
//// a: Scalars["Number"] | null;
//// b: number | null;
//// c: Scalars["Number"] | null;
////};
////
////const testA: Type/*typeA*/A = null!;
////const testB: Type/*typeB*/B = null!;

// Both types should show fully resolved types (number | null) for all fields
goTo.marker("typeA");
verify.quickInfoIs(`type TypeA = {
a: number | null;
b: number | null;
c: number | null;
}`);

goTo.marker("typeB");
verify.quickInfoIs(`type TypeB = {
a: number | null;
b: number | null;
c: number | null;
}`);
Loading