From 3ae7cb22dc6da7d3a5762c1d1aabe14e71ef72c4 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 13 Jun 2024 07:14:09 -0700 Subject: [PATCH] Implement 'deferred' modifier, remove Deferred marker type --- src/compiler/checker.ts | 50 ++++++++++++--------------- src/compiler/diagnosticMessages.json | 8 +++++ src/compiler/factory/nodeFactory.ts | 2 ++ src/compiler/program.ts | 1 + src/compiler/scanner.ts | 1 + src/compiler/transformers/ts.ts | 1 + src/compiler/types.ts | 17 +++++---- src/compiler/utilities.ts | 2 ++ src/compiler/utilitiesPublic.ts | 1 + src/harness/fourslashInterfaceImpl.ts | 1 - src/lib/es5.d.ts | 5 --- 11 files changed, 50 insertions(+), 39 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0a2a2deb6cf..a7a0727e7d8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1396,7 +1396,6 @@ const enum IntrinsicTypeKind { Capitalize, Uncapitalize, NoInfer, - Deferred, } const intrinsicTypeKinds: ReadonlyMap = new Map(Object.entries({ @@ -1405,7 +1404,6 @@ const intrinsicTypeKinds: ReadonlyMap = new Map(Objec Capitalize: IntrinsicTypeKind.Capitalize, Uncapitalize: IntrinsicTypeKind.Uncapitalize, NoInfer: IntrinsicTypeKind.NoInfer, - Deferred: IntrinsicTypeKind.Deferred, })); const SymbolLinks = class implements SymbolLinks { @@ -16105,9 +16103,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (type === intrinsicMarkerType) { const typeKind = intrinsicTypeKinds.get(symbol.escapedName as string); if (typeKind !== undefined && typeArguments && typeArguments.length === 1) { - return typeKind === IntrinsicTypeKind.NoInfer ? getNoInferType(typeArguments[0]) : - typeKind === IntrinsicTypeKind.Deferred ? getDeferredCallbackType(typeArguments[0], aliasSymbol, aliasTypeArguments) : - getStringMappingType(symbol, typeArguments[0]); + return typeKind === IntrinsicTypeKind.NoInfer ? getNoInferType(typeArguments[0]) : getStringMappingType(symbol, typeArguments[0]); } } const links = getSymbolLinks(symbol); @@ -16120,27 +16116,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return instantiation; } - function getDeferredCallbackType(type: Type, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) { - if (type.flags & TypeFlags.Object) { - const key = `F${getTypeId(type)}${getAliasId(aliasSymbol, aliasTypeArguments)}`; - return getCachedType(key) ?? setCachedType(key, createDeferredCallbackType(type as ObjectType, aliasSymbol, aliasTypeArguments)); - } - return type; - } - - function createDeferredCallbackType(type: ObjectType, aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined) { - const resolved = resolveStructuredTypeMembers(type); - const result = createObjectType(ObjectFlags.Anonymous | ObjectFlags.DeferredCallback, type.symbol); - result.members = resolved.members; - result.properties = resolved.properties; - result.callSignatures = resolved.callSignatures; - result.constructSignatures = resolved.constructSignatures; - result.indexInfos = resolved.indexInfos; - result.aliasSymbol = aliasSymbol; - result.aliasTypeArguments = aliasTypeArguments; - return result; - } - /** * Get type from reference to type alias. When a type alias is generic, the declared type of the type alias may include * references to the type parameters of the alias. We replace those with the actual type arguments by instantiating the @@ -28190,7 +28165,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // circularities. Instead, we simply check if any signature has a deferred callback marker in the // particular argument position. signatures ??= getSignaturesOfType(getTypeOfExpression(flow.node.expression), SignatureKind.Call); - if (!some(signatures, sig => !!(getObjectFlags(getTypeAtPosition(sig, i)) & ObjectFlags.DeferredCallback))) { + if (!some(signatures, sig => !!(getModifiersAtPosition(sig, i) & ModifierFlags.Deferred))) { const lambdaType = getTypeFromFlowType(getTypeAtFlowNode(lambda.returnFlowNode)); if (lambdaType !== initialType) { lambdaTypes ??= [initialType]; @@ -37257,6 +37232,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return elementType && isTypeAny(elementType) ? anyType : restType; } + function getModifiersAtPosition(signature: Signature, pos: number) { + const index = pos < signature.parameters.length ? pos : signatureHasRestParameter(signature) ? signature.parameters.length - 1 : -1; + return index >= 0 ? getDeclarationModifierFlagsFromSymbol(signature.parameters[index]) : ModifierFlags.None; + } + // Return the number of parameters in a signature. The rest parameter, if present, counts as one // parameter. For example, the parameter count of (x: number, y: number, ...z: string[]) is 3 and // the parameter count of (x: number, ...args: [number, ...string[], boolean])) is also 3. In the @@ -40570,6 +40550,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { error(node.name, Diagnostics.constructor_cannot_be_used_as_a_parameter_property_name); } } + if (hasSyntacticModifier(node, ModifierFlags.Deferred)) { + const funcType = node.dotDotDotToken ? createArrayType(globalFunctionType, /*readonly*/ true) : globalFunctionType; + if (!isTypeAssignableTo(getTypeOfSymbol(node.symbol), funcType)) { + error(node, Diagnostics.A_deferred_parameter_must_have_a_function_type); + } + } if (!node.initializer && isOptionalDeclaration(node) && isBindingPattern(node.name) && (func as FunctionLikeDeclaration).body) { error(node, Diagnostics.A_binding_pattern_parameter_cannot_be_optional_in_an_implementation_signature); } @@ -50151,6 +50137,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { flags |= inOutFlag; break; } + + case SyntaxKind.DeferredKeyword: + if (node.kind !== SyntaxKind.Parameter) { + return grammarErrorOnNode(modifier, Diagnostics.deferred_modifier_can_only_appear_on_a_parameter_declaration); + } + if (flags & ModifierFlags.Deferred) { + return grammarErrorOnNode(modifier, Diagnostics._0_modifier_already_seen, "deferred"); + } + flags |= ModifierFlags.Deferred; + break; } } } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index e74033719b5..e1570dd8fd5 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -971,6 +971,10 @@ "category": "Error", "code": 1293 }, + "'deferred' modifier can only appear on a parameter declaration.": { + "category": "Error", + "code": 1294 + }, "'with' statements are not allowed in an async function block.": { "category": "Error", @@ -3911,6 +3915,10 @@ "category": "Error", "code": 2868 }, + "A 'deferred' parameter must have a function type.": { + "category": "Error", + "code": 2869 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", diff --git a/src/compiler/factory/nodeFactory.ts b/src/compiler/factory/nodeFactory.ts index 9fcd2a4acf7..71f6f3cf13a 100644 --- a/src/compiler/factory/nodeFactory.ts +++ b/src/compiler/factory/nodeFactory.ts @@ -1471,6 +1471,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode case SyntaxKind.ReadonlyKeyword: case SyntaxKind.AbstractKeyword: case SyntaxKind.DeclareKeyword: + case SyntaxKind.DeferredKeyword: case SyntaxKind.ConstKeyword: case SyntaxKind.AnyKeyword: case SyntaxKind.NumberKeyword: @@ -1566,6 +1567,7 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode if (flags & ModifierFlags.Async) result.push(createModifier(SyntaxKind.AsyncKeyword)); if (flags & ModifierFlags.In) result.push(createModifier(SyntaxKind.InKeyword)); if (flags & ModifierFlags.Out) result.push(createModifier(SyntaxKind.OutKeyword)); + if (flags & ModifierFlags.Deferred) result.push(createModifier(SyntaxKind.DeferredKeyword)); return result.length ? result : undefined; } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 09f4ed4ff6f..deec24a5505 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -3322,6 +3322,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg case SyntaxKind.ProtectedKeyword: case SyntaxKind.ReadonlyKeyword: case SyntaxKind.DeclareKeyword: + case SyntaxKind.DeferredKeyword: case SyntaxKind.AbstractKeyword: case SyntaxKind.OverrideKeyword: case SyntaxKind.InKeyword: diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index ad6928331f2..4387895c592 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -148,6 +148,7 @@ export const textToKeywordObj: MapLike = { debugger: SyntaxKind.DebuggerKeyword, declare: SyntaxKind.DeclareKeyword, default: SyntaxKind.DefaultKeyword, + deferred: SyntaxKind.DeferredKeyword, delete: SyntaxKind.DeleteKeyword, do: SyntaxKind.DoKeyword, else: SyntaxKind.ElseKeyword, diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 668da90d8e6..d4735af6db5 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -661,6 +661,7 @@ export function transformTypeScript(context: TransformationContext) { case SyntaxKind.OverrideKeyword: case SyntaxKind.ConstKeyword: case SyntaxKind.DeclareKeyword: + case SyntaxKind.DeferredKeyword: case SyntaxKind.ReadonlyKeyword: case SyntaxKind.InKeyword: case SyntaxKind.OutKeyword: diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 6f69adc8675..76ae32f94b4 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -196,6 +196,7 @@ export const enum SyntaxKind { BooleanKeyword, ConstructorKeyword, DeclareKeyword, + DeferredKeyword, GetKeyword, InferKeyword, IntrinsicKeyword, @@ -598,6 +599,7 @@ export type KeywordSyntaxKind = | SyntaxKind.DebuggerKeyword | SyntaxKind.DeclareKeyword | SyntaxKind.DefaultKeyword + | SyntaxKind.DeferredKeyword | SyntaxKind.DeleteKeyword | SyntaxKind.DoKeyword | SyntaxKind.ElseKeyword @@ -669,6 +671,7 @@ export type ModifierSyntaxKind = | SyntaxKind.ConstKeyword | SyntaxKind.DeclareKeyword | SyntaxKind.DefaultKeyword + | SyntaxKind.DeferredKeyword | SyntaxKind.ExportKeyword | SyntaxKind.InKeyword | SyntaxKind.PrivateKeyword @@ -865,10 +868,11 @@ export const enum ModifierFlags { Const = 1 << 12, // Const enum In = 1 << 13, // Contravariance modifier Out = 1 << 14, // Covariance modifier - Decorator = 1 << 15, // Contains a decorator. + Deferred = 1 << 15, // Parameter + Decorator = 1 << 16, // Contains a decorator. // JSDoc-only modifiers - Deprecated = 1 << 16, // Deprecated tag. + Deprecated = 1 << 17, // Deprecated tag. // Cache-only JSDoc-modifiers. Should match order of Syntactic/JSDoc modifiers, above. /** @internal */ JSDocPublic = 1 << 23, // if this value changes, `selectEffectiveModifierFlags` must change accordingly @@ -878,7 +882,7 @@ export const enum ModifierFlags { /** @internal */ JSDocOverride = 1 << 27, /** @internal */ SyntacticOrJSDocModifiers = Public | Private | Protected | Readonly | Override, - /** @internal */ SyntacticOnlyModifiers = Export | Ambient | Abstract | Static | Accessor | Async | Default | Const | In | Out | Decorator, + /** @internal */ SyntacticOnlyModifiers = Export | Ambient | Abstract | Static | Accessor | Async | Default | Const | In | Out | Deferred | Decorator, /** @internal */ SyntacticModifiers = SyntacticOrJSDocModifiers | SyntacticOnlyModifiers, /** @internal */ JSDocCacheOnlyModifiers = JSDocPublic | JSDocPrivate | JSDocProtected | JSDocReadonly | JSDocOverride, /** @internal */ JSDocOnlyModifiers = Deprecated, @@ -892,9 +896,9 @@ export const enum ModifierFlags { ParameterPropertyModifier = AccessibilityModifier | Readonly | Override, NonPublicAccessibilityModifier = Private | Protected, - TypeScriptModifier = Ambient | Public | Private | Protected | Readonly | Abstract | Const | Override | In | Out, + TypeScriptModifier = Ambient | Public | Private | Protected | Readonly | Abstract | Const | Override | In | Out | Deferred, ExportDefault = Export | Default, - All = Export | Ambient | Public | Private | Protected | Static | Readonly | Abstract | Accessor | Async | Default | Const | Deprecated | Override | In | Out | Decorator, + All = Export | Ambient | Public | Private | Protected | Static | Readonly | Abstract | Accessor | Async | Default | Const | Deprecated | Override | In | Out | Deferred | Decorator, Modifier = All & ~Decorator, } @@ -1620,6 +1624,7 @@ export type AsyncKeyword = ModifierToken; export type ConstKeyword = ModifierToken; export type DeclareKeyword = ModifierToken; export type DefaultKeyword = ModifierToken; +export type DeferredKeyword = ModifierToken; export type ExportKeyword = ModifierToken; export type InKeyword = ModifierToken; export type PrivateKeyword = ModifierToken; @@ -1637,6 +1642,7 @@ export type Modifier = | ConstKeyword | DeclareKeyword | DefaultKeyword + | DeferredKeyword | ExportKeyword | InKeyword | PrivateKeyword @@ -6389,7 +6395,6 @@ export const enum ObjectFlags { /** @internal */ IdenticalBaseTypeExists = 1 << 26, // has a defined cachedEquivalentBaseType member SingleSignatureType = 1 << 27, // A single signature type extracted from a potentially broader type - DeferredCallback = 1 << 28, // Function type with Deferred marker // Flags that require TypeFlags.UnionOrIntersection or TypeFlags.Substitution /** @internal */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 918a7974c0c..04c2eb3b064 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -7159,6 +7159,8 @@ export function modifierToFlag(token: SyntaxKind): ModifierFlags { return ModifierFlags.In; case SyntaxKind.OutKeyword: return ModifierFlags.Out; + case SyntaxKind.DeferredKeyword: + return ModifierFlags.Deferred; case SyntaxKind.Decorator: return ModifierFlags.Decorator; } diff --git a/src/compiler/utilitiesPublic.ts b/src/compiler/utilitiesPublic.ts index 2ff6e7adaa8..1bca593edd2 100644 --- a/src/compiler/utilitiesPublic.ts +++ b/src/compiler/utilitiesPublic.ts @@ -1568,6 +1568,7 @@ export function isModifierKind(token: SyntaxKind): token is Modifier["kind"] { case SyntaxKind.ConstKeyword: case SyntaxKind.DeclareKeyword: case SyntaxKind.DefaultKeyword: + case SyntaxKind.DeferredKeyword: case SyntaxKind.ExportKeyword: case SyntaxKind.InKeyword: case SyntaxKind.PublicKeyword: diff --git a/src/harness/fourslashInterfaceImpl.ts b/src/harness/fourslashInterfaceImpl.ts index 7400966d833..f04ac4594f9 100644 --- a/src/harness/fourslashInterfaceImpl.ts +++ b/src/harness/fourslashInterfaceImpl.ts @@ -1210,7 +1210,6 @@ export namespace Completion { typeEntry("Capitalize"), typeEntry("Uncapitalize"), typeEntry("NoInfer"), - typeEntry("Deferred"), interfaceEntry("ThisType"), varEntry("ArrayBuffer"), interfaceEntry("ArrayBufferTypes"), diff --git a/src/lib/es5.d.ts b/src/lib/es5.d.ts index 3fc2031efd1..33bbb991474 100644 --- a/src/lib/es5.d.ts +++ b/src/lib/es5.d.ts @@ -1654,11 +1654,6 @@ type Uncapitalize = intrinsic; */ type NoInfer = intrinsic; -/** - * Marker for deferred callbacks - */ -type Deferred any> = intrinsic; - /** * Marker for contextual 'this' type */