Skip to content

Add checker speculation helper, use in overload resolution #57421

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

Closed
wants to merge 20 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
6c2105a
Heavy WIP
weswigham Jan 29, 2024
c507e37
Still a bit WIP, but most failing tests look like improvements now
weswigham Feb 14, 2024
eaa6845
These all seem like logical outcomes to the changes added
weswigham Feb 15, 2024
d258aa3
Add extra tests
weswigham Feb 15, 2024
fbf8ffe
Remove unneeded speculative cache registrations
weswigham Feb 15, 2024
d75610c
Revert removal of circularity error bypass
weswigham Feb 15, 2024
56a2026
Dont assume deferred type reference target types are resolved when we…
weswigham Feb 15, 2024
6d0dfbb
Remove whitespace
weswigham Feb 15, 2024
8a66709
Swap to lazy decorator+accessor-backed speculation machinery for link…
weswigham Feb 17, 2024
5196f05
Expedite and make consistent signature error reporting
weswigham Feb 29, 2024
0655150
Merge branch 'main' into speculation-helper-wip
weswigham Feb 29, 2024
e861368
Accept positive baseline diff...?
weswigham Feb 29, 2024
4b50e12
Run format
weswigham Feb 29, 2024
1beceeb
Backout unrelated change
weswigham Feb 29, 2024
8605b12
Backout no longer needed lib/target change
weswigham Feb 29, 2024
ada8221
Fix one class of the self-build errors
weswigham Mar 4, 2024
e92f50b
Now that we speculate, we can and should reset argCheckMode for every…
weswigham Mar 4, 2024
dd829c4
Use lazy speculatable map for relationship caches to reduce eager cop…
weswigham Mar 11, 2024
fb3cac4
Merge branch 'main' into speculation-helper-wip
weswigham Mar 11, 2024
bca2687
Sanity check assert, formatting
weswigham Mar 11, 2024
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
526 changes: 434 additions & 92 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

148 changes: 9 additions & 139 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
GetCanonicalFileName,
MapLike,
ModeAwareCache,
ModeAwareCacheKey,
ModuleResolutionCache,
MultiMap,
NodeFactoryFlags,
Expand All @@ -16,6 +15,7 @@ import {
ProgramBuildInfo,
SymlinkCache,
ThisContainer,
TransientSymbol,
} from "./_namespaces/ts";

// branded string type used to store absolute, normalized and canonicalized paths
Expand Down Expand Up @@ -5829,65 +5829,6 @@ export interface Symbol {
/** @internal */ assignmentDeclarationMembers?: Map<number, Declaration>; // detected late-bound assignment declarations associated with the symbol
}

// dprint-ignore
/** @internal */
export interface SymbolLinks {
_symbolLinksBrand: any;
immediateTarget?: Symbol; // Immediate target of an alias. May be another alias. Do not access directly, use `checker.getImmediateAliasedSymbol` instead.
aliasTarget?: Symbol, // Resolved (non-alias) target of an alias
target?: Symbol; // Original version of an instantiated symbol
type?: Type; // Type of value symbol
writeType?: Type; // Type of value symbol in write contexts
nameType?: Type; // Type associated with a late-bound symbol
uniqueESSymbolType?: Type; // UniqueESSymbol type for a symbol
declaredType?: Type; // Type of class, interface, enum, type alias, or type parameter
typeParameters?: TypeParameter[]; // Type parameters of type alias (undefined if non-generic)
outerTypeParameters?: TypeParameter[]; // Outer type parameters of anonymous object type
instantiations?: Map<string, Type>; // Instantiations of generic type alias (undefined if non-generic)
aliasSymbol?: Symbol; // Alias associated with generic type alias instantiation
aliasTypeArguments?: readonly Type[] // Alias type arguments (if any)
inferredClassSymbol?: Map<SymbolId, TransientSymbol>; // Symbol of an inferred ES5 constructor function
mapper?: TypeMapper; // Type mapper for instantiation alias
referenced?: boolean; // True if alias symbol has been referenced as a value that can be emitted
constEnumReferenced?: boolean; // True if alias symbol resolves to a const enum and is referenced as a value ('referenced' will be false)
containingType?: UnionOrIntersectionType; // Containing union or intersection type for synthetic property
leftSpread?: Symbol; // Left source for synthetic spread property
rightSpread?: Symbol; // Right source for synthetic spread property
syntheticOrigin?: Symbol; // For a property on a mapped or spread type, points back to the original property
isDiscriminantProperty?: boolean; // True if discriminant synthetic property
resolvedExports?: SymbolTable; // Resolved exports of module or combined early- and late-bound static members of a class.
resolvedMembers?: SymbolTable; // Combined early- and late-bound members of a symbol
exportsChecked?: boolean; // True if exports of external module have been checked
typeParametersChecked?: boolean; // True if type parameters of merged class and interface declarations have been checked.
isDeclarationWithCollidingName?: boolean; // True if symbol is block scoped redeclaration
bindingElement?: BindingElement; // Binding element associated with property symbol
exportsSomeValue?: boolean; // True if module exports some value (not just types)
enumKind?: EnumKind; // Enum declaration classification
originatingImport?: ImportDeclaration | ImportCall; // Import declaration which produced the symbol, present if the symbol is marked as uncallable but had call signatures in `resolveESModuleSymbol`
lateSymbol?: Symbol; // Late-bound symbol for a computed property
specifierCache?: Map<ModeAwareCacheKey, string>; // For symbols corresponding to external modules, a cache of incoming path -> module specifier name mappings
extendedContainers?: Symbol[]; // Containers (other than the parent) which this symbol is aliased in
extendedContainersByFile?: Map<NodeId, Symbol[]>; // Containers (other than the parent) which this symbol is aliased in
variances?: VarianceFlags[]; // Alias symbol type argument variance cache
deferralConstituents?: Type[]; // Calculated list of constituents for a deferred type
deferralWriteConstituents?: Type[]; // Constituents of a deferred `writeType`
deferralParent?: Type; // Source union/intersection of a deferred type
cjsExportMerged?: Symbol; // Version of the symbol with all non export= exports merged with the export= target
typeOnlyDeclaration?: TypeOnlyAliasDeclaration | false; // First resolved alias declaration that makes the symbol only usable in type constructs
typeOnlyExportStarMap?: Map<__String, ExportDeclaration & { readonly isTypeOnly: true, readonly moduleSpecifier: Expression }>; // Set on a module symbol when some of its exports were resolved through a 'export type * from "mod"' declaration
typeOnlyExportStarName?: __String; // Set to the name of the symbol re-exported by an 'export type *' declaration, when different from the symbol name
isConstructorDeclaredProperty?: boolean; // Property declared through 'this.x = ...' assignment in constructor
tupleLabelDeclaration?: NamedTupleMember | ParameterDeclaration; // Declaration associated with the tuple's label
accessibleChainCache?: Map<string, Symbol[] | undefined>;
filteredIndexSymbolCache?: Map<string, Symbol> //Symbol with applicable declarations
}

/** @internal */
export const enum EnumKind {
Numeric, // Numeric enum (each member has a TypeFlags.Enum type)
Literal, // Literal enum (each member has a TypeFlags.EnumLiteral type)
}

// dprint-ignore
/** @internal */
export const enum CheckFlags {
Expand Down Expand Up @@ -5918,39 +5859,6 @@ export const enum CheckFlags {
Partial = ReadPartial | WritePartial,
}

/** @internal */
export interface TransientSymbolLinks extends SymbolLinks {
checkFlags: CheckFlags;
}

/** @internal */
export interface TransientSymbol extends Symbol {
links: TransientSymbolLinks;
}

/** @internal */
export interface MappedSymbolLinks extends TransientSymbolLinks {
mappedType: MappedType;
keyType: Type;
}

/** @internal */
export interface MappedSymbol extends TransientSymbol {
links: MappedSymbolLinks;
}

/** @internal */
export interface ReverseMappedSymbolLinks extends TransientSymbolLinks {
propertyType: Type;
mappedType: MappedType;
constraintType: IndexType;
}

/** @internal */
export interface ReverseMappedSymbol extends TransientSymbol {
links: ReverseMappedSymbolLinks;
}

export const enum InternalSymbolName {
Call = "__call", // Call signatures
Constructor = "__constructor", // Constructor implementations
Expand Down Expand Up @@ -6031,52 +5939,6 @@ export const enum NodeCheckFlags {
InCheckIdentifier = 1 << 22,
}

// dprint-ignore
/** @internal */
export interface NodeLinks {
flags: NodeCheckFlags; // Set of flags specific to Node
resolvedType?: Type; // Cached type of type node
resolvedEnumType?: Type; // Cached constraint type from enum jsdoc tag
resolvedSignature?: Signature; // Cached signature of signature node or call expression
resolvedSymbol?: Symbol; // Cached name resolution result
resolvedIndexInfo?: IndexInfo; // Cached indexing info resolution result
effectsSignature?: Signature; // Signature with possible control flow effects
enumMemberValue?: string | number; // Constant value of enum member
isVisible?: boolean; // Is this node visible
containsArgumentsReference?: boolean; // Whether a function-like declaration contains an 'arguments' reference
hasReportedStatementInAmbientContext?: boolean; // Cache boolean if we report statements in ambient context
jsxFlags: JsxFlags; // flags for knowing what kind of element/attributes we're dealing with
resolvedJsxElementAttributesType?: Type; // resolved element attributes type of a JSX openinglike element
resolvedJsxElementAllAttributesType?: Type; // resolved all element attributes type of a JSX openinglike element
resolvedJSDocType?: Type; // Resolved type of a JSDoc type reference
switchTypes?: Type[]; // Cached array of switch case expression types
jsxNamespace?: Symbol | false; // Resolved jsx namespace symbol for this node
jsxImplicitImportContainer?: Symbol | false; // Resolved module symbol the implicit jsx import of this file should refer to
contextFreeType?: Type; // Cached context-free type used by the first pass of inference; used when a function's return is partially contextually sensitive
deferredNodes?: Set<Node>; // Set of nodes whose checking has been deferred
capturedBlockScopeBindings?: Symbol[]; // Block-scoped bindings captured beneath this part of an IterationStatement
outerTypeParameters?: TypeParameter[]; // Outer type parameters of anonymous object type
isExhaustive?: boolean | 0; // Is node an exhaustive switch statement (0 indicates in-process resolution)
skipDirectInference?: true; // Flag set by the API `getContextualType` call on a node when `Completions` is passed to force the checker to skip making inferences to a node's type
declarationRequiresScopeChange?: boolean; // Set by `useOuterVariableScopeInParameter` in checker when downlevel emit would change the name resolution scope inside of a parameter.
serializedTypes?: Map<string, SerializedTypeEntry>; // Collection of types serialized at this location
decoratorSignature?: Signature; // Signature for decorator as if invoked by the runtime.
spreadIndices?: { first: number | undefined, last: number | undefined }; // Indices of first and last spread elements in array literal
parameterInitializerContainsUndefined?: boolean; // True if this is a parameter declaration whose type annotation contains "undefined".
fakeScopeForSignatureDeclaration?: "params" | "typeParams"; // If present, this is a fake scope injected into an enclosing declaration chain.
assertionExpressionType?: Type; // Cached type of the expression of a type assertion
}

/** @internal */
export type TrackedSymbol = [symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags];
/** @internal */
export interface SerializedTypeEntry {
node: TypeNode;
truncating?: boolean;
addedLength: number;
trackedSymbols: readonly TrackedSymbol[] | undefined;
}

// dprint-ignore
export const enum TypeFlags {
Any = 1 << 0,
Expand Down Expand Up @@ -9768,6 +9630,14 @@ export interface DiagnosticCollection {
// Otherwise, returns all the diagnostics (global and file associated) in this collection.
getDiagnostics(): Diagnostic[];
getDiagnostics(fileName: string): DiagnosticWithLocation[];

checkpoint(): DiagnosticCollectionCheckpoint;
revert(position: DiagnosticCollectionCheckpoint): void;
}

/** @internal */
export interface DiagnosticCollectionCheckpoint {
__privateState: void; // Internal state saved is private to the collection implementation
}

// SyntaxKind.SyntaxList
Expand Down
36 changes: 34 additions & 2 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ import {
Diagnostic,
DiagnosticArguments,
DiagnosticCollection,
DiagnosticCollectionCheckpoint,
DiagnosticMessage,
DiagnosticMessageChain,
DiagnosticRelatedInformation,
Expand Down Expand Up @@ -5771,20 +5772,51 @@ export function getSemanticJsxChildren(children: readonly JsxChild[]) {
});
}

interface DiagnosticCollectionCheckpointInternals {
Copy link
Member Author

Choose a reason for hiding this comment

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

I don't really know if I need to have bothered making the save/restore mechanism for diagnostic collections this externally opaque, since they're @internal anyway.

__privateState?: void;
nonFileDiagnostics: SortedArray<Diagnostic>;
filesWithDiagnostics: SortedArray<string>;
fileDiagnostics: Map<string, SortedArray<DiagnosticWithLocation>>;
hasReadNonFileDiagnostics: boolean;
}

/** @internal */
export function createDiagnosticCollection(): DiagnosticCollection {
let nonFileDiagnostics = [] as Diagnostic[] as SortedArray<Diagnostic>; // See GH#19873
const filesWithDiagnostics = [] as string[] as SortedArray<string>;
const fileDiagnostics = new Map<string, SortedArray<DiagnosticWithLocation>>();
let filesWithDiagnostics = [] as string[] as SortedArray<string>;
let fileDiagnostics = new Map<string, SortedArray<DiagnosticWithLocation>>();
let hasReadNonFileDiagnostics = false;

return {
add,
lookup,
getGlobalDiagnostics,
getDiagnostics,
checkpoint,
revert
};

// TODO: Copying the state at time of checkpoint is slow to checkpoint and memory intensive (but fast to restore)
// - recording deltas on `add` after a checkpoint would be much better
function checkpoint(): DiagnosticCollectionCheckpoint {
const c: DiagnosticCollectionCheckpointInternals = {
nonFileDiagnostics: nonFileDiagnostics.slice() as SortedArray<Diagnostic>,
filesWithDiagnostics: filesWithDiagnostics.slice() as SortedArray<string>,
fileDiagnostics: new Map(map(arrayFrom(fileDiagnostics.entries()), ([k, v]) => [k, v.slice() as SortedArray<DiagnosticWithLocation>] as const)),
hasReadNonFileDiagnostics
};
return c as DiagnosticCollectionCheckpoint;
}

function revert(checkpoint: DiagnosticCollectionCheckpoint) {
({
nonFileDiagnostics,
filesWithDiagnostics,
fileDiagnostics,
hasReadNonFileDiagnostics,
} = checkpoint as DiagnosticCollectionCheckpointInternals);
}

function lookup(diagnostic: Diagnostic): Diagnostic | undefined {
let diagnostics: SortedArray<Diagnostic> | undefined;
if (diagnostic.file) {
Expand Down
4 changes: 2 additions & 2 deletions src/tsconfig-base.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
"outDir": "../built/local",

"pretty": true,
"lib": ["es2020"],
"target": "es2020",
"lib": ["es2021"],
"target": "es2021",
"module": "CommonJS",
"moduleResolution": "node",

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ var use: Overload;
use((req, res) => {});
>use((req, res) => {}) : void
>use : Overload
>(req, res) => {} : (req: any, res: any) => void
>req : any
>res : any
>(req, res) => {} : (req: number, res: number) => void
>req : number
>res : number

interface Overload {
(handler1: (req1: string) => void): void;
Expand All @@ -31,11 +31,11 @@ app.use((err: any, req, res, next) => { return; });
>app.use : IRouterHandler<MyApp> & IRouterMatcher<MyApp>
>app : MyApp
>use : IRouterHandler<MyApp> & IRouterMatcher<MyApp>
>(err: any, req, res, next) => { return; } : (err: any, req: any, res: any, next: any) => void
>(err: any, req, res, next) => { return; } : (err: any, req: Request, res: Response, next: NextFunction) => void
Copy link
Member Author

Choose a reason for hiding this comment

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

Previously, this'd get any argument types fixed by an earlier overload we didn't actually pick because of an arity mismatch - this is a pretty clear improvement!

>err : any
>req : any
>res : any
>next : any
>req : Request
>res : Response
>next : NextFunction


interface MyApp {
Expand Down
35 changes: 6 additions & 29 deletions tests/baselines/reference/destructuringTuple.errors.txt
Original file line number Diff line number Diff line change
@@ -1,18 +1,8 @@
destructuringTuple.ts(11,7): error TS2461: Type 'number' is not an array type.
destructuringTuple.ts(11,48): error TS2769: No overload matches this call.
Overload 1 of 3, '(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number, initialValue: number): number', gave the following error.
Type 'never[]' is not assignable to type 'number'.
Overload 2 of 3, '(callbackfn: (previousValue: [], currentValue: number, currentIndex: number, array: number[]) => [], initialValue: []): []', gave the following error.
Type 'never[]' is not assignable to type '[]'.
Target allows only 0 element(s) but source may have more.
destructuringTuple.ts(11,60): error TS2769: No overload matches this call.
Overload 1 of 2, '(...items: ConcatArray<never>[]): never[]', gave the following error.
Argument of type 'number' is not assignable to parameter of type 'ConcatArray<never>'.
Overload 2 of 2, '(...items: ConcatArray<never>[]): never[]', gave the following error.
Argument of type 'number' is not assignable to parameter of type 'ConcatArray<never>'.
destructuringTuple.ts(11,7): error TS2461: Type 'unknown' is not an array type.
destructuringTuple.ts(11,48): error TS18046: 'accu' is of type 'unknown'.


==== destructuringTuple.ts (3 errors) ====
==== destructuringTuple.ts (2 errors) ====
declare var tuple: [boolean, number, ...string[]];

const [a, b, c, ...rest] = tuple;
Expand All @@ -25,22 +15,9 @@ destructuringTuple.ts(11,60): error TS2769: No overload matches this call.

const [oops1] = [1, 2, 3].reduce((accu, el) => accu.concat(el), []);
~~~~~~~
!!! error TS2461: Type 'number' is not an array type.
~~~~~~~~~~~~~~~
!!! error TS2769: No overload matches this call.
!!! error TS2769: Overload 1 of 3, '(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: number[]) => number, initialValue: number): number', gave the following error.
!!! error TS2769: Type 'never[]' is not assignable to type 'number'.
!!! error TS2769: Overload 2 of 3, '(callbackfn: (previousValue: [], currentValue: number, currentIndex: number, array: number[]) => [], initialValue: []): []', gave the following error.
!!! error TS2769: Type 'never[]' is not assignable to type '[]'.
!!! error TS2769: Target allows only 0 element(s) but source may have more.
!!! related TS6502 lib.es5.d.ts:--:--: The expected type comes from the return type of this signature.
!!! related TS6502 lib.es5.d.ts:--:--: The expected type comes from the return type of this signature.
~~
!!! error TS2769: No overload matches this call.
!!! error TS2769: Overload 1 of 2, '(...items: ConcatArray<never>[]): never[]', gave the following error.
!!! error TS2769: Argument of type 'number' is not assignable to parameter of type 'ConcatArray<never>'.
!!! error TS2769: Overload 2 of 2, '(...items: ConcatArray<never>[]): never[]', gave the following error.
!!! error TS2769: Argument of type 'number' is not assignable to parameter of type 'ConcatArray<never>'.
!!! error TS2461: Type 'unknown' is not an array type.
~~~~
!!! error TS18046: 'accu' is of type 'unknown'.
Copy link
Member Author

Choose a reason for hiding this comment

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

I'm wishy washy on this one. Previously, we'd fix the first parameter to [] at the end of the first overload's checking, and then witness it while checking the second overload and that'd produce these errors. Now, since both overloads fail, we fall back to the constraint on the type parameter (unknown), which produces different errors.


const [oops2] = [1, 2, 3].reduce((acc: number[], e) => acc.concat(e), []);

2 changes: 0 additions & 2 deletions tests/baselines/reference/destructuringTuple.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ const [oops1] = [1, 2, 3].reduce((accu, el) => accu.concat(el), []);
>reduce : Symbol(Array.reduce, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>accu : Symbol(accu, Decl(destructuringTuple.ts, 10, 34))
>el : Symbol(el, Decl(destructuringTuple.ts, 10, 39))
>accu.concat : Symbol(Array.concat, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>accu : Symbol(accu, Decl(destructuringTuple.ts, 10, 34))
>concat : Symbol(Array.concat, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>el : Symbol(el, Decl(destructuringTuple.ts, 10, 39))

const [oops2] = [1, 2, 3].reduce((acc: number[], e) => acc.concat(e), []);
Expand Down
Loading