Skip to content
Open
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
70 changes: 70 additions & 0 deletions internal/checker/inference.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,10 @@ func (c *Checker) inferFromTypes(n *InferenceState, source *Type, target *Type)
case source.flags&TypeFlagsIndexedAccess != 0 && target.flags&TypeFlagsIndexedAccess != 0:
c.inferFromTypes(n, source.AsIndexedAccessType().objectType, target.AsIndexedAccessType().objectType)
c.inferFromTypes(n, source.AsIndexedAccessType().indexType, target.AsIndexedAccessType().indexType)
case isLiteralType(source) && target.flags&TypeFlagsIndexedAccess != 0:
// Handle reverse inference: when source is a literal type and target is T['property'],
// try to infer T based on the constraint that T['property'] = source
c.inferFromLiteralToIndexedAccess(n, source, target.AsIndexedAccessType())
case source.flags&TypeFlagsStringMapping != 0 && target.flags&TypeFlagsStringMapping != 0:
if source.symbol == target.symbol {
c.inferFromTypes(n, source.AsStringMappingType().target, target.AsStringMappingType().target)
Expand Down Expand Up @@ -1605,3 +1609,69 @@ func (c *Checker) mergeInferences(target []*InferenceInfo, source []*InferenceIn
}
}
}

// inferFromLiteralToIndexedAccess implements a reverse inference algorithm for indexed access types.
// This function is used during type inference when a literal value is assigned to an indexed access type.
// It infers a type parameter from a literal assigned to an indexed access, e.g. T['type'] = 'Declaration'.
func (c *Checker) inferFromLiteralToIndexedAccess(n *InferenceState, source *Type, target *IndexedAccessType) {
// Only proceed if the object type is a type variable that we're inferring
objectType := target.objectType
if objectType.flags&TypeFlagsTypeVariable != 0 {
// Get the inference info for the type parameter
inference := getInferenceInfoForType(n, objectType)
if inference == nil || inference.isFixed {
return
}

// Get the constraint of the type parameter (e.g., ASTNode)
constraint := c.getBaseConstraintOfType(inference.typeParameter)
if constraint == nil {
return
}

// Only handle union constraints (discriminated unions)
if constraint.flags&TypeFlagsUnion == 0 {
return
}

// Look for a union member where the indexed access type matches the source literal
indexType := target.indexType
for _, unionMember := range constraint.Types() {
// Skip sentinel type used to block string inference
if unionMember == c.blockedStringType {
continue
}

// Try to get the type of the indexed property from this union member
memberIndexedType := c.getIndexedAccessType(unionMember, indexType)

// Skip if we can't resolve the indexed access
if memberIndexedType == nil || c.isErrorType(memberIndexedType) {
continue
}

// Check if this member's indexed property type matches our literal source
if c.isTypeIdenticalTo(source, memberIndexedType) {
// Found a match! Infer this union member as a candidate for the type parameter
candidate := unionMember

if n.priority < inference.priority {
inference.candidates = nil
inference.contraCandidates = nil
inference.topLevel = true
inference.priority = n.priority
}

if n.priority == inference.priority {
if !slices.Contains(inference.candidates, candidate) {
inference.candidates = append(inference.candidates, candidate)
clearCachedInferences(n.inferences)
}
}

n.inferencePriority = min(n.inferencePriority, n.priority)
return
}
}
}
}
72 changes: 72 additions & 0 deletions testdata/baselines/reference/compiler/cssTreeTypeInference.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//// [tests/cases/compiler/cssTreeTypeInference.ts] ////

//// [cssTreeTypeInference.ts]
// Simplified reproduction of css-tree type inference issue
// https://github.com/microsoft/typescript-go/issues/1727

interface Declaration {
type: 'Declaration';
property: string;
value: string;
}

interface Rule {
type: 'Rule';
selector: string;
children: Declaration[];
}

type ASTNode = Declaration | Rule;

interface WalkOptions<T extends ASTNode> {
visit: T['type'];
enter(node: T): void;
}

declare function walk<T extends ASTNode>(ast: ASTNode, options: WalkOptions<T>): void;

// Test case 1: Simple type inference
const ast: ASTNode = {
type: 'Declaration',
property: 'color',
value: 'red'
};

// This should infer node as Declaration type
walk(ast, {
visit: 'Declaration',
enter(node) {
console.log(node.property); // Should not error - node should be inferred as Declaration
},
});

// Test case 2: More complex scenario
declare const complexAst: Rule;

walk(complexAst, {
visit: 'Declaration',
enter(node) {
console.log(node.value); // Should infer node as Declaration
},
});

//// [cssTreeTypeInference.js]
// Test case 1: Simple type inference
const ast = {
type: 'Declaration',
property: 'color',
value: 'red'
};
// This should infer node as Declaration type
walk(ast, {
visit: 'Declaration',
enter(node) {
console.log(node.property); // Should not error - node should be inferred as Declaration
},
});
walk(complexAst, {
visit: 'Declaration',
enter(node) {
console.log(node.value); // Should infer node as Declaration
},
});
128 changes: 128 additions & 0 deletions testdata/baselines/reference/compiler/cssTreeTypeInference.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
//// [tests/cases/compiler/cssTreeTypeInference.ts] ////

=== cssTreeTypeInference.ts ===
// Simplified reproduction of css-tree type inference issue
// https://github.com/microsoft/typescript-go/issues/1727

interface Declaration {
>Declaration : Symbol(Declaration, Decl(cssTreeTypeInference.ts, 0, 0))

type: 'Declaration';
>type : Symbol(Declaration.type, Decl(cssTreeTypeInference.ts, 3, 23))

property: string;
>property : Symbol(Declaration.property, Decl(cssTreeTypeInference.ts, 4, 24))

value: string;
>value : Symbol(Declaration.value, Decl(cssTreeTypeInference.ts, 5, 21))
}

interface Rule {
>Rule : Symbol(Rule, Decl(cssTreeTypeInference.ts, 7, 1))

type: 'Rule';
>type : Symbol(Rule.type, Decl(cssTreeTypeInference.ts, 9, 16))

selector: string;
>selector : Symbol(Rule.selector, Decl(cssTreeTypeInference.ts, 10, 17))

children: Declaration[];
>children : Symbol(Rule.children, Decl(cssTreeTypeInference.ts, 11, 21))
>Declaration : Symbol(Declaration, Decl(cssTreeTypeInference.ts, 0, 0))
}

type ASTNode = Declaration | Rule;
>ASTNode : Symbol(ASTNode, Decl(cssTreeTypeInference.ts, 13, 1))
>Declaration : Symbol(Declaration, Decl(cssTreeTypeInference.ts, 0, 0))
>Rule : Symbol(Rule, Decl(cssTreeTypeInference.ts, 7, 1))

interface WalkOptions<T extends ASTNode> {
>WalkOptions : Symbol(WalkOptions, Decl(cssTreeTypeInference.ts, 15, 34))
>T : Symbol(T, Decl(cssTreeTypeInference.ts, 17, 22))
>ASTNode : Symbol(ASTNode, Decl(cssTreeTypeInference.ts, 13, 1))

visit: T['type'];
>visit : Symbol(WalkOptions.visit, Decl(cssTreeTypeInference.ts, 17, 42))
>T : Symbol(T, Decl(cssTreeTypeInference.ts, 17, 22))

enter(node: T): void;
>enter : Symbol(WalkOptions.enter, Decl(cssTreeTypeInference.ts, 18, 21))
>node : Symbol(node, Decl(cssTreeTypeInference.ts, 19, 10))
>T : Symbol(T, Decl(cssTreeTypeInference.ts, 17, 22))
}

declare function walk<T extends ASTNode>(ast: ASTNode, options: WalkOptions<T>): void;
>walk : Symbol(walk, Decl(cssTreeTypeInference.ts, 20, 1))
>T : Symbol(T, Decl(cssTreeTypeInference.ts, 22, 22))
>ASTNode : Symbol(ASTNode, Decl(cssTreeTypeInference.ts, 13, 1))
>ast : Symbol(ast, Decl(cssTreeTypeInference.ts, 22, 41))
>ASTNode : Symbol(ASTNode, Decl(cssTreeTypeInference.ts, 13, 1))
>options : Symbol(options, Decl(cssTreeTypeInference.ts, 22, 54))
>WalkOptions : Symbol(WalkOptions, Decl(cssTreeTypeInference.ts, 15, 34))
>T : Symbol(T, Decl(cssTreeTypeInference.ts, 22, 22))

// Test case 1: Simple type inference
const ast: ASTNode = {
>ast : Symbol(ast, Decl(cssTreeTypeInference.ts, 25, 5))
>ASTNode : Symbol(ASTNode, Decl(cssTreeTypeInference.ts, 13, 1))

type: 'Declaration',
>type : Symbol(type, Decl(cssTreeTypeInference.ts, 25, 22))

property: 'color',
>property : Symbol(property, Decl(cssTreeTypeInference.ts, 26, 24))

value: 'red'
>value : Symbol(value, Decl(cssTreeTypeInference.ts, 27, 22))

};

// This should infer node as Declaration type
walk(ast, {
>walk : Symbol(walk, Decl(cssTreeTypeInference.ts, 20, 1))
>ast : Symbol(ast, Decl(cssTreeTypeInference.ts, 25, 5))

visit: 'Declaration',
>visit : Symbol(visit, Decl(cssTreeTypeInference.ts, 32, 11))

enter(node) {
>enter : Symbol(enter, Decl(cssTreeTypeInference.ts, 33, 25))
>node : Symbol(node, Decl(cssTreeTypeInference.ts, 34, 10))

console.log(node.property); // Should not error - node should be inferred as Declaration
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>node.property : Symbol(Declaration.property, Decl(cssTreeTypeInference.ts, 4, 24))
>node : Symbol(node, Decl(cssTreeTypeInference.ts, 34, 10))
>property : Symbol(Declaration.property, Decl(cssTreeTypeInference.ts, 4, 24))

},
});

// Test case 2: More complex scenario
declare const complexAst: Rule;
>complexAst : Symbol(complexAst, Decl(cssTreeTypeInference.ts, 40, 13))
>Rule : Symbol(Rule, Decl(cssTreeTypeInference.ts, 7, 1))

walk(complexAst, {
>walk : Symbol(walk, Decl(cssTreeTypeInference.ts, 20, 1))
>complexAst : Symbol(complexAst, Decl(cssTreeTypeInference.ts, 40, 13))

visit: 'Declaration',
>visit : Symbol(visit, Decl(cssTreeTypeInference.ts, 42, 18))

enter(node) {
>enter : Symbol(enter, Decl(cssTreeTypeInference.ts, 43, 25))
>node : Symbol(node, Decl(cssTreeTypeInference.ts, 44, 10))

console.log(node.value); // Should infer node as Declaration
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>node.value : Symbol(Declaration.value, Decl(cssTreeTypeInference.ts, 5, 21))
>node : Symbol(node, Decl(cssTreeTypeInference.ts, 44, 10))
>value : Symbol(Declaration.value, Decl(cssTreeTypeInference.ts, 5, 21))

},
});
Loading
Loading