diff --git a/README.md b/README.md index 3314c58f49221..cf95cbe21a7db 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,46 @@ +# Fork of Typescript + +This fork contains a few modifications to Typescript to preview the try-expression-proposal syntax. It is not intended to be used in production. + +## To play around with it for yourself + +- Create a new npm project folder using `npm init`. +- `npm install` the built tgz file from the releases page of this repository. + - https://github.com/Arlen22/TypeScript/releases/download/arlen22-dev-0.3/typescript-5.8.0-arlen22-0.3.tgz +- In VSCode, create a new typescript file and open it to enable the Typescript engine, then run the command "Typescript: Select Typescript Version" and select the version from your node_modules folder. +- Create a tsconfig.json file with `strictNullChecks` enabled. +- If you run `tsc` it will just pass the syntax through to the Javascript file. + +## The following code should be valid + +```ts +const [error, data]: TryResult<1> = try 1; +if (error) { + const e: TryError = error; + const r: undefined = data; +} else { + const e: undefined = error; + const r: 1 = data; +} +// try at the beginning of an arrow function body +const [error1, data1] = (() => try someExpression() ?? otherexpression() ? true : false)(); +// regular assignment, terminated by comma, semicolon, or other statement. +const [error2, data2] = try someExpression() ?? otherexpression() ? true : false; +// used as an array value, terminated by comma or end of array +const [[error3, data3]] = [try someExpression() ?? otherexpression() ? true : false]; +// used as an object value, terminated by comma or end of object +const { res: [error4, data4] } = { res: try someExpression() ?? otherexpression() ? true : false } +``` + +## Changelog + +### 0.2 + +- Added the try-expression-proposal syntax to the parser basically copying the yield syntax. + +### 0.1 + +- Failed attempt that only partly worked. # TypeScript @@ -6,7 +49,6 @@ [![Downloads](https://img.shields.io/npm/dm/typescript.svg)](https://www.npmjs.com/package/typescript) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/microsoft/TypeScript/badge)](https://securityscorecards.dev/viewer/?uri=github.com/microsoft/TypeScript) - [TypeScript](https://www.typescriptlang.org/) is a language for application-scale JavaScript. TypeScript adds optional types to JavaScript that support tools for large-scale JavaScript applications for any browser, for any host, on any OS. TypeScript compiles to readable, standards-based JavaScript. Try it out at the [playground](https://www.typescriptlang.org/play/), and stay up to date via [our blog](https://blogs.msdn.microsoft.com/typescript) and [Twitter account](https://twitter.com/typescript). Find others who are using TypeScript at [our community page](https://www.typescriptlang.org/community/). @@ -28,12 +70,13 @@ npm install -D typescript@next ## Contribute There are many ways to [contribute](https://github.com/microsoft/TypeScript/blob/main/CONTRIBUTING.md) to TypeScript. -* [Submit bugs](https://github.com/microsoft/TypeScript/issues) and help us verify fixes as they are checked in. -* Review the [source code changes](https://github.com/microsoft/TypeScript/pulls). -* Engage with other TypeScript users and developers on [StackOverflow](https://stackoverflow.com/questions/tagged/typescript). -* Help each other in the [TypeScript Community Discord](https://discord.gg/typescript). -* Join the [#typescript](https://twitter.com/search?q=%23TypeScript) discussion on Twitter. -* [Contribute bug fixes](https://github.com/microsoft/TypeScript/blob/main/CONTRIBUTING.md). + +- [Submit bugs](https://github.com/microsoft/TypeScript/issues) and help us verify fixes as they are checked in. +- Review the [source code changes](https://github.com/microsoft/TypeScript/pulls). +- Engage with other TypeScript users and developers on [StackOverflow](https://stackoverflow.com/questions/tagged/typescript). +- Help each other in the [TypeScript Community Discord](https://discord.gg/typescript). +- Join the [#typescript](https://twitter.com/search?q=%23TypeScript) discussion on Twitter. +- [Contribute bug fixes](https://github.com/microsoft/TypeScript/blob/main/CONTRIBUTING.md). This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) @@ -41,9 +84,9 @@ with any additional questions or comments. ## Documentation -* [TypeScript in 5 minutes](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html) -* [Programming handbook](https://www.typescriptlang.org/docs/handbook/intro.html) -* [Homepage](https://www.typescriptlang.org/) +- [TypeScript in 5 minutes](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html) +- [Programming handbook](https://www.typescriptlang.org/docs/handbook/intro.html) +- [Homepage](https://www.typescriptlang.org/) ## Roadmap diff --git a/example/package.json b/example/package.json new file mode 100644 index 0000000000000..22df595305e6b --- /dev/null +++ b/example/package.json @@ -0,0 +1,15 @@ +{ + "name": "example", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "build": "tsc test.ts --target esnext" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "typescript": "https://github.com/Arlen22/TypeScript/releases/download/arlen22-dev-0.3/typescript-5.8.0-arlen22-0.3.tgz" + } +} diff --git a/example/poly.ts b/example/poly.ts new file mode 100644 index 0000000000000..a110fe65fda0b --- /dev/null +++ b/example/poly.ts @@ -0,0 +1,59 @@ +function isPromise(result) { return result && typeof result.then === 'function'; } +function isIterable(result) { return result && typeof result[Symbol.iterator] === 'function'; } +function isAsyncIterable(result) { return result && typeof result[Symbol.asyncIterator] === 'function'; } + +function try_(callback) { + const syncCall = (function () { + try { + return TryResult.ok(callback()); + } catch (e) { + return TryResult.error(e); + } + }).apply(this) as TryResult; + + if (!syncCall.ok) return syncCall; + + if (isPromise(syncCall.value)) { + return syncCall.value.then( + (value) => TryResult.ok(value), + (e) => TryResult.error(e) + ); + } + // Returns the value returned by that iterator when it's closed (when done is true). + if (isIterable(syncCall.value)) { + return (function* () { + try { + return TryResult.ok(yield* syncCall.value); + } catch (e) { + return TryResult.error(e); + } + }).bind(this)(); + } + + if (isAsyncIterable(syncCall.value)) { + return (async function* () { + try { + return TryResult.ok(yield* syncCall.value); + } catch (e) { + return TryResult.error(e); + } + }).bind(this)(); + } + +} + + +async function* demo() { + // try "hello" + const [ok, error, value] = try_("hello"); + + // try await "hello" + const [ok, error, value] = await try_(async () => await "hello"); + + // try yield "hello" + const [ok, error, value] = yield* try_(function* () { return "hello"; }); + + // try yield await "hello" + const [ok, error, value] = yield* try_(async function* () { return await "hello"; }); + +} diff --git a/example/test.js b/example/test.js new file mode 100644 index 0000000000000..a3fb063f4f144 --- /dev/null +++ b/example/test.js @@ -0,0 +1,67 @@ +var TryResult = /** @class */ (function() { + function TryResult(ok, error, value) { + this.ok = ok; + this.error = error; + this.value = value; + } + TryResult.prototype[Symbol.iterator] = function() { + return [this.ok, this.error, this.value].values(); + }; + TryResult.ok = function(value) { + return new TryResult(true, undefined, value); + }; + TryResult.error = function(error) { + return new TryResult(false, error, undefined); + }; + return TryResult; +}()); +async function examples() { + // array.map((fn) => try fn()).filter((result) => result.ok); + const data = {}; + const expr1 = Promise.resolve("hello"); + const expr2 = Promise.resolve("world"); + const check = true; + let result; + result = function() { try { return TryResult.ok(expr1); } catch(e) { return TryResult.error(e); } }.bind(this)(); // literally any expression + result = function() { try { return TryResult.ok(expr1 || expr2); } catch(e) { return TryResult.error(e); } }.bind(this)(); // the try covers both, same as an arrow function body + result = function() { try { return TryResult.ok(check ? expr1 : expr2); } catch(e) { return TryResult.error(e); } }.bind(this)(); // try covers the entire expression + result = function() { try { return TryResult.ok(expr1 ?? expr2); } catch(e) { return TryResult.error(e); } }.bind(this)(); // again covers the entire expression + result = await async function() { try { return TryResult.ok(data?.someProperty.anotherFunction?.(await fetch("")).andAnotherOne()); } catch(e) { return TryResult.error(e); } }.bind(this)(); + result = await async function() { try { return TryResult.ok(await fetch("https://api.example.com/data", { headers: {} })); } catch(e) { return TryResult.error(e); } }.bind(this)(); + result = await async function() { try { return TryResult.ok((await expr1, await expr2)); } catch(e) { return TryResult.error(e); } }.bind(this)(); // covers all, returning the last one + result = await async function() { try { return TryResult.ok(({ "my": await expr1 })); } catch(e) { return TryResult.error(e); } }.bind(this)(); // convers everything inside the object + result = await async function() { try { return TryResult.ok([await (await fetch("")).json(), expr1, expr2]); } catch(e) { return TryResult.error(e); } }.bind(this)(); // covers everything inside the array + // result = try this.test = this.test2 = await fetch(""); // covers everything + const test = await async function() { try { return TryResult.ok(await createUser((await async function() { try { return TryResult.ok(await (await fetch("")).json()); } catch(e) { return TryResult.error(e); } }.bind(this)()).value)); } catch(e) { return TryResult.error(e); } }.bind(this)(); + // const exprsync = try expr1; + const exprsync = function() { try { return TryResult.ok(expr1); } catch(e) { return TryResult.error(e); } }.bind(this)(); + // const exprasync = try await expr1; + const exprasync = await async function() { try { return TryResult.ok(await expr1); } catch(e) { return TryResult.error(e); } }.bind(this)(); + // const expryield = try yield expr1; + const expryield = yield * function*() { try { return TryResult.ok(yield expr1); } catch(e) { return TryResult.error(e); } }.bind(this)(); + // const expryieldasync = try yield await expr1; + const expryieldasync = yield * async function*() { try { return TryResult.ok(yield await expr1); } catch(e) { return TryResult.error(e); } }.bind(this)(); + // const exprsyncyield = yield try expr1; + const exprsyncyield = yield function() { try { return TryResult.ok(expr1); } catch(e) { return TryResult.error(e); } }.bind(this)(); + // const exprsyncyieldasync = yield try await expr1; + const exprsyncyieldasync = yield await async function() { try { return TryResult.ok(await expr1); } catch(e) { return TryResult.error(e); } }.bind(this)(); + // const expryieldyieldasync = yield try yield await expr1; + const expryieldyieldasync = yield yield * async function*() { try { return TryResult.ok(yield await expr1); } catch(e) { return TryResult.error(e); } }.bind(this)(); +} +async function* examples2() { + const expression = "hello"; + console.log(function() { try { return TryResult.ok(expression); } catch(e) { return TryResult.error(e); } }.bind(this)()); + console.log(await async function() { try { return TryResult.ok(await expression); } catch(e) { return TryResult.error(e); } }.bind(this)()); + console.log(yield* function*() { try { return TryResult.ok(yield expression); } catch(e) { return TryResult.error(e); } }.bind(this)()); + console.log(yield* async function*() { try { return TryResult.ok(yield await expression); } catch(e) { return TryResult.error(e); } }.bind(this)()); + console.log(yield function() { try { return TryResult.ok(expression); } catch(e) { return TryResult.error(e); } }.bind(this)()); + console.log(yield await async function() { try { return TryResult.ok(await expression); } catch(e) { return TryResult.error(e); } }.bind(this)()); + console.log(yield yield* async function*() { try { return TryResult.ok(yield await expression); } catch(e) { return TryResult.error(e); } }.bind(this)()); + const result = function() { try { return TryResult.ok(expression); } catch(e) { return TryResult.error(e); } }.bind(this)(); + await Promise.resolve("hello"); +} +(async () => { + for await(const t of examples2()) + console.log(t); +})(); +export {}; diff --git a/example/test.ts b/example/test.ts new file mode 100644 index 0000000000000..5766f7bc607e0 --- /dev/null +++ b/example/test.ts @@ -0,0 +1,59 @@ +export { }; + + + + +async function examples() { + // array.map((fn) => try fn()).filter((result) => result.ok); + const data = {} as any; + const expr1 = Promise.resolve("hello"); + const expr2 = Promise.resolve("world"); + const check = true; + let result; + result = try expr1; // literally any expression + result = try expr1 || expr2; // the try covers both, same as an arrow function body + result = try check ? expr1 : expr2; // try covers the entire expression + result = try expr1 ?? expr2; // again covers the entire expression + result = try data?.someProperty.anotherFunction?.(await fetch("")).andAnotherOne() + result = try await fetch("https://api.example.com/data", { headers: {} }) + result = try (await expr1, await expr2); // covers all, returning the last one + result = try ({ "my": await expr1 }); // convers everything inside the object + result = try [await (await fetch("")).json(), expr1, expr2]; // covers everything inside the array + // result = try this.test = this.test2 = await fetch(""); // covers everything + + const test = try await createUser((try await (await fetch("")).json() as {hello: true}).value) + + // const exprsync = try expr1; + const exprsync = try expr1; + // const exprasync = try await expr1; + const exprasync = try await expr1; + // const expryield = try yield expr1; + const expryield = try yield expr1; + // const expryieldasync = try yield await expr1; + const expryieldasync = try yield await expr1; + // const exprsyncyield = yield try expr1; + const exprsyncyield = yield try expr1; + // const exprsyncyieldasync = yield try await expr1; + const exprsyncyieldasync = yield try await expr1; + // const expryieldyieldasync = yield try yield await expr1; + const expryieldyieldasync = yield try yield await expr1; + +} + +async function* examples2() { + + const expression = "hello"; + console.log(try expression); + console.log(try await expression); + console.log(try yield expression); + console.log(try yield await expression); + console.log(yield try expression); + console.log(yield try await expression); + console.log(yield try yield await expression); + const result = try expression; + await Promise.resolve("hello"); +} + +(async () => { + for await (const t of examples2()) console.log(t); +})(); diff --git a/example/test2.js b/example/test2.js new file mode 100644 index 0000000000000..956d427c00c94 --- /dev/null +++ b/example/test2.js @@ -0,0 +1,18 @@ + +function* test() { + + return yield* function*(){ + try { + console.log(yield "hello"); + console.log(yield "world"); + return "done"; + } catch (e){ + return e; + } + }.bind(this)(); +} +// const test = (while(!i.done) {i = gen.next(yield i.value);}) +const gen = test(1); +console.log(gen.next()); +console.log(gen.next("welcome")); +console.log(gen.next("finished")); diff --git a/package-lock.json b/package-lock.json index b089dc3b035a7..00118fe615f0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "typescript", - "version": "5.8.0", + "version": "5.8.0-arlen22-0.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "typescript", - "version": "5.8.0", + "version": "5.8.0-arlen22-0.3", "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", diff --git a/package.json b/package.json index 4ddb7a53a7146..96fa4d8e16d56 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "typescript", "author": "Microsoft Corp.", "homepage": "https://www.typescriptlang.org/", - "version": "5.8.0", + "version": "5.8.0-arlen22-0.3", "license": "Apache-2.0", "description": "TypeScript is a language for application scale JavaScript development", "keywords": [ @@ -99,7 +99,10 @@ "lint": "hereby lint", "knip": "hereby knip", "format": "dprint fmt", - "setup-hooks": "node scripts/link-hooks.mjs" + "setup-hooks": "node scripts/link-hooks.mjs", + "pack": "hereby local && hereby LKG && hereby clean", + "build:dev": "npm run pack && npm pack && (cd example/ && npm install ../typescript-5.8.0-arlen22-0.3.tgz)", + "install:dev": "mkdir -p example && cd example/ && npm init -y" }, "browser": { "fs": false, diff --git a/src/compiler/_namespaces/ts.ts b/src/compiler/_namespaces/ts.ts index 94fb16857d38e..ba365a6498668 100644 --- a/src/compiler/_namespaces/ts.ts +++ b/src/compiler/_namespaces/ts.ts @@ -1,5 +1,5 @@ /* Generated file to emulate the ts namespace. */ - +export * from "../transformers/arlen22.js"; export * from "../corePublic.js"; export * from "../core.js"; export * from "../debug.js"; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cc8a7f7428fcf..c4deab9cf4672 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1132,6 +1132,8 @@ import { WithStatement, WriterContextOut, YieldExpression, + TryExpression, + isTryExpression, } from "./_namespaces/ts.js"; import * as moduleSpecifiers from "./_namespaces/ts.moduleSpecifiers.js"; import * as performance from "./_namespaces/ts.performance.js"; @@ -1651,6 +1653,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { getParameterIdentifierInfoAtPosition, getPromisedTypeOfPromise, getAwaitedType: type => getAwaitedType(type), + getTryResultType: type => getTryResultType(type), getReturnTypeOfSignature, isNullableType, getNullableType, @@ -2274,6 +2277,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { var deferredGlobalExtractSymbol: Symbol | undefined; var deferredGlobalOmitSymbol: Symbol | undefined; var deferredGlobalAwaitedSymbol: Symbol | undefined; + var deferredGlobalTryResultSymbol: Symbol | undefined; var deferredGlobalBigIntType: ObjectType | undefined; var deferredGlobalNaNSymbol: Symbol | undefined; var deferredGlobalRecordSymbol: Symbol | undefined; @@ -17014,6 +17018,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return deferredGlobalAwaitedSymbol === unknownSymbol ? undefined : deferredGlobalAwaitedSymbol; } + function getGlobalTryResultSymbol(reportErrors: boolean): Symbol | undefined { + // Only cache `unknownSymbol` if we are reporting errors so that we don't report the error more than once. + deferredGlobalTryResultSymbol ||= getGlobalTypeAliasSymbol("TryResult" as __String, /*arity*/ 1, reportErrors) || (reportErrors ? unknownSymbol : undefined); + return deferredGlobalTryResultSymbol === unknownSymbol ? undefined : deferredGlobalTryResultSymbol; + } + function getGlobalBigIntType() { return (deferredGlobalBigIntType ||= getGlobalType("BigInt" as __String, /*arity*/ 0, /*reportErrors*/ false)) || emptyObjectType; } @@ -31386,6 +31396,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return undefined; } + + function getContextualTypeForTryOperand(node: TryExpression, contextFlags: ContextFlags | undefined): Type | undefined { + const contextualType = getContextualType(node, contextFlags); + return contextualType && getTryResultType(contextualType); + } + function getContextualTypeForAwaitOperand(node: AwaitExpression, contextFlags: ContextFlags | undefined): Type | undefined { const contextualType = getContextualType(node, contextFlags); if (contextualType) { @@ -32133,6 +32149,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return getContextualTypeForYieldOperand(parent as YieldExpression, contextFlags); case SyntaxKind.AwaitExpression: return getContextualTypeForAwaitOperand(parent as AwaitExpression, contextFlags); + case SyntaxKind.TryExpression: + return getContextualTypeForTryOperand(parent as TryExpression, contextFlags); case SyntaxKind.CallExpression: case SyntaxKind.NewExpression: return getContextualTypeForArgument(parent as CallExpression | NewExpression | Decorator, node); @@ -39236,6 +39254,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return hasError; } + function checkTryExpression(node: TryExpression): Type { + const operandType = checkExpression(node.expression); + return getTryResultType(operandType) ?? unknownType; + } + function checkAwaitExpression(node: AwaitExpression): Type { addLazyDiagnostic(() => checkAwaitGrammar(node)); @@ -40973,6 +40996,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const type = getQuickTypeOfExpression(expr.expression); return type ? getAwaitedType(type) : undefined; } + if (isTryExpression(expr)) { + const type = getQuickTypeOfExpression(expr.expression); + return type ? getTryResultType(type) : undefined; + } // Optimize for the common case of a call to a function with a single non-generic call // signature where we can just fetch the return type without checking the arguments. if (isCallExpression(expr) && expr.expression.kind !== SyntaxKind.SuperKeyword && !isRequireCall(expr, /*requireStringLiteralLikeArgument*/ true) && !isSymbolOrSymbolForCall(expr)) { @@ -41163,6 +41190,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return checkVoidExpression(node as VoidExpression); case SyntaxKind.AwaitExpression: return checkAwaitExpression(node as AwaitExpression); + case SyntaxKind.TryExpression: + return checkTryExpression(node as TryExpression); case SyntaxKind.PrefixUnaryExpression: return checkPrefixUnaryExpression(node as PrefixUnaryExpression); case SyntaxKind.PostfixUnaryExpression: @@ -42645,6 +42674,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } + function getTryResultType(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, ...args: DiagnosticArguments) { + // Nothing to do if `TryResult` doesn't exist + const tryresultSymbol = getGlobalTryResultSymbol(/*reportErrors*/ true); + return tryresultSymbol && getTypeAliasInstantiation(tryresultSymbol, [type]) + } + function getAwaitedTypeOfPromise(type: Type, errorNode?: Node, diagnosticMessage?: DiagnosticMessage, ...args: DiagnosticArguments): Type | undefined { const promisedType = getPromisedTypeOfPromise(type, errorNode); return promisedType && getAwaitedType(promisedType, errorNode, diagnosticMessage, ...args); diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 01a96579861f4..fa4fbacbaf7ad 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -422,6 +422,7 @@ import { writeFile, WriteFileCallbackData, YieldExpression, + TryExpression, } from "./_namespaces/ts.js"; import * as performance from "./_namespaces/ts.performance.js"; @@ -1962,6 +1963,8 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri return emitTemplateExpression(node as TemplateExpression); case SyntaxKind.YieldExpression: return emitYieldExpression(node as YieldExpression); + case SyntaxKind.TryExpression: + return emitTryExpression(node as TryExpression); case SyntaxKind.SpreadElement: return emitSpreadElement(node as SpreadElement); case SyntaxKind.ClassExpression: @@ -2956,6 +2959,11 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri emitExpressionWithLeadingSpace(node.expression && parenthesizeExpressionForNoAsi(node.expression), parenthesizeExpressionForNoAsiAndDisallowedComma); } + function emitTryExpression(node: TryExpression) { + emitTokenWithComment(SyntaxKind.TryKeyword, node.pos, writeKeyword, node); + emitExpressionWithLeadingSpace(node.expression && parenthesizeExpressionForNoAsi(node.expression), parenthesizeExpressionForNoAsiAndDisallowedComma); + } + function emitSpreadElement(node: SpreadElement) { emitTokenWithComment(SyntaxKind.DotDotDotToken, node.pos, writePunctuation, node); emitExpression(node.expression, parenthesizer.parenthesizeExpressionForDisallowedComma); diff --git a/src/compiler/factory/nodeFactory.ts b/src/compiler/factory/nodeFactory.ts index 694262eeeb54a..3b9ffa2401239 100644 --- a/src/compiler/factory/nodeFactory.ts +++ b/src/compiler/factory/nodeFactory.ts @@ -431,6 +431,7 @@ import { TokenFlags, TransformFlags, TrueLiteral, + TryExpression, TryStatement, TupleTypeNode, Type, @@ -684,6 +685,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode createTemplateLiteralLikeNode, createYieldExpression, updateYieldExpression, + createTryExpression, + updateTryExpression, createSpreadElement, updateSpreadElement, createClassExpression, @@ -3629,6 +3632,22 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode : node; } + + // @api + function createTryExpression(expression: Expression): TryExpression { + const node = createBaseNode(SyntaxKind.TryExpression); + node.expression = expression && parenthesizerRules().parenthesizeExpressionForDisallowedComma(expression); + node.transformFlags |= propagateChildFlags(node.expression); + return node; + } + + // @api + function updateTryExpression(node: TryExpression, expression: Expression) { + return node.expression !== expression + ? update(createTryExpression(expression), node) + : node; + } + // @api function createSpreadElement(expression: Expression) { const node = createBaseNode(SyntaxKind.SpreadElement); diff --git a/src/compiler/factory/nodeTests.ts b/src/compiler/factory/nodeTests.ts index 8aa4bb02e83d2..4e999d6159358 100644 --- a/src/compiler/factory/nodeTests.ts +++ b/src/compiler/factory/nodeTests.ts @@ -209,6 +209,7 @@ import { ThisTypeNode, ThrowStatement, Token, + TryExpression, TryStatement, TupleTypeNode, TypeAliasDeclaration, @@ -657,6 +658,11 @@ export function isYieldExpression(node: Node): node is YieldExpression { return node.kind === SyntaxKind.YieldExpression; } +export function isTryExpression(node: Node): node is TryExpression { + return node.kind === SyntaxKind.TryExpression; +} + + export function isSpreadElement(node: Node): node is SpreadElement { return node.kind === SyntaxKind.SpreadElement; } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 59ad1f030220f..f29d4cb070247 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -63,6 +63,7 @@ import { DeleteExpression, Diagnostic, DiagnosticArguments, + DiagnosticCategory, DiagnosticMessage, Diagnostics, DiagnosticWithDetachedLocation, @@ -372,6 +373,7 @@ import { tracing, transferSourceFileChildren, TransformFlags, + TryExpression, TryStatement, TupleTypeNode, TypeAliasDeclaration, @@ -474,10 +476,10 @@ export function isFileProbablyExternalModule(sourceFile: SourceFile): Node | und function isAnExternalModuleIndicatorNode(node: Node) { return canHaveModifiers(node) && hasModifierOfKind(node, SyntaxKind.ExportKeyword) - || isImportEqualsDeclaration(node) && isExternalModuleReference(node.moduleReference) - || isImportDeclaration(node) - || isExportAssignment(node) - || isExportDeclaration(node) ? node : undefined; + || isImportEqualsDeclaration(node) && isExternalModuleReference(node.moduleReference) + || isImportDeclaration(node) + || isExportAssignment(node) + || isExportDeclaration(node) ? node : undefined; } function getImportMetaIfNecessary(sourceFile: SourceFile) { @@ -773,6 +775,9 @@ const forEachChildTable: ForEachChildTable = { return visitNode(cbNode, node.asteriskToken) || visitNode(cbNode, node.expression); }, + [SyntaxKind.TryExpression]: function forEachChildInTryExpression(node: TryExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { + return visitNode(cbNode, node.expression); + }, [SyntaxKind.AwaitExpression]: function forEachChildInAwaitExpression(node: AwaitExpression, cbNode: (node: Node) => T | undefined, _cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.expression); }, @@ -1090,13 +1095,13 @@ const forEachChildTable: ForEachChildTable = { [SyntaxKind.JSDocTypedefTag]: function forEachChildInJSDocTypedefTag(node: JSDocTypedefTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.tagName) || (node.typeExpression && - node.typeExpression.kind === SyntaxKind.JSDocTypeExpression + node.typeExpression.kind === SyntaxKind.JSDocTypeExpression ? visitNode(cbNode, node.typeExpression) || - visitNode(cbNode, node.fullName) || - (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)) + visitNode(cbNode, node.fullName) || + (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment)) : visitNode(cbNode, node.fullName) || - visitNode(cbNode, node.typeExpression) || - (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment))); + visitNode(cbNode, node.typeExpression) || + (typeof node.comment === "string" ? undefined : visitNodes(cbNode, cbNodes, node.comment))); }, [SyntaxKind.JSDocCallbackTag]: function forEachChildInJSDocCallbackTag(node: JSDocCallbackTag, cbNode: (node: Node) => T | undefined, cbNodes?: (nodes: NodeArray) => T | undefined): T | undefined { return visitNode(cbNode, node.tagName) || @@ -1683,7 +1688,7 @@ namespace Parser { expression = parseLiteralNode() as StringLiteral | NumericLiteral; break; } - // falls through + // falls through default: expression = parseObjectLiteralExpression(); break; @@ -2626,10 +2631,10 @@ namespace Parser { const pos = getNodePos(); const result = kind === SyntaxKind.Identifier ? factoryCreateIdentifier("", /*originalKeywordKind*/ undefined) : isTemplateLiteralKind(kind) ? factory.createTemplateLiteralLikeNode(kind, "", "", /*templateFlags*/ undefined) : - kind === SyntaxKind.NumericLiteral ? factoryCreateNumericLiteral("", /*numericLiteralFlags*/ undefined) : - kind === SyntaxKind.StringLiteral ? factoryCreateStringLiteral("", /*isSingleQuote*/ undefined) : - kind === SyntaxKind.MissingDeclaration ? factory.createMissingDeclaration() : - factoryCreateToken(kind); + kind === SyntaxKind.NumericLiteral ? factoryCreateNumericLiteral("", /*numericLiteralFlags*/ undefined) : + kind === SyntaxKind.StringLiteral ? factoryCreateStringLiteral("", /*isSingleQuote*/ undefined) : + kind === SyntaxKind.MissingDeclaration ? factory.createMissingDeclaration() : + factoryCreateToken(kind); return finishNode(result, pos) as T; } @@ -2793,9 +2798,9 @@ namespace Parser { function canFollowExportModifier(): boolean { return token() === SyntaxKind.AtToken || token() !== SyntaxKind.AsteriskToken - && token() !== SyntaxKind.AsKeyword - && token() !== SyntaxKind.OpenBraceToken - && canFollowModifier(); + && token() !== SyntaxKind.AsKeyword + && token() !== SyntaxKind.OpenBraceToken + && canFollowModifier(); } function nextTokenCanFollowExportModifier(): boolean { @@ -2906,7 +2911,7 @@ namespace Parser { case SyntaxKind.DotToken: // Not an array literal member, but don't want to close the array (see `tests/cases/fourslash/completionsDotInArrayLiteralInObjectLiteral.ts`) return true; } - // falls through + // falls through case ParsingContext.ArgumentExpressions: return token() === SyntaxKind.DotDotDotToken || isStartOfExpression(); case ParsingContext.Parameters: @@ -3225,53 +3230,53 @@ namespace Parser { case ParsingContext.Parameters: return isReusableParameter(node); - // Any other lists we do not care about reusing nodes in. But feel free to add if - // you can do so safely. Danger areas involve nodes that may involve speculative - // parsing. If speculative parsing is involved with the node, then the range the - // parser reached while looking ahead might be in the edited range (see the example - // in canReuseVariableDeclaratorNode for a good case of this). - - // case ParsingContext.HeritageClauses: - // This would probably be safe to reuse. There is no speculative parsing with - // heritage clauses. - - // case ParsingContext.TypeParameters: - // This would probably be safe to reuse. There is no speculative parsing with - // type parameters. Note that that's because type *parameters* only occur in - // unambiguous *type* contexts. While type *arguments* occur in very ambiguous - // *expression* contexts. - - // case ParsingContext.TupleElementTypes: - // This would probably be safe to reuse. There is no speculative parsing with - // tuple types. - - // Technically, type argument list types are probably safe to reuse. While - // speculative parsing is involved with them (since type argument lists are only - // produced from speculative parsing a < as a type argument list), we only have - // the types because speculative parsing succeeded. Thus, the lookahead never - // went past the end of the list and rewound. - // case ParsingContext.TypeArguments: - - // Note: these are almost certainly not safe to ever reuse. Expressions commonly - // need a large amount of lookahead, and we should not reuse them as they may - // have actually intersected the edit. - // case ParsingContext.ArgumentExpressions: - - // This is not safe to reuse for the same reason as the 'AssignmentExpression' - // cases. i.e. a property assignment may end with an expression, and thus might - // have lookahead far beyond it's old node. - // case ParsingContext.ObjectLiteralMembers: - - // This is probably not safe to reuse. There can be speculative parsing with - // type names in a heritage clause. There can be generic names in the type - // name list, and there can be left hand side expressions (which can have type - // arguments.) - // case ParsingContext.HeritageClauseElement: - - // Perhaps safe to reuse, but it's unlikely we'd see more than a dozen attributes - // on any given element. Same for children. - // case ParsingContext.JsxAttributes: - // case ParsingContext.JsxChildren: + // Any other lists we do not care about reusing nodes in. But feel free to add if + // you can do so safely. Danger areas involve nodes that may involve speculative + // parsing. If speculative parsing is involved with the node, then the range the + // parser reached while looking ahead might be in the edited range (see the example + // in canReuseVariableDeclaratorNode for a good case of this). + + // case ParsingContext.HeritageClauses: + // This would probably be safe to reuse. There is no speculative parsing with + // heritage clauses. + + // case ParsingContext.TypeParameters: + // This would probably be safe to reuse. There is no speculative parsing with + // type parameters. Note that that's because type *parameters* only occur in + // unambiguous *type* contexts. While type *arguments* occur in very ambiguous + // *expression* contexts. + + // case ParsingContext.TupleElementTypes: + // This would probably be safe to reuse. There is no speculative parsing with + // tuple types. + + // Technically, type argument list types are probably safe to reuse. While + // speculative parsing is involved with them (since type argument lists are only + // produced from speculative parsing a < as a type argument list), we only have + // the types because speculative parsing succeeded. Thus, the lookahead never + // went past the end of the list and rewound. + // case ParsingContext.TypeArguments: + + // Note: these are almost certainly not safe to ever reuse. Expressions commonly + // need a large amount of lookahead, and we should not reuse them as they may + // have actually intersected the edit. + // case ParsingContext.ArgumentExpressions: + + // This is not safe to reuse for the same reason as the 'AssignmentExpression' + // cases. i.e. a property assignment may end with an expression, and thus might + // have lookahead far beyond it's old node. + // case ParsingContext.ObjectLiteralMembers: + + // This is probably not safe to reuse. There can be speculative parsing with + // type names in a heritage clause. There can be generic names in the type + // name list, and there can be left hand side expressions (which can have type + // arguments.) + // case ParsingContext.HeritageClauseElement: + + // Perhaps safe to reuse, but it's unlikely we'd see more than a dozen attributes + // on any given element. Same for children. + // case ParsingContext.JsxAttributes: + // case ParsingContext.JsxChildren: } return false; @@ -3765,9 +3770,9 @@ namespace Parser { // We also do not need to check for negatives because any prefix operator would be part of a // parent unary expression. kind === SyntaxKind.NumericLiteral ? factoryCreateNumericLiteral(scanner.getTokenValue(), scanner.getNumericLiteralFlags()) : - kind === SyntaxKind.StringLiteral ? factoryCreateStringLiteral(scanner.getTokenValue(), /*isSingleQuote*/ undefined, scanner.hasExtendedUnicodeEscape()) : - isLiteralKind(kind) ? factoryCreateLiteralLikeNode(kind, scanner.getTokenValue()) : - Debug.fail(); + kind === SyntaxKind.StringLiteral ? factoryCreateStringLiteral(scanner.getTokenValue(), /*isSingleQuote*/ undefined, scanner.hasExtendedUnicodeEscape()) : + isLiteralKind(kind) ? factoryCreateLiteralLikeNode(kind, scanner.getTokenValue()) : + Debug.fail(); if (scanner.hasExtendedUnicodeEscape()) { node.hasExtendedUnicodeEscape = true; @@ -4601,13 +4606,13 @@ namespace Parser { case SyntaxKind.AsteriskEqualsToken: // If there is '*=', treat it as * followed by postfix = scanner.reScanAsteriskEqualsToken(); - // falls through + // falls through case SyntaxKind.AsteriskToken: return parseJSDocAllType(); case SyntaxKind.QuestionQuestionToken: // If there is '??', treat it as prefix-'?' in JSDoc type. scanner.reScanQuestionToken(); - // falls through + // falls through case SyntaxKind.QuestionToken: return parseJSDocUnknownOrNullableType(); case SyntaxKind.FunctionKeyword: @@ -5014,6 +5019,16 @@ namespace Parser { // it is definitely an expression). Or it's a keyword (either because we're in // a generator or async function, or in strict mode (or both)) and it started a yield or await expression. return true; + case SyntaxKind.TryKeyword: + // if we encounter the try keyword and the next token is not an open brace, + // this is probably the try expression, but since the try statement takes priority + // wherever it is valid, there is no point in a lookahead here. + // however, this does not allow the try expression to be used as a statement. + // i.e. it must be encountered somewhere where an expression is already expected. + // but this makes sense because we don't want to allow the try to just get rid of + // errors without explicitly dealing with them somehow. In a context where an expression + // is already expected it would then have to be handled explicitly somehow. + return true; default: // Error tolerance. If we see the start of some binary operator, we consider // that the start of an expression. That way we'll parse out a missing identifier, @@ -5080,6 +5095,9 @@ namespace Parser { if (isYieldExpression()) { return parseYieldExpression(); } + if (isTryExpression()) { + return parseTryExpression(); + } // Then, check if we have an arrow function (production '4' and '5') that starts with a parenthesized // parameter list or is an async arrow function. @@ -5131,6 +5149,31 @@ namespace Parser { return parseConditionalExpressionRest(expr, pos, allowReturnTypeInArrowFunction); } + function isTryExpression() { + return token() === SyntaxKind.TryKeyword; + } + function parseTryExpression(): TryExpression { + const pos = getNodePos(); + + nextToken(); + + if(token() === SyntaxKind.OpenBraceToken) { + parseErrorAtCurrentToken({ + message: "Object literal following the try keyword must be wrapped in parentheses.", + category: DiagnosticCategory.Error, + code: 99999, + key: "Object_literal_following_the_try_keyword_must_be_wrapped_in_parentheses", + }); + } + + return finishNode( + factory.createTryExpression( + parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true), + ), + pos, + ); + } + function isYieldExpression(): boolean { if (token() === SyntaxKind.YieldKeyword) { // If we have a 'yield' keyword, and this is a context where yield expressions are @@ -5164,6 +5207,7 @@ namespace Parser { return !scanner.hasPrecedingLineBreak() && isIdentifier(); } + function parseYieldExpression(): YieldExpression { const pos = getNodePos(); @@ -5815,7 +5859,7 @@ namespace Parser { if (isAwaitExpression()) { return parseAwaitExpression(); } - // falls through + // falls through default: return parseUpdateExpression(); } @@ -5849,8 +5893,8 @@ namespace Parser { if (languageVariant !== LanguageVariant.JSX) { return false; } - // We are in JSX context and the token is part of JSXElement. - // falls through + // We are in JSX context and the token is part of JSXElement. + // falls through default: return true; } @@ -6666,7 +6710,7 @@ namespace Parser { function parseArgumentOrArrayLiteralElement(): Expression { return token() === SyntaxKind.DotDotDotToken ? parseSpreadElement() : token() === SyntaxKind.CommaToken ? finishNode(factory.createOmittedExpression(), getNodePos()) : - parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true); + parseAssignmentExpressionOrHigher(/*allowReturnTypeInArrowFunction*/ true); } function parseArgumentExpression(): Expression { @@ -6767,8 +6811,8 @@ namespace Parser { const isAsync = some(modifiers, isAsyncModifier) ? SignatureFlags.Await : SignatureFlags.None; const name = isGenerator && isAsync ? doInYieldAndAwaitContext(parseOptionalBindingIdentifier) : isGenerator ? doInYieldContext(parseOptionalBindingIdentifier) : - isAsync ? doInAwaitContext(parseOptionalBindingIdentifier) : - parseOptionalBindingIdentifier(); + isAsync ? doInAwaitContext(parseOptionalBindingIdentifier) : + parseOptionalBindingIdentifier(); const typeParameters = parseTypeParameters(); const parameters = parseParameters(isGenerator | isAsync); @@ -7272,8 +7316,10 @@ namespace Parser { case SyntaxKind.WithKeyword: case SyntaxKind.SwitchKeyword: case SyntaxKind.ThrowKeyword: - case SyntaxKind.TryKeyword: case SyntaxKind.DebuggerKeyword: + // 'try' applies to both try statement and try expression statement + // falls through + case SyntaxKind.TryKeyword: // 'catch' and 'finally' do not actually indicate that the code is part of a statement, // however, we say they are here so that we may gracefully parse them and error later. // falls through @@ -8957,7 +9003,7 @@ namespace Parser { linkEnd = scanner.getTokenEnd(); break; } - // fallthrough if it's not a {@link sequence + // fallthrough if it's not a {@link sequence default: // Anything else is doc comment text. We just save it. Because it // wasn't a tag, we can no longer parse a tag on this line until we hit the next @@ -9237,8 +9283,8 @@ namespace Parser { indent += 1; break; } - // record the * as a comment - // falls through + // record the * as a comment + // falls through default: if (state !== JSDocState.SavingBackticks) { state = JSDocState.SavingComments; // leading identifiers start recording as well @@ -9282,7 +9328,7 @@ namespace Parser { } const create = linkType === "link" ? factory.createJSDocLink : linkType === "linkcode" ? factory.createJSDocLinkCode - : factory.createJSDocLinkPlain; + : factory.createJSDocLinkPlain; return finishNode(create(name, text.join("")), start, scanner.getTokenEnd()); } diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index 0d499f3a08298..302e93909ee1d 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -72,6 +72,7 @@ import { transformSystemModule, transformTypeScript, VariableDeclaration, + transformTryExpression, } from "./_namespaces/ts.js"; import * as performance from "./_namespaces/ts.performance.js"; @@ -142,6 +143,8 @@ function getScriptTransformers(compilerOptions: CompilerOptions, customTransform transformers.push(transformJsx); } + transformers.push(transformTryExpression); + if (languageVersion < ScriptTarget.ESNext) { transformers.push(transformESNext); } @@ -181,6 +184,8 @@ function getScriptTransformers(compilerOptions: CompilerOptions, customTransform transformers.push(transformGenerators); } + + transformers.push(getModuleTransformer(moduleKind)); addRange(transformers, customTransformers && map(customTransformers.after, wrapScriptTransformerFactory)); diff --git a/src/compiler/transformers/arlen22.ts b/src/compiler/transformers/arlen22.ts new file mode 100644 index 0000000000000..ecb5d6deb430f --- /dev/null +++ b/src/compiler/transformers/arlen22.ts @@ -0,0 +1,141 @@ +import { + Bundle, + chainBundle, + EmitFlags, + isFunctionLikeDeclaration, + ModifierFlags, + Node, + setEmitFlags, + SourceFile, + SyntaxKind, + TransformationContext, + TryExpression, + UnscopedEmitHelper, + visitEachChild, + VisitResult, +} from "../_namespaces/ts.js"; +const TryResultConstructor: UnscopedEmitHelper = { + name: "typescript:tryresult", + importName: "__TryResultConstructor", + scoped: false, + priority: 3, + text: ` +var TryResult = /** @class */ (function () { + function TryResult(ok, error, value) { + this.ok = ok; + this.error = error; + this.value = value; + } + TryResult.prototype[Symbol.iterator] = function () { + return [this.ok, this.error, this.value].values(); + }; + TryResult.ok = function (value) { + return new TryResult(true, undefined, value); + }; + TryResult.error = function (error) { + return new TryResult(false, error, undefined); + }; + return TryResult; +}()); +`, +}; +/** @internal */ +export function transformTryExpression(context: TransformationContext): (x: SourceFile | Bundle) => SourceFile | Bundle { + const { + factory, + hoistVariableDeclaration, + requestEmitHelper, + + } = context; + + return chainBundle(context, transformSourceFile); + + function transformSourceFile(node: SourceFile) { + if (node.isDeclarationFile) { + return node; + } + + return visitEachChild(node, visitor, context); + } + + function visitor(node: Node): VisitResult { + switch (node.kind) { + case SyntaxKind.TryExpression: + return visitTryExpression(node as TryExpression); + default: + return visitEachChild(node, visitor, context); + } + } + + function visitTryExpression(node: TryExpression): VisitResult { + let hasAwait = false, hasYield = false;; + function checkForAwait(node: Node) { + if (node.kind === SyntaxKind.AwaitExpression) hasAwait = true; + if (node.kind === SyntaxKind.YieldExpression) hasYield = true; + if (isFunctionLikeDeclaration(node)) return node; + return visitEachChild(node, checkForAwait, context); + } + checkForAwait(node); + const expression = visitEachChild(node.expression, visitor, context); + const catchVar = factory.createIdentifier("e"); + requestEmitHelper(TryResultConstructor); + + const callExpression = factory.createCallExpression( + factory.createFunctionBindCall( + factory.createFunctionExpression( + factory.createModifiersFromModifierFlags(+hasAwait && ModifierFlags.Async), + hasYield ? factory.createToken(SyntaxKind.AsteriskToken) : undefined, + /*name*/ undefined, + /*typeParameters*/ undefined, + [], + /*type*/ undefined, + factory.createBlock([ + setEmitFlags(factory.createTryStatement( + setEmitFlags( + factory.createBlock([ + factory.createReturnStatement( + factory.createGlobalMethodCall("TryResult", "ok", [ + expression + ]) + ) + ]), + EmitFlags.SingleLine + ), + factory.createCatchClause( + catchVar, + setEmitFlags( + factory.createBlock([ + factory.createReturnStatement( + factory.createGlobalMethodCall("TryResult", "error", [ + catchVar + ]) + ) + ]), + EmitFlags.SingleLine + ), + ), + /*finallyBlock*/ undefined + ), EmitFlags.SingleLine) + ]) + ), + factory.createThis(), + [] + ), + /*typeArguments*/ undefined, + [] + ); + // yield handles both sync and async + if (hasYield) { + return factory.createYieldExpression( + factory.createToken(SyntaxKind.AsteriskToken), + callExpression + ) + } else if (hasAwait) { + return factory.createAwaitExpression(callExpression); + } else { + return callExpression; + } + + } + +} diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 9b4915f1cc293..5a4ca7033a3f6 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -295,6 +295,7 @@ export const enum SyntaxKind { ConditionalExpression, TemplateExpression, YieldExpression, + TryExpression, SpreadElement, ClassExpression, OmittedExpression, @@ -1119,6 +1120,7 @@ export type HasChildren = | ConditionalExpression | TemplateExpression | YieldExpression + | TryExpression | SpreadElement | ClassExpression | ExpressionWithTypeArguments @@ -2505,6 +2507,11 @@ export interface YieldExpression extends Expression { readonly expression?: Expression; } +export interface TryExpression extends Expression { + readonly kind: SyntaxKind.TryExpression; + readonly expression: Expression; +} + export interface SyntheticExpression extends Expression { readonly kind: SyntaxKind.SyntheticExpression; readonly isSpread: boolean; @@ -5072,6 +5079,7 @@ export interface TypeChecker { getWidenedLiteralType(type: Type): Type; /** @internal */ getPromisedTypeOfPromise(promise: Type, errorNode?: Node): Type | undefined; + getTryResultType(type: Type): Type | undefined; /** * Gets the "awaited type" of a type. * @@ -8930,6 +8938,8 @@ export interface NodeFactory { createYieldExpression(asteriskToken: undefined, expression: Expression | undefined): YieldExpression; /** @internal */ createYieldExpression(asteriskToken: AsteriskToken | undefined, expression: Expression | undefined): YieldExpression; // eslint-disable-line @typescript-eslint/unified-signatures updateYieldExpression(node: YieldExpression, asteriskToken: AsteriskToken | undefined, expression: Expression | undefined): YieldExpression; + createTryExpression(expression: Expression): TryExpression; + updateTryExpression(node: TryExpression, expression: Expression): TryExpression; createSpreadElement(expression: Expression): SpreadElement; updateSpreadElement(node: SpreadElement, expression: Expression): SpreadElement; createClassExpression(modifiers: readonly ModifierLike[] | undefined, name: string | Identifier | undefined, typeParameters: readonly TypeParameterDeclaration[] | undefined, heritageClauses: readonly HeritageClause[] | undefined, members: readonly ClassElement[]): ClassExpression; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index d12ae297b81ad..b09aa04893f4f 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -559,6 +559,7 @@ import { TransientSymbol, TriviaSyntaxKind, tryCast, + TryExpression, tryRemovePrefix, TryStatement, TsConfigSourceFile, @@ -2501,9 +2502,10 @@ export function getErrorSpanForNode(sourceFile: SourceFile, node: Node): TextSpa const end = (node as CaseOrDefaultClause).statements.length > 0 ? (node as CaseOrDefaultClause).statements[0].pos : (node as CaseOrDefaultClause).end; return createTextSpanFromBounds(start, end); } + case SyntaxKind.TryExpression: case SyntaxKind.ReturnStatement: case SyntaxKind.YieldExpression: { - const pos = skipTrivia(sourceFile.text, (node as ReturnStatement | YieldExpression).pos); + const pos = skipTrivia(sourceFile.text, (node as ReturnStatement | YieldExpression | TryExpression).pos); return getSpanOfTokenAtPosition(sourceFile, pos); } case SyntaxKind.SatisfiesExpression: { @@ -3587,6 +3589,7 @@ export function isExpressionNode(node: Node): boolean { case SyntaxKind.JsxFragment: case SyntaxKind.YieldExpression: case SyntaxKind.AwaitExpression: + case SyntaxKind.TryExpression: case SyntaxKind.MetaProperty: return true; case SyntaxKind.ExpressionWithTypeArguments: @@ -5558,6 +5561,7 @@ export function getOperatorAssociativity(kind: SyntaxKind, operator: SyntaxKind, case SyntaxKind.DeleteExpression: case SyntaxKind.AwaitExpression: case SyntaxKind.ConditionalExpression: + case SyntaxKind.TryExpression: case SyntaxKind.YieldExpression: return Associativity.Right; @@ -5635,6 +5639,13 @@ export const enum OperatorPrecedence { // `yield` `*` AssignmentExpression Yield, + // NOTE: `Yield` and `Try` should have the same precedence + // AssignmentExpression: TryExpression + // TryExpression: + // `try` AssignmentExpression + TryExpression, + + // AssignmentExpression: LeftHandSideExpression `=` AssignmentExpression // AssignmentExpression: LeftHandSideExpression AssignmentOperator AssignmentExpression // AssignmentOperator: one of @@ -5810,6 +5821,9 @@ export function getOperatorPrecedence(nodeKind: SyntaxKind, operatorKind: Syntax case SyntaxKind.YieldExpression: return OperatorPrecedence.Yield; + case SyntaxKind.TryExpression: + return OperatorPrecedence.TryExpression; + case SyntaxKind.ConditionalExpression: return OperatorPrecedence.Conditional; diff --git a/src/compiler/utilitiesPublic.ts b/src/compiler/utilitiesPublic.ts index 02e22b94c895e..25dd96162510b 100644 --- a/src/compiler/utilitiesPublic.ts +++ b/src/compiler/utilitiesPublic.ts @@ -2082,6 +2082,7 @@ function isExpressionKind(kind: SyntaxKind): boolean { switch (kind) { case SyntaxKind.ConditionalExpression: case SyntaxKind.YieldExpression: + case SyntaxKind.TryExpression: case SyntaxKind.ArrowFunction: case SyntaxKind.BinaryExpression: case SyntaxKind.SpreadElement: diff --git a/src/compiler/visitorPublic.ts b/src/compiler/visitorPublic.ts index dbd49379e5750..ac196e6ed56df 100644 --- a/src/compiler/visitorPublic.ts +++ b/src/compiler/visitorPublic.ts @@ -1201,6 +1201,13 @@ const visitEachChildTable: VisitEachChildTable = { ); }, + [SyntaxKind.TryExpression]: function visitEachChildOfTryExpression(node, visitor, context, _nodesVisitor, nodeVisitor, _tokenVisitor) { + return context.factory.updateTryExpression( + node, + Debug.checkDefined(nodeVisitor(node.expression, visitor, isExpression)), + ); + }, + [SyntaxKind.SpreadElement]: function visitEachChildOfSpreadElement(node, visitor, context, _nodesVisitor, nodeVisitor, _tokenVisitor) { return context.factory.updateSpreadElement( node, diff --git a/src/lib/es5.d.ts b/src/lib/es5.d.ts index ab484e1812e7b..c50539892f05a 100644 --- a/src/lib/es5.d.ts +++ b/src/lib/es5.d.ts @@ -1,6 +1,72 @@ /// /// +///////////////////////////// +/// Arlen22 Fork Additions +///////////////////////////// + +/** + * Error result type expressed as object + */ +type TryResultErrorObject = { ok: false; error: unknown; value: undefined } + +/** + * Error result type expressed as tuple. + * + * - `error` type depends on `useUnknownInCatchVariables` tsconfig option + */ +type TryResultErrorTuple = [ok: false, error: unknown, value: undefined] + +/** + * An error result is a object that can be either destructured {@link TryResultErrorObject} or accessed by index {@link TryResultErrorTuple} + */ +type TryResultError = TryResultErrorObject & TryResultErrorTuple + +/** + * Value result type expressed as object + */ +type TryResultValueObject = { ok: true; error: undefined; value: V } + +/** + * Value result type expressed as tuple + */ +type TryResultValueTuple = [ok: true, error: undefined, value: V] + +/** + * A value result is a object that can be either destructured {@link TryResultValueObject} or accessed by index {@link TryResultValueTuple} + */ +type TryResultValue = TryResultValueObject & TryResultValueTuple + +/** + * A result is a object that can represent the result of either a failed or successful operation. + */ +type TryResult = TryResultError | TryResultValue + +interface ResultConstructor { + /** + * Creates a result from a tuple + * + * @example + * + * new Result(true, undefined, 42) + * new Result(false, new Error('Something went wrong')) + */ + new (...args: TryResultValueTuple | TryResultErrorTuple): TryResult + + /** + * Creates a result for a successful operation + */ + ok(value: V): TryResult + + /** + * Creates a result for a failed operation + */ + error(error: unknown): TryResult +} + +declare const TryResult: ResultConstructor + + ///////////////////////////// /// ECMAScript APIs ///////////////////////////// diff --git a/src/services/formatting/rules.ts b/src/services/formatting/rules.ts index 45d80841d1894..d36413e973f06 100644 --- a/src/services/formatting/rules.ts +++ b/src/services/formatting/rules.ts @@ -164,6 +164,7 @@ export function getAllRules(): RuleSpec[] { rule("NoSpaceBetweenYieldKeywordAndStar", SyntaxKind.YieldKeyword, SyntaxKind.AsteriskToken, [isNonJsxSameLineTokenContext, isYieldOrYieldStarWithOperand], RuleAction.DeleteSpace), rule("SpaceBetweenYieldOrYieldStarAndOperand", [SyntaxKind.YieldKeyword, SyntaxKind.AsteriskToken], anyToken, [isNonJsxSameLineTokenContext, isYieldOrYieldStarWithOperand], RuleAction.InsertSpace), + rule("SpaceBetweenTryAndOperand", [SyntaxKind.TryKeyword], anyToken, [isNonJsxSameLineTokenContext, isTryExpression], RuleAction.InsertSpace), rule("NoSpaceBetweenReturnAndSemicolon", SyntaxKind.ReturnKeyword, SyntaxKind.SemicolonToken, [isNonJsxSameLineTokenContext], RuleAction.DeleteSpace), rule("SpaceAfterCertainKeywords", [SyntaxKind.VarKeyword, SyntaxKind.ThrowKeyword, SyntaxKind.NewKeyword, SyntaxKind.DeleteKeyword, SyntaxKind.ReturnKeyword, SyntaxKind.TypeOfKeyword, SyntaxKind.AwaitKeyword], anyToken, [isNonJsxSameLineTokenContext], RuleAction.InsertSpace), @@ -885,6 +886,10 @@ function isYieldOrYieldStarWithOperand(context: FormattingContext): boolean { return context.contextNode.kind === SyntaxKind.YieldExpression && (context.contextNode as YieldExpression).expression !== undefined; } +function isTryExpression(context: FormattingContext): boolean { + return context.contextNode.kind === SyntaxKind.TryExpression; +} + function isNonNullAssertionContext(context: FormattingContext): boolean { return context.contextNode.kind === SyntaxKind.NonNullExpression; } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index fbbcb67a106b9..36bbcb9d7d7a3 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -253,6 +253,7 @@ import { isTemplateLiteralKind, isToken, isTransientSymbol, + isTryExpression, isTypeAliasDeclaration, isTypeElement, isTypeNode, @@ -1500,7 +1501,8 @@ function getAdjustedLocation(node: Node, forRename: boolean): Node { node.kind === SyntaxKind.TypeOfKeyword && isTypeOfExpression(parent) || node.kind === SyntaxKind.AwaitKeyword && isAwaitExpression(parent) || node.kind === SyntaxKind.YieldKeyword && isYieldExpression(parent) || - node.kind === SyntaxKind.DeleteKeyword && isDeleteExpression(parent) + node.kind === SyntaxKind.DeleteKeyword && isDeleteExpression(parent) || + node.kind === SyntaxKind.TryKeyword && isTryExpression(parent) ) { if (parent.expression) { return skipOuterExpressions(parent.expression);