Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e90edb3
Improve inference for context sensitive functions within reverse mapp…
Andarist Apr 25, 2023
557cd99
use more specialized `Debug.assertNode`
Andarist Apr 26, 2023
4774f8f
Merge remote-tracking branch 'origin/main' into intra-inference-in-re…
Andarist May 24, 2023
b7eae05
check freshness of the source type before attempting to infer from in…
Andarist May 24, 2023
a0804b5
get value declaration from the passed in argument
Andarist May 25, 2023
57b54c9
Ignore `valueDeclaration` in `getDeclarationModifierFlagsFromSymbol` …
Andarist May 25, 2023
fae5ae6
Add an extra test case for array literals used as values of object re…
Andarist May 25, 2023
34d675a
Merge remote-tracking branch 'origin/main' into intra-inference-in-re…
Andarist May 25, 2023
a8e8a24
Merge remote-tracking branch 'origin/main' into intra-inference-in-re…
Andarist Jun 12, 2023
d71dcd2
Merge remote-tracking branch 'origin/main' into intra-inference-in-re…
Andarist Nov 27, 2025
5b33846
refactor
Andarist Nov 29, 2025
cca600f
add comments
Andarist Nov 29, 2025
e7e9125
fmt
Andarist Nov 29, 2025
5086dfa
fix cache
Andarist Nov 29, 2025
b5c6ef4
add extra tests
Andarist Nov 29, 2025
9e748b7
prefer inferring from non-context sensitive type
Andarist Dec 4, 2025
f38ceb3
use a different approach to link partial reverse mapped type with the…
Andarist Dec 5, 2025
2724523
Merge remote-tracking branch 'origin/main' into intra-inference-in-re…
Andarist Dec 5, 2025
ec14355
Merge remote-tracking branch 'origin/main' into intra-inference-in-re…
Andarist Dec 9, 2025
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
154 changes: 114 additions & 40 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6153,6 +6153,7 @@ export interface MappedSymbol extends TransientSymbol {

/** @internal */
export interface ReverseMappedSymbolLinks extends TransientSymbolLinks {
reverseMappedType: ReverseMappedType;
propertyType: Type;
mappedType: MappedType;
constraintType: IndexType;
Expand Down Expand Up @@ -7156,6 +7157,8 @@ export interface InferenceContext {
outerReturnMapper?: TypeMapper; // Type mapper for inferences from return types of outer function (if any)
inferredTypeParameters?: readonly TypeParameter[]; // Inferred type parameters for function result
intraExpressionInferenceSites?: IntraExpressionInferenceSite[];
reverseMappedIntraExpressionInferenceScopeNodes?: Node[];
reverseMappedIntraExpressionInferenceSites?: IntraExpressionInferenceSite[][];
}

/** @internal */
Expand Down
5 changes: 3 additions & 2 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8002,13 +8002,14 @@ export function getCheckFlags(symbol: Symbol): CheckFlags {

/** @internal */
export function getDeclarationModifierFlagsFromSymbol(s: Symbol, isWrite = false): ModifierFlags {
if (s.valueDeclaration) {
const checkFlags = getCheckFlags(s);
if (!(checkFlags & CheckFlags.ReverseMapped) && s.valueDeclaration) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Since I added .valueDeclaration to reverse mapped properties, we need to ignore this here - otherwise, we break stripping of readonly modifiers that is required by #12589

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Basically, this change just maintains compatibility with the old behavior as 0 was always returned from here in the implicit case of checkFlags & CheckFlags.ReverseMapped

const declaration = (isWrite && s.declarations && find(s.declarations, isSetAccessorDeclaration))
|| (s.flags & SymbolFlags.GetAccessor && find(s.declarations, isGetAccessorDeclaration)) || s.valueDeclaration;
const flags = getCombinedModifierFlags(declaration);
return s.parent && s.parent.flags & SymbolFlags.Class ? flags : flags & ~ModifierFlags.AccessibilityModifier;
}
if (getCheckFlags(s) & CheckFlags.Synthetic) {
if (checkFlags & CheckFlags.Synthetic) {
// NOTE: potentially unchecked cast to TransientSymbol
const checkFlags = (s as TransientSymbol).links.checkFlags;
const accessModifier = checkFlags & CheckFlags.ContainsPrivate ? ModifierFlags.Private :
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,310 @@
intraExpressionInferencesReverseMappedTypes.ts(67,21): error TS18046: 'x' is of type 'unknown'.
intraExpressionInferencesReverseMappedTypes.ts(71,21): error TS18046: 'x' is of type 'unknown'.
intraExpressionInferencesReverseMappedTypes.ts(80,21): error TS18046: 'x' is of type 'unknown'.
intraExpressionInferencesReverseMappedTypes.ts(86,21): error TS18046: 'x' is of type 'unknown'.
intraExpressionInferencesReverseMappedTypes.ts(95,21): error TS18046: 'x' is of type 'unknown'.
intraExpressionInferencesReverseMappedTypes.ts(101,21): error TS18046: 'x' is of type 'unknown'.


==== intraExpressionInferencesReverseMappedTypes.ts (6 errors) ====
// repro cases based on https://github.com/microsoft/TypeScript/issues/53018

declare function f<T>(
arg: {
[K in keyof T]: {
produce: (n: string) => T[K];
consume: (x: T[K]) => void;
};
}
): T;

const res1 = f({
a: {
produce: (n) => n,
consume: (x) => x.toLowerCase(),
},
b: {
produce: (n) => ({ v: n }),
consume: (x) => x.v.toLowerCase(),
},
});

const res2 = f({
a: {
produce: function () {
return "hello";
},
consume: (x) => x.toLowerCase(),
},
b: {
produce: function () {
return { v: "hello" };
},
consume: (x) => x.v.toLowerCase(),
},
});

const res3 = f({
a: {
produce() {
return "hello";
},
consume: (x) => x.toLowerCase(),
},
b: {
produce() {
return { v: "hello" };
},
consume: (x) => x.v.toLowerCase(),
},
});

declare function f2<T extends unknown[]>(
arg: [
...{
[K in keyof T]: {
produce: (n: string) => T[K];
consume: (x: T[K]) => void;
};
}
]
): T;

const res4 = f2([
{
produce: (n) => n,
consume: (x) => x.toLowerCase(),
~
!!! error TS18046: 'x' is of type 'unknown'.
},
{
produce: (n) => ({ v: n }),
consume: (x) => x.v.toLowerCase(),
~
!!! error TS18046: 'x' is of type 'unknown'.
},
]);

const res5 = f2([
{
produce: function () {
return "hello";
},
consume: (x) => x.toLowerCase(),
~
!!! error TS18046: 'x' is of type 'unknown'.
},
{
produce: function () {
return { v: "hello" };
},
consume: (x) => x.v.toLowerCase(),
~
!!! error TS18046: 'x' is of type 'unknown'.
},
]);

const res6 = f2([
{
produce() {
return "hello";
},
consume: (x) => x.toLowerCase(),
~
!!! error TS18046: 'x' is of type 'unknown'.
},
{
produce() {
return { v: "hello" };
},
consume: (x) => x.v.toLowerCase(),
~
!!! error TS18046: 'x' is of type 'unknown'.
},
]);

declare function f3<T>(
arg: {
[K in keyof T]: {
other: number,
produce: (n: string) => T[K];
consume: (x: T[K]) => void;
};
}
): T;

const res7 = f3({
a: {
other: 42,
produce: (n) => n,
consume: (x) => x.toLowerCase(),
},
b: {
other: 100,
produce: (n) => ({ v: n }),
consume: (x) => x.v.toLowerCase(),
},
});

declare function f4<T>(
arg: {
[K in keyof T]: [
(n: string) => T[K],
(x: T[K]) => void
];
}
): T;

const res8 = f4({
a: [
(n) => n,
(x) => x.toLowerCase(),
],
b: [
(n) => ({ v: n }),
(x) => x.v.toLowerCase(),
],
});

declare function f5<T1, T2>(
arg: {
[K in keyof T1]: {
produce1: (n: string) => T1[K];
consume1: (x: T1[K]) => void;
};
} & {
[K in keyof T2]: {
produce2: (n: string) => T2[K];
consume2: (x: T2[K]) => void;
};
},
): [T1, T2];

const res9 = f5({
a: {
produce1: (n) => n,
consume1: (x) => x.toLowerCase(),
produce2: (n) => [n],
consume2: (x) => x[0].toLowerCase(),
},
b: {
produce1: (n) => ({ v: n }),
consume1: (x) => x.v.toLowerCase(),
produce2: (n) => ({ v: [n] }),
consume2: (x) => x.v[0].toLowerCase(),
},
});

declare function f6<T>(arg: {
[K in keyof T]: () => {
produce: (n: string) => T[K];
consume: (x: T[K]) => void;
};
}): T;

const res10 = f6({
a() {
return {
produce: (n) => n,
consume: (x) => x.toLowerCase(),
};
},
b() {
return {
produce: (n) => ({ v: n }),
consume: (x) => x.v.toLowerCase(),
};
},
});

const res11 = f6({
a: () => {
return {
produce: (n) => n,
consume: (x) => x.toLowerCase(),
};
},
b: () => {
return {
produce: (n) => ({ v: n }),
consume: (x) => x.v.toLowerCase(),
};
},
});

declare function f7<T>(arg: {
[K in keyof T]: (arg: boolean) => {
produce: (n: string) => T[K];
consume: (x: T[K]) => void;
};
}): T;

const res12 = f7({
a(arg) {
return {
produce: (n) => n,
consume: (x) => x.toLowerCase(),
};
},
b(arg) {
return {
produce: (n) => ({ v: n }),
consume: (x) => x.v.toLowerCase(),
};
},
});

const res13 = f7({
a: (arg) => {
return {
produce: (n) => n,
consume: (x) => x.toLowerCase(),
};
},
b: (arg) => {
return {
produce: (n) => ({ v: n }),
consume: (x) => x.v.toLowerCase(),
};
},
});

declare function f8<T>(arg: {
[K in keyof T]: () => [(n: string) => T[K], (x: T[K]) => void];
}): T;

const res14 = f8({
a() {
return [(n) => n, (x) => x.toLowerCase()];
},
b() {
return [(n) => ({ v: n }), (x) => x.v.toLowerCase()];
},
});

declare function f9<T1, T2>(
arg: {
[K in keyof T1]: {
produce: (n: string) => [T1[K], any];
consume: (x: T1[K]) => void;
};
} & {
a: {
produce: (n: string) => [any, T2];
consume2: (x: T2) => void;
};
},
): [T1, T2];

const res15 = f9({
a: {
produce: (n) => [n, [n]],
consume2: (x) => x[0].toLowerCase(),
consume: (x) => x.toLowerCase(),
},
b: {
produce: (n) => [{ v: n }, null],
consume: (x) => x.v.toLowerCase(),
},
});

Loading