From 4ed80b62df9531db3863854c702809eb3b0e7bb7 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Wed, 16 Oct 2019 11:24:24 -0700 Subject: [PATCH 01/47] Stop pre-emptively creating directories Checking for directory existence is expensive and frequently indicates success. Instead of pre-emptively creating the directory containing a file to be written, attempt to create the file and only do the directory scaffolding if the write fails. Appears to reduce file write time by 10-20% for a file-I/O heavy partner build. Thanks to @rbuckton for the suggestion! --- src/compiler/program.ts | 22 +++++++++++++++++----- src/compiler/sys.ts | 17 +++++++++++++---- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 0c8444daa2..b9495c787c 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -151,13 +151,16 @@ namespace ts { function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { try { performance.mark("beforeIOWrite"); - ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); - if (isWatchSet(options) && system.createHash && system.getModifiedTime) { - writeFileIfUpdated(fileName, data, writeByteOrderMark); + // PERF: Checking for directory existence is expensive. + // Instead, assume the directory exists and fall back + // to creating it if the file write fails. + try { + writeFileWorker(fileName, data, writeByteOrderMark); } - else { - system.writeFile(fileName, data, writeByteOrderMark); + catch (_) { + ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); + writeFileWorker(fileName, data, writeByteOrderMark); } performance.mark("afterIOWrite"); @@ -170,6 +173,15 @@ namespace ts { } } + function writeFileWorker(fileName: string, data: string, writeByteOrderMark: boolean) { + if (isWatchSet(options) && system.createHash && system.getModifiedTime) { + writeFileIfUpdated(fileName, data, writeByteOrderMark); + } + else { + system.writeFile(fileName, data, writeByteOrderMark); + } + } + function getDefaultLibLocation(): string { return getDirectoryPath(normalizePath(system.getExecutingFilePath())); } diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index ad866cba13..c85bb48bb9 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -541,11 +541,20 @@ namespace ts { // patch writefile to create folder before writing the file const originalWriteFile = sys.writeFile; sys.writeFile = (path, data, writeBom) => { - const directoryPath = getDirectoryPath(normalizeSlashes(path)); - if (directoryPath && !sys.directoryExists(directoryPath)) { - recursiveCreateDirectory(directoryPath, sys); + // PERF: Checking for directory existence is expensive. + // Instead, assume the directory exists and fall back + // to creating it if the file write fails. + try { + originalWriteFile.call(sys, path, data, writeBom); + } + catch (_) { + const directoryPath = getDirectoryPath(normalizeSlashes(path)); + if (directoryPath && !sys.directoryExists(directoryPath)) { + recursiveCreateDirectory(directoryPath, sys); + } + + originalWriteFile.call(sys, path, data, writeBom); } - originalWriteFile.call(sys, path, data, writeBom); }; } From b6659e5d6eed1a2daf5cca5a87febb5cfceb5372 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Wed, 16 Oct 2019 11:31:44 -0700 Subject: [PATCH 02/47] Inline function to tidy up control flow --- src/compiler/program.ts | 79 +++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 43 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index b9495c787c..44cf374ea7 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -96,7 +96,7 @@ namespace ts { if (existingDirectories.has(directoryPath)) { return true; } - if (system.directoryExists(directoryPath)) { + if ((compilerHost.directoryExists || system.directoryExists)(directoryPath)) { existingDirectories.set(directoryPath, true); return true; } @@ -107,47 +107,10 @@ namespace ts { if (directoryPath.length > getRootLength(directoryPath) && !directoryExists(directoryPath)) { const parentDirectory = getDirectoryPath(directoryPath); ensureDirectoriesExist(parentDirectory); - if (compilerHost.createDirectory) { - compilerHost.createDirectory(directoryPath); - } - else { - system.createDirectory(directoryPath); - } + (compilerHost.createDirectory || system.createDirectory)(directoryPath); } } - let outputFingerprints: Map; - - function writeFileIfUpdated(fileName: string, data: string, writeByteOrderMark: boolean): void { - if (!outputFingerprints) { - outputFingerprints = createMap(); - } - - const hash = system.createHash!(data); // TODO: GH#18217 - const mtimeBefore = system.getModifiedTime!(fileName); // TODO: GH#18217 - - if (mtimeBefore) { - const fingerprint = outputFingerprints.get(fileName); - // If output has not been changed, and the file has no external modification - if (fingerprint && - fingerprint.byteOrderMark === writeByteOrderMark && - fingerprint.hash === hash && - fingerprint.mtime.getTime() === mtimeBefore.getTime()) { - return; - } - } - - system.writeFile(fileName, data, writeByteOrderMark); - - const mtimeAfter = system.getModifiedTime!(fileName) || missingFileModifiedTime; // TODO: GH#18217 - - outputFingerprints.set(fileName, { - hash, - byteOrderMark: writeByteOrderMark, - mtime: mtimeAfter - }); - } - function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { try { performance.mark("beforeIOWrite"); @@ -155,6 +118,9 @@ namespace ts { // PERF: Checking for directory existence is expensive. // Instead, assume the directory exists and fall back // to creating it if the file write fails. + // NOTE: If patchWriteFileEnsuringDirectory has been called, + // the file write will do its own directory creation and + // the ensureDirectoriesExist call will always be redundant. try { writeFileWorker(fileName, data, writeByteOrderMark); } @@ -173,13 +139,40 @@ namespace ts { } } + let outputFingerprints: Map; function writeFileWorker(fileName: string, data: string, writeByteOrderMark: boolean) { - if (isWatchSet(options) && system.createHash && system.getModifiedTime) { - writeFileIfUpdated(fileName, data, writeByteOrderMark); - } - else { + if (!isWatchSet(options) || !system.createHash || !system.getModifiedTime) { system.writeFile(fileName, data, writeByteOrderMark); + return; } + + if (!outputFingerprints) { + outputFingerprints = createMap(); + } + + const hash = system.createHash(data); + const mtimeBefore = system.getModifiedTime(fileName); + + if (mtimeBefore) { + const fingerprint = outputFingerprints.get(fileName); + // If output has not been changed, and the file has no external modification + if (fingerprint && + fingerprint.byteOrderMark === writeByteOrderMark && + fingerprint.hash === hash && + fingerprint.mtime.getTime() === mtimeBefore.getTime()) { + return; + } + } + + system.writeFile(fileName, data, writeByteOrderMark); + + const mtimeAfter = system.getModifiedTime(fileName) || missingFileModifiedTime; + + outputFingerprints.set(fileName, { + hash, + byteOrderMark: writeByteOrderMark, + mtime: mtimeAfter + }); } function getDefaultLibLocation(): string { From f39b49d756110e24ac75c81413cb13e8a0a423b0 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Thu, 17 Oct 2019 11:36:45 -0700 Subject: [PATCH 03/47] Update another writeFile call-site --- src/compiler/program.ts | 2 +- src/compiler/sys.ts | 2 +- src/compiler/watch.ts | 15 +++++++++++++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 44cf374ea7..6a697b9c18 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -124,7 +124,7 @@ namespace ts { try { writeFileWorker(fileName, data, writeByteOrderMark); } - catch (_) { + catch { ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); writeFileWorker(fileName, data, writeByteOrderMark); } diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index c85bb48bb9..c4a5012b26 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -547,7 +547,7 @@ namespace ts { try { originalWriteFile.call(sys, path, data, writeBom); } - catch (_) { + catch { const directoryPath = getDirectoryPath(normalizeSlashes(path)); if (directoryPath && !sys.directoryExists(directoryPath)) { recursiveCreateDirectory(directoryPath, sys); diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index 06871fc9ca..f6e334aa47 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -307,9 +307,20 @@ namespace ts { function writeFile(fileName: string, text: string, writeByteOrderMark: boolean, onError: (message: string) => void) { try { performance.mark("beforeIOWrite"); - ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); - host.writeFile!(fileName, text, writeByteOrderMark); + // PERF: Checking for directory existence is expensive. + // Instead, assume the directory exists and fall back + // to creating it if the file write fails. + // NOTE: If patchWriteFileEnsuringDirectory has been called, + // the file write will do its own directory creation and + // the ensureDirectoriesExist call will always be redundant. + try { + host.writeFile!(fileName, text, writeByteOrderMark); + } + catch { + ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); + host.writeFile!(fileName, text, writeByteOrderMark); + } performance.mark("afterIOWrite"); performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); From 205b3dae3bb86b7b78864f585364f73b0830d7e7 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Thu, 17 Oct 2019 16:26:43 -0700 Subject: [PATCH 04/47] Extract shared helper --- src/compiler/program.ts | 27 ++++++++------------------- src/compiler/sys.ts | 35 ++++++++--------------------------- src/compiler/utilities.ts | 30 ++++++++++++++++++++++++++++++ src/compiler/watch.ts | 21 ++------------------- 4 files changed, 48 insertions(+), 65 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 6a697b9c18..0be96b7d55 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -103,31 +103,20 @@ namespace ts { return false; } - function ensureDirectoriesExist(directoryPath: string) { - if (directoryPath.length > getRootLength(directoryPath) && !directoryExists(directoryPath)) { - const parentDirectory = getDirectoryPath(directoryPath); - ensureDirectoriesExist(parentDirectory); - (compilerHost.createDirectory || system.createDirectory)(directoryPath); - } - } - function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) { try { performance.mark("beforeIOWrite"); - // PERF: Checking for directory existence is expensive. - // Instead, assume the directory exists and fall back - // to creating it if the file write fails. // NOTE: If patchWriteFileEnsuringDirectory has been called, - // the file write will do its own directory creation and + // the system.writeFile will do its own directory creation and // the ensureDirectoriesExist call will always be redundant. - try { - writeFileWorker(fileName, data, writeByteOrderMark); - } - catch { - ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); - writeFileWorker(fileName, data, writeByteOrderMark); - } + writeFileEnsuringDirectories( + fileName, + data, + writeByteOrderMark, + writeFileWorker, + compilerHost.createDirectory || system.createDirectory, + directoryExists); performance.mark("afterIOWrite"); performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index c4a5012b26..9de6193d73 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -522,17 +522,6 @@ namespace ts { } } - function recursiveCreateDirectory(directoryPath: string, sys: System) { - const basePath = getDirectoryPath(directoryPath); - const shouldCreateParent = basePath !== "" && directoryPath !== basePath && !sys.directoryExists(basePath); - if (shouldCreateParent) { - recursiveCreateDirectory(basePath, sys); - } - if (shouldCreateParent || !sys.directoryExists(directoryPath)) { - sys.createDirectory(directoryPath); - } - } - /** * patch writefile to create folder before writing the file */ @@ -540,22 +529,14 @@ namespace ts { export function patchWriteFileEnsuringDirectory(sys: System) { // patch writefile to create folder before writing the file const originalWriteFile = sys.writeFile; - sys.writeFile = (path, data, writeBom) => { - // PERF: Checking for directory existence is expensive. - // Instead, assume the directory exists and fall back - // to creating it if the file write fails. - try { - originalWriteFile.call(sys, path, data, writeBom); - } - catch { - const directoryPath = getDirectoryPath(normalizeSlashes(path)); - if (directoryPath && !sys.directoryExists(directoryPath)) { - recursiveCreateDirectory(directoryPath, sys); - } - - originalWriteFile.call(sys, path, data, writeBom); - } - }; + sys.writeFile = (path, data, writeBom) => + writeFileEnsuringDirectories( + path, + data, + writeBom, + (p, d, w) => originalWriteFile.call(sys, p, d, w), + sys.createDirectory, + sys.directoryExists); } /*@internal*/ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 2fb1173dee..8f7602cbe2 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3700,6 +3700,36 @@ namespace ts { }, sourceFiles); } + function ensureDirectoriesExist( + directoryPath: string, + createDirectory: (path: string) => void, + directoryExists: (path: string) => boolean): void { + if (directoryPath.length > getRootLength(directoryPath) && !directoryExists(directoryPath)) { + const parentDirectory = getDirectoryPath(directoryPath); + ensureDirectoriesExist(parentDirectory, createDirectory, directoryExists); + createDirectory(directoryPath); + } + } + + export function writeFileEnsuringDirectories( + path: string, + data: string, + writeByteOrderMark: boolean | undefined, + writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void, + createDirectory: (path: string) => void, + directoryExists: (path: string) => boolean): void { + + // PERF: Checking for directory existence is expensive. Instead, assume the directory exists + // and fall back to creating it if the file write fails. + try { + writeFile(path, data, writeByteOrderMark); + } + catch { + ensureDirectoriesExist(getDirectoryPath(normalizePath(path)), createDirectory, directoryExists); + writeFile(path, data, writeByteOrderMark); + } + } + export function getLineOfLocalPosition(currentSourceFile: SourceFile, pos: number) { return getLineAndCharacterOfPosition(currentSourceFile, pos).line; } diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index f6e334aa47..cc6bd0bcce 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -296,31 +296,14 @@ namespace ts { readDirectory: maybeBind(host, host.readDirectory), }; - function ensureDirectoriesExist(directoryPath: string) { - if (directoryPath.length > getRootLength(directoryPath) && !host.directoryExists!(directoryPath)) { - const parentDirectory = getDirectoryPath(directoryPath); - ensureDirectoriesExist(parentDirectory); - if (host.createDirectory) host.createDirectory(directoryPath); - } - } - function writeFile(fileName: string, text: string, writeByteOrderMark: boolean, onError: (message: string) => void) { try { performance.mark("beforeIOWrite"); - // PERF: Checking for directory existence is expensive. - // Instead, assume the directory exists and fall back - // to creating it if the file write fails. // NOTE: If patchWriteFileEnsuringDirectory has been called, - // the file write will do its own directory creation and + // the host.writeFile will do its own directory creation and // the ensureDirectoriesExist call will always be redundant. - try { - host.writeFile!(fileName, text, writeByteOrderMark); - } - catch { - ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName))); - host.writeFile!(fileName, text, writeByteOrderMark); - } + writeFileEnsuringDirectories(fileName, text, writeByteOrderMark, host.writeFile!, host.createDirectory!, host.directoryExists!); performance.mark("afterIOWrite"); performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); From d2fab65df691b998c5a1acb2fcbca9c25e493b8d Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 17 Oct 2019 17:04:45 -0700 Subject: [PATCH 05/47] Added test. --- src/compiler/types.ts | 1 + ...InsertQuestionDotWithUserPreferencesOff.ts | 20 +++++++++++++++++++ tests/cases/fourslash/fourslash.ts | 1 + 3 files changed, 22 insertions(+) create mode 100644 tests/cases/fourslash/completionNoAutoInsertQuestionDotWithUserPreferencesOff.ts diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 03b849b36c..b444c9d104 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6441,6 +6441,7 @@ namespace ts { readonly disableSuggestions?: boolean; readonly quotePreference?: "auto" | "double" | "single"; readonly includeCompletionsForModuleExports?: boolean; + readonly includeAutomaticOptionalChainCompletions?: boolean; readonly includeCompletionsWithInsertText?: boolean; readonly importModuleSpecifierPreference?: "relative" | "non-relative"; /** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */ diff --git a/tests/cases/fourslash/completionNoAutoInsertQuestionDotWithUserPreferencesOff.ts b/tests/cases/fourslash/completionNoAutoInsertQuestionDotWithUserPreferencesOff.ts new file mode 100644 index 0000000000..a1071a37f9 --- /dev/null +++ b/tests/cases/fourslash/completionNoAutoInsertQuestionDotWithUserPreferencesOff.ts @@ -0,0 +1,20 @@ +/// +// @strict: true + +//// interface User { +//// address?: { +//// city: string; +//// "postal code": string; +//// } +//// }; +//// declare const user: User; +//// user.address[|./**/|] + +verify.completions({ + marker: "", + exact: [], + preferences: { + includeInsertTextCompletions: true, + includeAutomaticOptionalChainCompletions: false + }, +}); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 5672f43043..cd184df3be 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -583,6 +583,7 @@ declare namespace FourSlashInterface { readonly quotePreference?: "double" | "single"; readonly includeCompletionsForModuleExports?: boolean; readonly includeInsertTextCompletions?: boolean; + readonly includeAutomaticOptionalChainCompletions?: boolean; readonly importModuleSpecifierPreference?: "relative" | "non-relative"; readonly importModuleSpecifierEnding?: "minimal" | "index" | "js"; } From 73e9715da5584b8be4001cdf5746e4d42516a57e Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 17 Oct 2019 17:05:08 -0700 Subject: [PATCH 06/47] Added option 'includeAutomaticOptionalChainCompletions' to disable '?.' completions. --- src/services/completions.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/services/completions.ts b/src/services/completions.ts index 11f92c3d4e..75b0d3a1c1 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -338,7 +338,12 @@ namespace ts.Completions { ): CompletionEntry | undefined { let insertText: string | undefined; let replacementSpan: TextSpan | undefined; + const insertQuestionDot = origin && originIsNullableMember(origin); + if (insertQuestionDot && preferences.includeAutomaticOptionalChainCompletions === false) { + return undefined; + } + const useBraces = origin && originIsSymbolMember(origin) || needsConvertPropertyAccess; if (origin && originIsThisType(origin)) { insertText = needsConvertPropertyAccess From 15445e156a8f26d7fa5db4fe90e510c66011d689 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 17 Oct 2019 17:17:19 -0700 Subject: [PATCH 07/47] Add user preference to the protocol. --- src/server/protocol.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/server/protocol.ts b/src/server/protocol.ts index c86876f1ca..fb0969fbef 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -3007,6 +3007,12 @@ namespace ts.server.protocol { * For those entries, The `insertText` and `replacementSpan` properties will be set to change from `.x` property access to `["x"]`. */ readonly includeCompletionsWithInsertText?: boolean; + /** + * Unless this option is disabled, or `includeCompletionsWithInsertText` is not enabled, + * member completion lists triggered with `.` will include entries on potentially-null and potentially-undefined + * values, with insertion text to replace preceding `.` tokens with `?.`. + */ + readonly includeAutomaticOptionalChainCompletions?: boolean; readonly importModuleSpecifierPreference?: "relative" | "non-relative"; readonly allowTextChangesInNewFiles?: boolean; readonly lazyConfiguredProjectsFromExternalProject?: boolean; From 3d7451c08f98f6710414fe2c90c4a4d1d5709b06 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 17 Oct 2019 17:40:41 -0700 Subject: [PATCH 08/47] Accepted baselines. --- tests/baselines/reference/api/tsserverlibrary.d.ts | 7 +++++++ tests/baselines/reference/api/typescript.d.ts | 1 + 2 files changed, 8 insertions(+) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index aa1945e74b..9aef523202 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3171,6 +3171,7 @@ declare namespace ts { readonly disableSuggestions?: boolean; readonly quotePreference?: "auto" | "double" | "single"; readonly includeCompletionsForModuleExports?: boolean; + readonly includeAutomaticOptionalChainCompletions?: boolean; readonly includeCompletionsWithInsertText?: boolean; readonly importModuleSpecifierPreference?: "relative" | "non-relative"; /** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */ @@ -8295,6 +8296,12 @@ declare namespace ts.server.protocol { * For those entries, The `insertText` and `replacementSpan` properties will be set to change from `.x` property access to `["x"]`. */ readonly includeCompletionsWithInsertText?: boolean; + /** + * Unless this option is disabled, or `includeCompletionsWithInsertText` is not enabled, + * member completion lists triggered with `.` will include entries on potentially-null and potentially-undefined + * values, with insertion text to replace preceding `.` tokens with `?.`. + */ + readonly includeAutomaticOptionalChainCompletions?: boolean; readonly importModuleSpecifierPreference?: "relative" | "non-relative"; readonly allowTextChangesInNewFiles?: boolean; readonly lazyConfiguredProjectsFromExternalProject?: boolean; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 7c756d8a0a..7361ccb676 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3171,6 +3171,7 @@ declare namespace ts { readonly disableSuggestions?: boolean; readonly quotePreference?: "auto" | "double" | "single"; readonly includeCompletionsForModuleExports?: boolean; + readonly includeAutomaticOptionalChainCompletions?: boolean; readonly includeCompletionsWithInsertText?: boolean; readonly importModuleSpecifierPreference?: "relative" | "non-relative"; /** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */ From 85e09134be6fbd0077486274672d4f67748e0203 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 17 Oct 2019 17:45:13 -0700 Subject: [PATCH 09/47] Update comment. --- src/server/protocol.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/protocol.ts b/src/server/protocol.ts index fb0969fbef..925e7638f9 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -3008,7 +3008,7 @@ namespace ts.server.protocol { */ readonly includeCompletionsWithInsertText?: boolean; /** - * Unless this option is disabled, or `includeCompletionsWithInsertText` is not enabled, + * Unless this option is `false`, or `includeCompletionsWithInsertText` is not enabled, * member completion lists triggered with `.` will include entries on potentially-null and potentially-undefined * values, with insertion text to replace preceding `.` tokens with `?.`. */ From 30cca63eaf609261839aee091d36843acef5bb0f Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Thu, 17 Oct 2019 17:45:25 -0700 Subject: [PATCH 10/47] Accepted baselines. --- tests/baselines/reference/api/tsserverlibrary.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 9aef523202..b02074e75e 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -8297,7 +8297,7 @@ declare namespace ts.server.protocol { */ readonly includeCompletionsWithInsertText?: boolean; /** - * Unless this option is disabled, or `includeCompletionsWithInsertText` is not enabled, + * Unless this option is `false`, or `includeCompletionsWithInsertText` is not enabled, * member completion lists triggered with `.` will include entries on potentially-null and potentially-undefined * values, with insertion text to replace preceding `.` tokens with `?.`. */ From d3df927c7ab899eba1d4c575d96de8a03ddc1ffd Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 18 Oct 2019 10:50:03 -0700 Subject: [PATCH 11/47] Optional chain control flow analysis fixes --- src/compiler/checker.ts | 57 ++++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 96a783a6c9..33c7962b64 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19175,20 +19175,25 @@ namespace ts { if (isMatchingReference(reference, expr)) { type = narrowTypeBySwitchOnDiscriminant(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd); } - else if (isMatchingReferenceDiscriminant(expr, type)) { - type = narrowTypeByDiscriminant( - type, - expr as AccessExpression, - t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd)); - } else if (expr.kind === SyntaxKind.TypeOfExpression && isMatchingReference(reference, (expr as TypeOfExpression).expression)) { type = narrowBySwitchOnTypeOf(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd); } - else if (containsMatchingReferenceDiscriminant(reference, expr)) { - type = declaredType; - } - else if (flow.clauseStart === flow.clauseEnd && isExhaustiveSwitchStatement(flow.switchStatement)) { - return unreachableNeverType; + else { + if (strictNullChecks && optionalChainContainsReference(expr, reference)) { + type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd); + } + if (isMatchingReferenceDiscriminant(expr, type)) { + type = narrowTypeByDiscriminant( + type, + expr as AccessExpression, + t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd)); + } + else if (containsMatchingReferenceDiscriminant(reference, expr)) { + type = declaredType; + } + else if (flow.clauseStart === flow.clauseEnd && isExhaustiveSwitchStatement(flow.switchStatement)) { + return unreachableNeverType; + } } return createFlowType(type, isIncomplete(flowType)); } @@ -19384,12 +19389,12 @@ namespace ts { if (isMatchingReference(reference, right)) { return narrowTypeByEquality(type, operator, left, assumeTrue); } - if (assumeTrue && strictNullChecks) { + if (strictNullChecks) { if (optionalChainContainsReference(left, reference)) { - type = narrowTypeByOptionalChainContainment(type, operator, right); + type = narrowTypeByOptionalChainContainment(type, operator, right, assumeTrue); } else if (optionalChainContainsReference(right, reference)) { - type = narrowTypeByOptionalChainContainment(type, operator, left); + type = narrowTypeByOptionalChainContainment(type, operator, left, assumeTrue); } } if (isMatchingReferenceDiscriminant(left, declaredType)) { @@ -19416,15 +19421,21 @@ namespace ts { return type; } - function narrowTypeByOptionalChainContainment(type: Type, operator: SyntaxKind, value: Expression): Type { - // We are in the true branch of obj?.foo === value or obj?.foo !== value. We remove undefined and null from + function narrowTypeByOptionalChainContainment(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type { + const op = assumeTrue ? operator : + operator === SyntaxKind.EqualsEqualsToken ? SyntaxKind.ExclamationEqualsToken : + operator === SyntaxKind.EqualsEqualsEqualsToken ? SyntaxKind.ExclamationEqualsEqualsToken : + operator === SyntaxKind.ExclamationEqualsToken ? SyntaxKind.EqualsEqualsToken : + operator === SyntaxKind.ExclamationEqualsEqualsToken ? SyntaxKind.EqualsEqualsEqualsToken : + operator; + // We are in a branch of obj?.foo === value or obj?.foo !== value. We remove undefined and null from // the type of obj if (a) the operator is === and the type of value doesn't include undefined or (b) the // operator is !== and the type of value is undefined. const valueType = getTypeOfExpression(value); - return operator === SyntaxKind.EqualsEqualsToken && !(getTypeFacts(valueType) & TypeFacts.EQUndefinedOrNull) || - operator === SyntaxKind.EqualsEqualsEqualsToken && !(getTypeFacts(valueType) & TypeFacts.EQUndefined) || - operator === SyntaxKind.ExclamationEqualsToken && valueType.flags & TypeFlags.Nullable || - operator === SyntaxKind.ExclamationEqualsEqualsToken && valueType.flags & TypeFlags.Undefined ? + return op === SyntaxKind.EqualsEqualsToken && !(getTypeFacts(valueType) & TypeFacts.EQUndefinedOrNull) || + op === SyntaxKind.EqualsEqualsEqualsToken && !(getTypeFacts(valueType) & TypeFacts.EQUndefined) || + op === SyntaxKind.ExclamationEqualsToken && valueType.flags & TypeFlags.Nullable || + op === SyntaxKind.ExclamationEqualsEqualsToken && valueType.flags & TypeFlags.Undefined ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; } @@ -19526,6 +19537,12 @@ namespace ts { } } + function narrowTypeBySwitchOptionalChainContainment(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { + const noClauseIsDefaultOrUndefined = clauseStart !== clauseEnd && + every(getSwitchClauseTypes(switchStatement).slice(clauseStart, clauseEnd), t => !(t.flags & (TypeFlags.Undefined | TypeFlags.Never))); + return noClauseIsDefaultOrUndefined ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; + } + function narrowTypeBySwitchOnDiscriminant(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { // We only narrow if all case expressions specify // values with unit types, except for the case where From 9b6a02716798145a2edad7369178e02971fd9636 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 18 Oct 2019 13:03:25 -0700 Subject: [PATCH 12/47] Perform checks prior to calling `addTypeProperties`. --- src/services/completions.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index 75b0d3a1c1..ac54cf9c69 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -340,10 +340,6 @@ namespace ts.Completions { let replacementSpan: TextSpan | undefined; const insertQuestionDot = origin && originIsNullableMember(origin); - if (insertQuestionDot && preferences.includeAutomaticOptionalChainCompletions === false) { - return undefined; - } - const useBraces = origin && originIsSymbolMember(origin) || needsConvertPropertyAccess; if (origin && originIsThisType(origin)) { insertText = needsConvertPropertyAccess @@ -785,7 +781,7 @@ namespace ts.Completions { sourceFile: SourceFile, isUncheckedFile: boolean, position: number, - preferences: Pick, + preferences: Pick, detailsEntryId: CompletionEntryIdentifier | undefined, host: LanguageServiceHost, ): CompletionData | Request | undefined { @@ -1124,6 +1120,9 @@ namespace ts.Completions { insertQuestionDot = isRightOfDot && !isRightOfQuestionDot; type = type.getNonNullableType(); } + if (insertQuestionDot && preferences.includeAutomaticOptionalChainCompletions === false) { + return; + } addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); } @@ -1145,6 +1144,9 @@ namespace ts.Completions { insertQuestionDot = isRightOfDot && !isRightOfQuestionDot; type = type.getNonNullableType(); } + if (insertQuestionDot && preferences.includeAutomaticOptionalChainCompletions === false) { + return; + } addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); } } From cdf1ab2decee9e0af76d776d53d8238ad9fcea18 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Fri, 18 Oct 2019 14:13:14 -0700 Subject: [PATCH 13/47] Bind @class in the right place -- bindWorker not bindChildrenWorker (#34575) Also add an assert to make future mismatches fail in an obvious place instead of in a while loop. --- src/compiler/binder.ts | 5 ++--- src/compiler/checker.ts | 1 + ...lOfPropertylessConstructorFunction.errors.txt | 14 ++++++++++++++ ...callOfPropertylessConstructorFunction.symbols | 14 ++++++++++++++ .../callOfPropertylessConstructorFunction.types | 16 ++++++++++++++++ .../callOfPropertylessConstructorFunction.ts | 11 +++++++++++ 6 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 tests/baselines/reference/callOfPropertylessConstructorFunction.errors.txt create mode 100644 tests/baselines/reference/callOfPropertylessConstructorFunction.symbols create mode 100644 tests/baselines/reference/callOfPropertylessConstructorFunction.types create mode 100644 tests/cases/conformance/jsdoc/callOfPropertylessConstructorFunction.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 0b0ef4825b..8009bf45c8 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -821,9 +821,6 @@ namespace ts { case SyntaxKind.JSDocEnumTag: bindJSDocTypeAlias(node as JSDocTypedefTag | JSDocCallbackTag | JSDocEnumTag); break; - case SyntaxKind.JSDocClassTag: - bindJSDocClassTag(node as JSDocClassTag); - break; // In source files and blocks, bind functions first to match hoisting that occurs at runtime case SyntaxKind.SourceFile: { bindEachFunctionsFirst((node as SourceFile).statements); @@ -2454,6 +2451,8 @@ namespace ts { case SyntaxKind.JSDocTypeLiteral: case SyntaxKind.MappedType: return bindAnonymousTypeWorker(node as TypeLiteralNode | MappedTypeNode | JSDocTypeLiteral); + case SyntaxKind.JSDocClassTag: + return bindJSDocClassTag(node as JSDocClassTag); case SyntaxKind.ObjectLiteralExpression: return bindObjectLiteralExpression(node); case SyntaxKind.FunctionExpression: diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 50c6d8d85a..45cabbb3f9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7688,6 +7688,7 @@ namespace ts { // The outer type parameters are those defined by enclosing generic classes, methods, or functions. function getOuterTypeParametersOfClassOrInterface(symbol: Symbol): TypeParameter[] | undefined { const declaration = symbol.flags & SymbolFlags.Class ? symbol.valueDeclaration : getDeclarationOfKind(symbol, SyntaxKind.InterfaceDeclaration)!; + Debug.assert(!!declaration, "Class was missing valueDeclaration -OR- non-class had no interface declarations"); return getOuterTypeParameters(declaration); } diff --git a/tests/baselines/reference/callOfPropertylessConstructorFunction.errors.txt b/tests/baselines/reference/callOfPropertylessConstructorFunction.errors.txt new file mode 100644 index 0000000000..0abc6cf8d0 --- /dev/null +++ b/tests/baselines/reference/callOfPropertylessConstructorFunction.errors.txt @@ -0,0 +1,14 @@ +tests/cases/conformance/jsdoc/callOfPropertylessConstructorFunction.js(7,1): error TS2348: Value of type 'typeof Dependency' is not callable. Did you mean to include 'new'? + + +==== tests/cases/conformance/jsdoc/callOfPropertylessConstructorFunction.js (1 errors) ==== + /** + * @constructor + */ + function Dependency(j) { + return j + } + Dependency({}) + ~~~~~~~~~~~~~~ +!!! error TS2348: Value of type 'typeof Dependency' is not callable. Did you mean to include 'new'? + \ No newline at end of file diff --git a/tests/baselines/reference/callOfPropertylessConstructorFunction.symbols b/tests/baselines/reference/callOfPropertylessConstructorFunction.symbols new file mode 100644 index 0000000000..94ebfa6cef --- /dev/null +++ b/tests/baselines/reference/callOfPropertylessConstructorFunction.symbols @@ -0,0 +1,14 @@ +=== tests/cases/conformance/jsdoc/callOfPropertylessConstructorFunction.js === +/** + * @constructor + */ +function Dependency(j) { +>Dependency : Symbol(Dependency, Decl(callOfPropertylessConstructorFunction.js, 0, 0)) +>j : Symbol(j, Decl(callOfPropertylessConstructorFunction.js, 3, 20)) + + return j +>j : Symbol(j, Decl(callOfPropertylessConstructorFunction.js, 3, 20)) +} +Dependency({}) +>Dependency : Symbol(Dependency, Decl(callOfPropertylessConstructorFunction.js, 0, 0)) + diff --git a/tests/baselines/reference/callOfPropertylessConstructorFunction.types b/tests/baselines/reference/callOfPropertylessConstructorFunction.types new file mode 100644 index 0000000000..1294e4164c --- /dev/null +++ b/tests/baselines/reference/callOfPropertylessConstructorFunction.types @@ -0,0 +1,16 @@ +=== tests/cases/conformance/jsdoc/callOfPropertylessConstructorFunction.js === +/** + * @constructor + */ +function Dependency(j) { +>Dependency : typeof Dependency +>j : any + + return j +>j : any +} +Dependency({}) +>Dependency({}) : any +>Dependency : typeof Dependency +>{} : {} + diff --git a/tests/cases/conformance/jsdoc/callOfPropertylessConstructorFunction.ts b/tests/cases/conformance/jsdoc/callOfPropertylessConstructorFunction.ts new file mode 100644 index 0000000000..213b66908d --- /dev/null +++ b/tests/cases/conformance/jsdoc/callOfPropertylessConstructorFunction.ts @@ -0,0 +1,11 @@ +// @allowJs: true +// @checkJs: true +// @noEmit: true +// @Filename: callOfPropertylessConstructorFunction.js +/** + * @constructor + */ +function Dependency(j) { + return j +} +Dependency({}) From 218bbcd669733b2df752501202730d8868480fac Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 18 Oct 2019 15:23:56 -0700 Subject: [PATCH 14/47] Don't immediately return in getMemberSymbols. --- src/services/completions.ts | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/services/completions.ts b/src/services/completions.ts index ac54cf9c69..076cc32f37 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -1117,11 +1117,17 @@ namespace ts.Completions { let type = typeChecker.getTypeOfSymbolAtLocation(symbol, node).getNonOptionalType(); let insertQuestionDot = false; if (type.isNullableType()) { - insertQuestionDot = isRightOfDot && !isRightOfQuestionDot; - type = type.getNonNullableType(); - } - if (insertQuestionDot && preferences.includeAutomaticOptionalChainCompletions === false) { - return; + const canCorrectToQuestionDot = + isRightOfDot && + !isRightOfQuestionDot && + preferences.includeAutomaticOptionalChainCompletions !== false; + + if (canCorrectToQuestionDot || isRightOfQuestionDot) { + type = type.getNonNullableType(); + if (canCorrectToQuestionDot) { + insertQuestionDot = true; + } + } } addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); } @@ -1141,11 +1147,17 @@ namespace ts.Completions { let type = typeChecker.getTypeAtLocation(node).getNonOptionalType(); let insertQuestionDot = false; if (type.isNullableType()) { - insertQuestionDot = isRightOfDot && !isRightOfQuestionDot; - type = type.getNonNullableType(); - } - if (insertQuestionDot && preferences.includeAutomaticOptionalChainCompletions === false) { - return; + const canCorrectToQuestionDot = + isRightOfDot && + !isRightOfQuestionDot && + preferences.includeAutomaticOptionalChainCompletions !== false; + + if (canCorrectToQuestionDot || isRightOfQuestionDot) { + type = type.getNonNullableType(); + if (canCorrectToQuestionDot) { + insertQuestionDot = true; + } + } } addTypeProperties(type, !!(node.flags & NodeFlags.AwaitContext), insertQuestionDot); } From fbc070f328a98e8296c710c3cd1bf6877cffbdf0 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Fri, 18 Oct 2019 16:12:26 -0700 Subject: [PATCH 15/47] Extend tsconfig.release.json from the sibling tsconfig.json to ensure files aren't forgotten. --- src/tsc/tsconfig.release.json | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/tsc/tsconfig.release.json b/src/tsc/tsconfig.release.json index 45a7577a34..2d2d28ac66 100644 --- a/src/tsc/tsconfig.release.json +++ b/src/tsc/tsconfig.release.json @@ -1,5 +1,5 @@ { - "extends": "../tsconfig-base", + "extends": "../tsconfig.json", "compilerOptions": { "outFile": "../../built/local/tsc.release.js", "stripInternal": true, @@ -10,9 +10,6 @@ "composite": false, "incremental": true }, - "files": [ - "tsc.ts" - ], "references": [ { "path": "../compiler/tsconfig.release.json", "prepend": true } ] From 91196fc53f71466ff0798971a9ceed19e735a1e9 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Fri, 18 Oct 2019 16:31:43 -0700 Subject: [PATCH 16/47] Ensure functions that have prototype properties assigned by Object.defineProperty get marked as classes (#34577) * Ensure functions that have prototype properties assigned by Object.defineProperty get marked as classes * Revert unneeded change --- src/compiler/binder.ts | 4 +++ ...inePropertyPrototypeNonConstructor.symbols | 23 ++++++++++++++++ ...efinePropertyPrototypeNonConstructor.types | 26 +++++++++++++++++++ ...ptDefinePropertyPrototypeNonConstructor.ts | 14 ++++++++++ 4 files changed, 67 insertions(+) create mode 100644 tests/baselines/reference/javascriptDefinePropertyPrototypeNonConstructor.symbols create mode 100644 tests/baselines/reference/javascriptDefinePropertyPrototypeNonConstructor.types create mode 100644 tests/cases/compiler/javascriptDefinePropertyPrototypeNonConstructor.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 8009bf45c8..47cf840c94 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2788,6 +2788,10 @@ namespace ts { function bindObjectDefinePrototypeProperty(node: BindableObjectDefinePropertyCall) { const namespaceSymbol = lookupSymbolForPropertyAccess((node.arguments[0] as PropertyAccessExpression).expression as EntityNameExpression); + if (namespaceSymbol) { + // Ensure the namespace symbol becomes class-like + addDeclarationToSymbol(namespaceSymbol, namespaceSymbol.valueDeclaration, SymbolFlags.Class); + } bindPotentiallyNewExpandoMemberToNamespace(node, namespaceSymbol, /*isPrototypeProperty*/ true); } diff --git a/tests/baselines/reference/javascriptDefinePropertyPrototypeNonConstructor.symbols b/tests/baselines/reference/javascriptDefinePropertyPrototypeNonConstructor.symbols new file mode 100644 index 0000000000..db4aaca770 --- /dev/null +++ b/tests/baselines/reference/javascriptDefinePropertyPrototypeNonConstructor.symbols @@ -0,0 +1,23 @@ +=== /a.js === +function Graphic() { +>Graphic : Symbol(Graphic, Decl(a.js, 0, 0)) +} + +Object.defineProperty(Graphic.prototype, "instance", { +>Object.defineProperty : Symbol(ObjectConstructor.defineProperty, Decl(lib.es5.d.ts, --, --)) +>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>defineProperty : Symbol(ObjectConstructor.defineProperty, Decl(lib.es5.d.ts, --, --)) +>Graphic.prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --)) +>Graphic : Symbol(Graphic, Decl(a.js, 0, 0)) +>prototype : Symbol(Function.prototype, Decl(lib.es5.d.ts, --, --)) +>"instance" : Symbol(Graphic.instance, Decl(a.js, 1, 1)) + + get: function() { +>get : Symbol(get, Decl(a.js, 3, 54)) + + return this; +>this : Symbol(Graphic, Decl(a.js, 0, 0)) + } +}); + + diff --git a/tests/baselines/reference/javascriptDefinePropertyPrototypeNonConstructor.types b/tests/baselines/reference/javascriptDefinePropertyPrototypeNonConstructor.types new file mode 100644 index 0000000000..9c873b65d0 --- /dev/null +++ b/tests/baselines/reference/javascriptDefinePropertyPrototypeNonConstructor.types @@ -0,0 +1,26 @@ +=== /a.js === +function Graphic() { +>Graphic : typeof Graphic +} + +Object.defineProperty(Graphic.prototype, "instance", { +>Object.defineProperty(Graphic.prototype, "instance", { get: function() { return this; }}) : any +>Object.defineProperty : (o: any, p: string | number | symbol, attributes: PropertyDescriptor & ThisType) => any +>Object : ObjectConstructor +>defineProperty : (o: any, p: string | number | symbol, attributes: PropertyDescriptor & ThisType) => any +>Graphic.prototype : any +>Graphic : typeof Graphic +>prototype : any +>"instance" : "instance" +>{ get: function() { return this; }} : { get: () => this; } + + get: function() { +>get : () => this +>function() { return this; } : () => this + + return this; +>this : this + } +}); + + diff --git a/tests/cases/compiler/javascriptDefinePropertyPrototypeNonConstructor.ts b/tests/cases/compiler/javascriptDefinePropertyPrototypeNonConstructor.ts new file mode 100644 index 0000000000..830ca4376b --- /dev/null +++ b/tests/cases/compiler/javascriptDefinePropertyPrototypeNonConstructor.ts @@ -0,0 +1,14 @@ +// @allowJs: true +// @checkJs: false +// @noEmit: true +// @Filename: /a.js + +function Graphic() { +} + +Object.defineProperty(Graphic.prototype, "instance", { + get: function() { + return this; + } +}); + From 98fe34225c28803b40ea67a2cb9ef7b17e64c88b Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 18 Oct 2019 16:33:55 -0700 Subject: [PATCH 17/47] Handle more cases --- src/compiler/checker.ts | 60 +++++++++++++++++------------------------ 1 file changed, 25 insertions(+), 35 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 33c7962b64..2c670397fe 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18787,6 +18787,14 @@ namespace ts { signature.declaration && (getReturnTypeFromAnnotation(signature.declaration) || unknownType).flags & TypeFlags.Never); } + function getTypePredicateArgument(predicate: TypePredicate, callExpression: CallExpression) { + if (predicate.kind === TypePredicateKind.Identifier || predicate.kind === TypePredicateKind.AssertsIdentifier) { + return callExpression.arguments[predicate.parameterIndex]; + } + const invokedExpression = skipParentheses(callExpression.expression); + return isAccessExpression(invokedExpression) ? skipParentheses(invokedExpression.expression) : undefined; + } + function reportFlowControlError(node: Node) { const block = findAncestor(node, isFunctionOrModuleBlock); const sourceFile = getSourceFileOfNode(node); @@ -19338,6 +19346,9 @@ namespace ts { if (isMatchingReference(reference, expr)) { return getTypeWithFacts(type, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy); } + if (strictNullChecks && assumeTrue && optionalChainContainsReference(expr, reference)) { + type = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); + } if (isMatchingReferenceDiscriminant(expr, declaredType)) { return narrowTypeByDiscriminant(type, expr, t => getTypeWithFacts(t, assumeTrue ? TypeFacts.Truthy : TypeFacts.Falsy)); } @@ -19422,21 +19433,13 @@ namespace ts { } function narrowTypeByOptionalChainContainment(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type { - const op = assumeTrue ? operator : - operator === SyntaxKind.EqualsEqualsToken ? SyntaxKind.ExclamationEqualsToken : - operator === SyntaxKind.EqualsEqualsEqualsToken ? SyntaxKind.ExclamationEqualsEqualsToken : - operator === SyntaxKind.ExclamationEqualsToken ? SyntaxKind.EqualsEqualsToken : - operator === SyntaxKind.ExclamationEqualsEqualsToken ? SyntaxKind.EqualsEqualsEqualsToken : - operator; // We are in a branch of obj?.foo === value or obj?.foo !== value. We remove undefined and null from // the type of obj if (a) the operator is === and the type of value doesn't include undefined or (b) the // operator is !== and the type of value is undefined. - const valueType = getTypeOfExpression(value); - return op === SyntaxKind.EqualsEqualsToken && !(getTypeFacts(valueType) & TypeFacts.EQUndefinedOrNull) || - op === SyntaxKind.EqualsEqualsEqualsToken && !(getTypeFacts(valueType) & TypeFacts.EQUndefined) || - op === SyntaxKind.ExclamationEqualsToken && valueType.flags & TypeFlags.Nullable || - op === SyntaxKind.ExclamationEqualsEqualsToken && valueType.flags & TypeFlags.Undefined ? - getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; + const effectiveTrue = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken ? assumeTrue : !assumeTrue; + const doubleEquals = operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsToken; + const valueNonNullish = !(getTypeFacts(getTypeOfExpression(value)) & (doubleEquals ? TypeFacts.EQUndefinedOrNull : TypeFacts.EQUndefined)); + return effectiveTrue === valueNonNullish ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; } function narrowTypeByEquality(type: Type, operator: SyntaxKind, value: Expression, assumeTrue: boolean): Type { @@ -19487,10 +19490,12 @@ namespace ts { function narrowTypeByTypeof(type: Type, typeOfExpr: TypeOfExpression, operator: SyntaxKind, literal: LiteralExpression, assumeTrue: boolean): Type { // We have '==', '!=', '===', or !==' operator with 'typeof xxx' and string literal operands + if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { + assumeTrue = !assumeTrue; + } const target = getReferenceCandidate(typeOfExpr.expression); if (!isMatchingReference(reference, target)) { - if (assumeTrue && (operator === SyntaxKind.EqualsEqualsToken || operator === SyntaxKind.EqualsEqualsEqualsToken) && - strictNullChecks && optionalChainContainsReference(target, reference)) { + if (strictNullChecks && optionalChainContainsReference(target, reference) && assumeTrue === (literal.text !== "undefined")) { return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); } // For a reference of the form 'x.y', a 'typeof x === ...' type guard resets the @@ -19500,9 +19505,6 @@ namespace ts { } return type; } - if (operator === SyntaxKind.ExclamationEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) { - assumeTrue = !assumeTrue; - } if (type.flags & TypeFlags.Any && literal.text === "function") { return type; } @@ -19763,32 +19765,20 @@ namespace ts { function narrowTypeByTypePredicate(type: Type, predicate: TypePredicate, callExpression: CallExpression, assumeTrue: boolean): Type { // Don't narrow from 'any' if the predicate type is exactly 'Object' or 'Function' - if (isTypeAny(type) && (predicate.type === globalObjectType || predicate.type === globalFunctionType)) { - return type; - } - if (predicate.kind === TypePredicateKind.Identifier || predicate.kind === TypePredicateKind.AssertsIdentifier) { - const predicateArgument = callExpression.arguments[predicate.parameterIndex]; - if (predicateArgument && predicate.type) { + if (predicate.type && !(isTypeAny(type) && (predicate.type === globalObjectType || predicate.type === globalFunctionType))) { + const predicateArgument = getTypePredicateArgument(predicate, callExpression); + if (predicateArgument) { if (isMatchingReference(reference, predicateArgument)) { return getNarrowedType(type, predicate.type, assumeTrue, isTypeSubtypeOf); } + if (strictNullChecks && assumeTrue && !(getTypeFacts(predicate.type) & TypeFacts.EQUndefined)) { + type = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); + } if (containsMatchingReference(reference, predicateArgument)) { return declaredType; } } } - else { - const invokedExpression = skipParentheses(callExpression.expression); - if (isAccessExpression(invokedExpression) && predicate.type) { - const possibleReference = skipParentheses(invokedExpression.expression); - if (isMatchingReference(reference, possibleReference)) { - return getNarrowedType(type, predicate.type, assumeTrue, isTypeSubtypeOf); - } - if (containsMatchingReference(reference, possibleReference)) { - return declaredType; - } - } - } return type; } From 556da72ffdd32f543f51a3789ac6ae98f59893f8 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Fri, 18 Oct 2019 17:27:47 -0700 Subject: [PATCH 18/47] [WIP] Improve optional chaining checker performance (#33794) * Improve optional chaining checker performance * Improve optional chaining checker performance * Add flags to Signature * Inline getOptionalExpression * split checks for optional chains * Cache optional call signatures --- src/compiler/binder.ts | 2 +- src/compiler/checker.ts | 300 +++++++++++++---------- src/compiler/types.ts | 26 +- src/compiler/utilities.ts | 17 +- src/services/codefixes/helpers.ts | 6 +- src/services/codefixes/inferFromUsage.ts | 2 +- src/services/services.ts | 7 +- src/services/stringCompletions.ts | 2 +- 8 files changed, 210 insertions(+), 152 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 47cf840c94..9646aabe0a 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -953,7 +953,7 @@ namespace ts { } if (expression.kind === SyntaxKind.TrueKeyword && flags & FlowFlags.FalseCondition || expression.kind === SyntaxKind.FalseKeyword && flags & FlowFlags.TrueCondition) { - if (!isOptionalChainRoot(expression.parent)) { + if (!isExpressionOfOptionalChainRoot(expression)) { return unreachableFlow; } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 45cabbb3f9..b83fa543a8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -327,10 +327,6 @@ namespace ts { /** This will be set during calls to `getResolvedSignature` where services determines an apparent number of arguments greater than what is actually provided. */ let apparentArgumentCount: number | undefined; - // This object is reused for `checkOptionalExpression` return values to avoid frequent GC due to nursery object allocations. - // This object represents a pool-size of 1. - const pooledOptionalTypeResult: { isOptional: boolean, type: Type } = { isOptional: false, type: undefined! }; - // for public members that accept a Node or one of its subtypes, we must guard against // synthetic nodes created during transformations by calling `getParseTreeNode`. // for most of these, we perform the guard only on `checker` to avoid any possible @@ -712,10 +708,10 @@ namespace ts { const noTypePredicate = createTypePredicate(TypePredicateKind.Identifier, "<>", 0, anyType); - const anySignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); - const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, errorType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); - const resolvingSignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); - const silentNeverSignature = createSignature(undefined, undefined, undefined, emptyArray, silentNeverType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); + const anySignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + const unknownSignature = createSignature(undefined, undefined, undefined, emptyArray, errorType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + const resolvingSignature = createSignature(undefined, undefined, undefined, emptyArray, anyType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); + const silentNeverSignature = createSignature(undefined, undefined, undefined, emptyArray, silentNeverType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); const enumNumberIndexInfo = createIndexInfo(stringType, /*isReadonly*/ true); @@ -7721,7 +7717,7 @@ namespace ts { const signatures = getSignaturesOfType(type, SignatureKind.Construct); if (signatures.length === 1) { const s = signatures[0]; - return !s.typeParameters && s.parameters.length === 1 && s.hasRestParameter && getElementTypeOfArrayType(getTypeOfParameter(s.parameters[0])) === anyType; + return !s.typeParameters && s.parameters.length === 1 && signatureHasRestParameter(s) && getElementTypeOfArrayType(getTypeOfParameter(s.parameters[0])) === anyType; } return false; } @@ -8612,10 +8608,9 @@ namespace ts { resolvedReturnType: Type | undefined, resolvedTypePredicate: TypePredicate | undefined, minArgumentCount: number, - hasRestParameter: boolean, - hasLiteralTypes: boolean, + flags: SignatureFlags ): Signature { - const sig = new Signature(checker); + const sig = new Signature(checker, flags); sig.declaration = declaration; sig.typeParameters = typeParameters; sig.parameters = parameters; @@ -8623,8 +8618,6 @@ namespace ts { sig.resolvedReturnType = resolvedReturnType; sig.resolvedTypePredicate = resolvedTypePredicate; sig.minArgumentCount = minArgumentCount; - sig.hasRestParameter = hasRestParameter; - sig.hasLiteralTypes = hasLiteralTypes; sig.target = undefined; sig.mapper = undefined; return sig; @@ -8632,7 +8625,7 @@ namespace ts { function cloneSignature(sig: Signature): Signature { const result = createSignature(sig.declaration, sig.typeParameters, sig.thisParameter, sig.parameters, /*resolvedReturnType*/ undefined, - /*resolvedTypePredicate*/ undefined, sig.minArgumentCount, sig.hasRestParameter, sig.hasLiteralTypes); + /*resolvedTypePredicate*/ undefined, sig.minArgumentCount, sig.flags & SignatureFlags.PropagatingFlags); result.target = sig.target; result.mapper = sig.mapper; return result; @@ -8646,14 +8639,19 @@ namespace ts { return result; } + function getOptionalCallSignature(signature: Signature) { + return signatureIsOptionalCall(signature) ? signature : + (signature.optionalCallSignatureCache || (signature.optionalCallSignatureCache = createOptionalCallSignature(signature))); + } + function createOptionalCallSignature(signature: Signature) { const result = cloneSignature(signature); - result.isOptionalCall = true; + result.flags |= SignatureFlags.IsOptionalCall; return result; } function getExpandedParameters(sig: Signature): readonly Symbol[] { - if (sig.hasRestParameter) { + if (signatureHasRestParameter(sig)) { const restIndex = sig.parameters.length - 1; const restParameter = sig.parameters[restIndex]; const restType = getTypeOfSymbol(restParameter); @@ -8679,7 +8677,7 @@ namespace ts { const baseConstructorType = getBaseConstructorTypeOfClass(classType); const baseSignatures = getSignaturesOfType(baseConstructorType, SignatureKind.Construct); if (baseSignatures.length === 0) { - return [createSignature(undefined, classType.localTypeParameters, undefined, emptyArray, classType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false)]; + return [createSignature(undefined, classType.localTypeParameters, undefined, emptyArray, classType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None)]; } const baseTypeNode = getBaseTypeNodeOfClass(classType)!; const isJavaScript = isInJSFile(baseTypeNode); @@ -8843,8 +8841,6 @@ namespace ts { const params = combineUnionParameters(left, right); const thisParam = combineUnionThisParam(left.thisParameter, right.thisParameter); const minArgCount = Math.max(left.minArgumentCount, right.minArgumentCount); - const hasRestParam = left.hasRestParameter || right.hasRestParameter; - const hasLiteralTypes = left.hasLiteralTypes || right.hasLiteralTypes; const result = createSignature( declaration, left.typeParameters || right.typeParameters, @@ -8853,8 +8849,7 @@ namespace ts { /*resolvedReturnType*/ undefined, /*resolvedTypePredicate*/ undefined, minArgCount, - hasRestParam, - hasLiteralTypes + (left.flags | right.flags) & SignatureFlags.PropagatingFlags ); result.unionSignatures = concatenate(left.unionSignatures || [left], [right]); return result; @@ -9036,7 +9031,7 @@ namespace ts { constructSignatures = addRange(constructSignatures.slice(), mapDefined( type.callSignatures, sig => isJSConstructor(sig.declaration) ? - createSignature(sig.declaration, sig.typeParameters, sig.thisParameter, sig.parameters, classType, /*resolvedTypePredicate*/ undefined, sig.minArgumentCount, sig.hasRestParameter, sig.hasLiteralTypes) : + createSignature(sig.declaration, sig.typeParameters, sig.thisParameter, sig.parameters, classType, /*resolvedTypePredicate*/ undefined, sig.minArgumentCount, sig.flags & SignatureFlags.PropagatingFlags) : undefined)); } if (!constructSignatures.length) { @@ -10043,7 +10038,7 @@ namespace ts { const links = getNodeLinks(declaration); if (!links.resolvedSignature) { const parameters: Symbol[] = []; - let hasLiteralTypes = false; + let flags = SignatureFlags.None; let minArgumentCount = 0; let thisParameter: Symbol | undefined; let hasThisParameter = false; @@ -10077,7 +10072,7 @@ namespace ts { } if (type && type.kind === SyntaxKind.LiteralType) { - hasLiteralTypes = true; + flags |= SignatureFlags.HasLiteralTypes; } // Record a new minimum argument count if this is not an optional parameter @@ -10106,10 +10101,12 @@ namespace ts { getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent).symbol)) : undefined; const typeParameters = classType ? classType.localTypeParameters : getTypeParametersFromDeclaration(declaration); - const hasRestLikeParameter = hasRestParameter(declaration) || isInJSFile(declaration) && maybeAddJsSyntheticRestParameter(declaration, parameters); + if (hasRestParameter(declaration) || isInJSFile(declaration) && maybeAddJsSyntheticRestParameter(declaration, parameters)) { + flags |= SignatureFlags.HasRestParameter; + } links.resolvedSignature = createSignature(declaration, typeParameters, thisParameter, parameters, /*resolvedReturnType*/ undefined, /*resolvedTypePredicate*/ undefined, - minArgumentCount, hasRestLikeParameter, hasLiteralTypes); + minArgumentCount, flags); } return links.resolvedSignature; } @@ -10264,8 +10261,8 @@ namespace ts { signature.unionSignatures ? getUnionType(map(signature.unionSignatures, getReturnTypeOfSignature), UnionReduction.Subtype) : getReturnTypeFromAnnotation(signature.declaration!) || (nodeIsMissing((signature.declaration).body) ? anyType : getReturnTypeFromBody(signature.declaration)); - if (signature.isOptionalCall) { - type = propagateOptionalTypeMarker(type, /*wasOptional*/ true); + if (signatureIsOptionalCall(signature)) { + type = addOptionalTypeMarker(type); } if (!popTypeResolution()) { if (signature.declaration) { @@ -10325,7 +10322,7 @@ namespace ts { } function tryGetRestTypeOfSignature(signature: Signature): Type | undefined { - if (signature.hasRestParameter) { + if (signatureHasRestParameter(signature)) { const sigRestType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); const restType = isTupleType(sigRestType) ? getRestTypeOfTupleType(sigRestType) : sigRestType; return restType && getIndexTypeOfType(restType, IndexKind.Number); @@ -12907,8 +12904,7 @@ namespace ts { /*resolvedReturnType*/ undefined, /*resolvedTypePredicate*/ undefined, signature.minArgumentCount, - signature.hasRestParameter, - signature.hasLiteralTypes); + signature.flags & SignatureFlags.PropagatingFlags); result.target = signature; result.mapper = mapper; return result; @@ -13861,7 +13857,7 @@ namespace ts { */ function isAnySignature(s: Signature) { return !s.typeParameters && (!s.thisParameter || isTypeAny(getTypeOfParameter(s.thisParameter))) && s.parameters.length === 1 && - s.hasRestParameter && (getTypeOfParameter(s.parameters[0]) === anyArrayType || isTypeAny(getTypeOfParameter(s.parameters[0]))) && + signatureHasRestParameter(s) && (getTypeOfParameter(s.parameters[0]) === anyArrayType || isTypeAny(getTypeOfParameter(s.parameters[0]))) && isTypeAny(getReturnTypeOfSignature(s)); } @@ -16686,48 +16682,22 @@ namespace ts { return strictNullChecks ? getUnionType([type, optionalType]) : type; } + function isNotOptionalTypeMarker(type: Type) { + return type !== optionalType; + } + function removeOptionalTypeMarker(type: Type): Type { - return strictNullChecks ? filterType(type, t => t !== optionalType) : type; + return strictNullChecks ? filterType(type, isNotOptionalTypeMarker) : type; } function propagateOptionalTypeMarker(type: Type, wasOptional: boolean) { return wasOptional ? addOptionalTypeMarker(type) : type; } - function createPooledOptionalTypeResult(isOptional: boolean, type: Type) { - pooledOptionalTypeResult.isOptional = isOptional; - pooledOptionalTypeResult.type = type; - return pooledOptionalTypeResult; - } - - function checkOptionalExpression( - parent: PropertyAccessExpression | QualifiedName | ElementAccessExpression | CallExpression, - expression: Expression | QualifiedName, - nullDiagnostic?: DiagnosticMessage, - undefinedDiagnostic?: DiagnosticMessage, - nullOrUndefinedDiagnostic?: DiagnosticMessage, - ) { - let isOptional = false; - let type = checkExpression(expression); - if (isOptionalChain(parent)) { - if (parent.questionDotToken) { - // If we have a questionDotToken then we are an OptionalExpression and should remove `null` and - // `undefined` from the type and add the optionalType to the result, if needed. - isOptional = isNullableType(type); - return createPooledOptionalTypeResult(isOptional, isOptional ? getNonNullableType(type) : type); - } - - // If we do not have a questionDotToken, then we are an OptionalChain and we remove the optionalType and - // indicate whether we need to add optionalType back into the result. - const nonOptionalType = removeOptionalTypeMarker(type); - if (nonOptionalType !== type) { - isOptional = true; - type = nonOptionalType; - } - } - - type = checkNonNullType(type, expression, nullDiagnostic, undefinedDiagnostic, nullOrUndefinedDiagnostic); - return createPooledOptionalTypeResult(isOptional, type); + function getOptionalExpressionType(exprType: Type, expression: Expression) { + return isExpressionOfOptionalChainRoot(expression) ? getNonNullableType(exprType) : + isOptionalChain(expression) ? removeOptionalTypeMarker(exprType) : + exprType; } /** @@ -18771,9 +18741,21 @@ namespace ts { // expressions are potential type predicate function calls. In order to avoid triggering // circularities in control flow analysis, we use getTypeOfDottedName when resolving the call // target expression of an assertion. - const funcType = node.parent.kind === SyntaxKind.ExpressionStatement ? getTypeOfDottedName(node.expression, /*diagnostic*/ undefined) : - node.expression.kind !== SyntaxKind.SuperKeyword ? checkOptionalExpression(node, node.expression).type : - undefined; + let funcType: Type | undefined; + if (node.parent.kind === SyntaxKind.ExpressionStatement) { + funcType = getTypeOfDottedName(node.expression, /*diagnostic*/ undefined); + } + else if (node.expression.kind !== SyntaxKind.SuperKeyword) { + if (isOptionalChain(node)) { + funcType = checkNonNullType( + getOptionalExpressionType(checkExpression(node.expression), node.expression), + node.expression + ); + } + else { + funcType = checkNonNullExpression(node.expression); + } + } const signatures = getSignaturesOfType(funcType && getApparentType(funcType) || unknownType, SignatureKind.Call); const candidate = signatures.length === 1 && !signatures[0].typeParameters ? signatures[0] : some(signatures, hasTypePredicateOrNeverReturnType) ? getResolvedSignature(node) : @@ -19780,7 +19762,7 @@ namespace ts { // will be a subtype or the same type as the argument. function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type { // for `a?.b`, we emulate a synthetic `a !== null && a !== undefined` condition for `a` - if (isOptionalChainRoot(expr.parent) || + if (isExpressionOfOptionalChainRoot(expr) || isBinaryExpression(expr.parent) && expr.parent.operatorToken.kind === SyntaxKind.QuestionQuestionToken && expr.parent.left === expr) { return narrowTypeByOptionality(type, expr, assumeTrue); } @@ -22682,19 +22664,8 @@ namespace ts { return !!forEachProperty(symbol, prop => !(prop.flags & SymbolFlags.Method)); } - function checkNonNullExpression( - node: Expression | QualifiedName, - nullDiagnostic?: DiagnosticMessage, - undefinedDiagnostic?: DiagnosticMessage, - nullOrUndefinedDiagnostic?: DiagnosticMessage, - ) { - return checkNonNullType( - checkExpression(node), - node, - nullDiagnostic, - undefinedDiagnostic, - nullOrUndefinedDiagnostic - ); + function checkNonNullExpression(node: Expression | QualifiedName) { + return checkNonNullType(checkExpression(node), node); } function isNullableType(type: Type) { @@ -22705,12 +22676,26 @@ namespace ts { return isNullableType(type) ? getNonNullableType(type) : type; } - function checkNonNullType( + function reportObjectPossiblyNullOrUndefinedError(node: Node, flags: TypeFlags) { + error(node, flags & TypeFlags.Undefined ? flags & TypeFlags.Null ? + Diagnostics.Object_is_possibly_null_or_undefined : + Diagnostics.Object_is_possibly_undefined : + Diagnostics.Object_is_possibly_null + ); + } + + function reportCannotInvokePossiblyNullOrUndefinedError(node: Node, flags: TypeFlags) { + error(node, flags & TypeFlags.Undefined ? flags & TypeFlags.Null ? + Diagnostics.Cannot_invoke_an_object_which_is_possibly_null_or_undefined : + Diagnostics.Cannot_invoke_an_object_which_is_possibly_undefined : + Diagnostics.Cannot_invoke_an_object_which_is_possibly_null + ); + } + + function checkNonNullTypeWithReporter( type: Type, node: Node, - nullDiagnostic?: DiagnosticMessage, - undefinedDiagnostic?: DiagnosticMessage, - nullOrUndefinedDiagnostic?: DiagnosticMessage + reportError: (node: Node, kind: TypeFlags) => void ): Type { if (strictNullChecks && type.flags & TypeFlags.Unknown) { error(node, Diagnostics.Object_is_of_type_unknown); @@ -22718,17 +22703,17 @@ namespace ts { } const kind = (strictNullChecks ? getFalsyFlags(type) : type.flags) & TypeFlags.Nullable; if (kind) { - error(node, kind & TypeFlags.Undefined ? kind & TypeFlags.Null ? - (nullOrUndefinedDiagnostic || Diagnostics.Object_is_possibly_null_or_undefined) : - (undefinedDiagnostic || Diagnostics.Object_is_possibly_undefined) : - (nullDiagnostic || Diagnostics.Object_is_possibly_null) - ); + reportError(node, kind); const t = getNonNullableType(type); return t.flags & (TypeFlags.Nullable | TypeFlags.Never) ? errorType : t; } return type; } + function checkNonNullType(type: Type, node: Node) { + return checkNonNullTypeWithReporter(type, node, reportObjectPossiblyNullOrUndefinedError); + } + function checkNonNullNonVoidType(type: Type, node: Node): Type { const nonNullType = checkNonNullType(type, node); if (nonNullType !== errorType && nonNullType.flags & TypeFlags.Void) { @@ -22738,11 +22723,18 @@ namespace ts { } function checkPropertyAccessExpression(node: PropertyAccessExpression) { - return checkPropertyAccessExpressionOrQualifiedName(node, node.expression, node.name); + return node.flags & NodeFlags.OptionalChain ? checkPropertyAccessChain(node as PropertyAccessChain) : + checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullExpression(node.expression), node.name); + } + + function checkPropertyAccessChain(node: PropertyAccessChain) { + const leftType = checkExpression(node.expression); + const nonOptionalType = getOptionalExpressionType(leftType, node.expression); + return propagateOptionalTypeMarker(checkPropertyAccessExpressionOrQualifiedName(node, node.expression, checkNonNullType(nonOptionalType, node.expression), node.name), nonOptionalType !== leftType); } function checkQualifiedName(node: QualifiedName) { - return checkPropertyAccessExpressionOrQualifiedName(node, node.left, node.right); + return checkPropertyAccessExpressionOrQualifiedName(node, node.left, checkNonNullExpression(node.left), node.right); } function isMethodAccessForCall(node: Node) { @@ -22752,8 +22744,7 @@ namespace ts { return isCallOrNewExpression(node.parent) && node.parent.expression === node; } - function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier) { - const { isOptional, type: leftType } = checkOptionalExpression(node, left); + function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, leftType: Type, right: Identifier) { const parentSymbol = getNodeLinks(left).resolvedSymbol; const assignmentKind = getAssignmentTargetKind(node); const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(leftType) : leftType); @@ -22807,7 +22798,7 @@ namespace ts { } propType = getConstraintForLocation(getTypeOfSymbol(prop), node); } - return propagateOptionalTypeMarker(getFlowTypeOfAccessExpression(node, prop, propType, right), isOptional); + return getFlowTypeOfAccessExpression(node, prop, propType, right); } function getFlowTypeOfAccessExpression(node: ElementAccessExpression | PropertyAccessExpression | QualifiedName, prop: Symbol | undefined, propType: Type, errorNode: Node) { @@ -23164,7 +23155,17 @@ namespace ts { } function checkIndexedAccess(node: ElementAccessExpression): Type { - const { isOptional, type: exprType } = checkOptionalExpression(node, node.expression); + return node.flags & NodeFlags.OptionalChain ? checkElementAccessChain(node as ElementAccessChain) : + checkElementAccessExpression(node, checkNonNullExpression(node.expression)); + } + + function checkElementAccessChain(node: ElementAccessChain) { + const exprType = checkExpression(node.expression); + const nonOptionalType = getOptionalExpressionType(exprType, node.expression); + return propagateOptionalTypeMarker(checkElementAccessExpression(node, checkNonNullType(nonOptionalType, node.expression)), nonOptionalType !== exprType); + } + + function checkElementAccessExpression(node: ElementAccessExpression, exprType: Type): Type { const objectType = getAssignmentTargetKind(node) !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(exprType) : exprType; const indexExpression = node.argumentExpression; const indexType = checkExpression(indexExpression); @@ -23183,7 +23184,7 @@ namespace ts { AccessFlags.Writing | (isGenericObjectType(objectType) && !isThisTypeParameter(objectType) ? AccessFlags.NoIndexSignatures : 0) : AccessFlags.None; const indexedAccessType = getIndexedAccessTypeOrUndefined(objectType, effectiveIndexType, node, accessFlags) || errorType; - return propagateOptionalTypeMarker(checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, indexedAccessType.symbol, indexedAccessType, indexExpression), node), isOptional); + return checkIndexedAccessIndexType(getFlowTypeOfAccessExpression(node, indexedAccessType.symbol, indexedAccessType, indexExpression), node); } function checkThatExpressionIsProperSymbolReference(expression: Expression, expressionType: Type, reportError: boolean): boolean { @@ -23296,7 +23297,7 @@ namespace ts { // specialized signatures always need to be placed before non-specialized signatures regardless // of the cutoff position; see GH#1133 - if (signature.hasLiteralTypes) { + if (signatureHasLiteralTypes(signature)) { specializedIndex++; spliceIndex = specializedIndex; // The cutoff index always needs to be greater than or equal to the specialized signature index @@ -23308,7 +23309,7 @@ namespace ts { spliceIndex = index; } - result.splice(spliceIndex, 0, isOptionalCall ? createOptionalCallSignature(signature) : signature); + result.splice(spliceIndex, 0, isOptionalCall ? getOptionalCallSignature(signature) : signature); } } @@ -24266,17 +24267,21 @@ namespace ts { const { min: minArgumentCount, max: maxNonRestParam } = minAndMax(candidates, getNumNonRestParameters); const parameters: Symbol[] = []; for (let i = 0; i < maxNonRestParam; i++) { - const symbols = mapDefined(candidates, ({ parameters, hasRestParameter }) => hasRestParameter ? - i < parameters.length - 1 ? parameters[i] : last(parameters) : - i < parameters.length ? parameters[i] : undefined); + const symbols = mapDefined(candidates, s => signatureHasRestParameter(s) ? + i < s.parameters.length - 1 ? s.parameters[i] : last(s.parameters) : + i < s.parameters.length ? s.parameters[i] : undefined); Debug.assert(symbols.length !== 0); parameters.push(createCombinedSymbolFromTypes(symbols, mapDefined(candidates, candidate => tryGetTypeAtPosition(candidate, i)))); } - const restParameterSymbols = mapDefined(candidates, c => c.hasRestParameter ? last(c.parameters) : undefined); - const hasRestParameter = restParameterSymbols.length !== 0; - if (hasRestParameter) { + const restParameterSymbols = mapDefined(candidates, c => signatureHasRestParameter(c) ? last(c.parameters) : undefined); + let flags = SignatureFlags.None; + if (restParameterSymbols.length !== 0) { const type = createArrayType(getUnionType(mapDefined(candidates, tryGetRestTypeOfSignature), UnionReduction.Subtype)); parameters.push(createCombinedSymbolForOverloadFailure(restParameterSymbols, type)); + flags |= SignatureFlags.HasRestParameter; + } + if (candidates.some(signatureHasLiteralTypes)) { + flags |= SignatureFlags.HasLiteralTypes; } return createSignature( candidates[0].declaration, @@ -24286,13 +24291,12 @@ namespace ts { /*resolvedReturnType*/ getIntersectionType(candidates.map(getReturnTypeOfSignature)), /*typePredicate*/ undefined, minArgumentCount, - hasRestParameter, - /*hasLiteralTypes*/ candidates.some(c => c.hasLiteralTypes)); + flags); } function getNumNonRestParameters(signature: Signature): number { const numParams = signature.parameters.length; - return signature.hasRestParameter ? numParams - 1 : numParams; + return signatureHasRestParameter(signature) ? numParams - 1 : numParams; } function createCombinedSymbolFromTypes(sources: readonly Symbol[], types: Type[]): Symbol { @@ -24383,12 +24387,20 @@ namespace ts { return resolveUntypedCall(node); } - const { isOptional, type: funcType } = checkOptionalExpression( - node, + let isOptional: boolean; + let funcType = checkExpression(node.expression); + if (isCallChain(node)) { + const nonOptionalType = getOptionalExpressionType(funcType, node.expression); + isOptional = nonOptionalType !== funcType; + funcType = nonOptionalType; + } + else { + isOptional = false; + } + funcType = checkNonNullTypeWithReporter( + funcType, node.expression, - Diagnostics.Cannot_invoke_an_object_which_is_possibly_null, - Diagnostics.Cannot_invoke_an_object_which_is_possibly_undefined, - Diagnostics.Cannot_invoke_an_object_which_is_possibly_null_or_undefined + reportCannotInvokePossiblyNullOrUndefinedError ); if (funcType === silentNeverType) { @@ -24842,8 +24854,7 @@ namespace ts { typeSymbol ? getDeclaredTypeOfSymbol(typeSymbol) : errorType, /*returnTypePredicate*/ undefined, 1, - /*hasRestparameter*/ false, - /*hasLiteralTypes*/ false + SignatureFlags.None ); } @@ -24882,7 +24893,7 @@ namespace ts { function isPotentiallyUncalledDecorator(decorator: Decorator, signatures: readonly Signature[]) { return signatures.length && every(signatures, signature => signature.minArgumentCount === 0 && - !signature.hasRestParameter && + !signatureHasRestParameter(signature) && signature.parameters.length < getDecoratorArgumentCount(decorator, signature)); } @@ -25298,7 +25309,7 @@ namespace ts { } function getParameterNameAtPosition(signature: Signature, pos: number) { - const paramCount = signature.parameters.length - (signature.hasRestParameter ? 1 : 0); + const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); if (pos < paramCount) { return signature.parameters[pos].escapedName; } @@ -25317,11 +25328,11 @@ namespace ts { } function tryGetTypeAtPosition(signature: Signature, pos: number): Type | undefined { - const paramCount = signature.parameters.length - (signature.hasRestParameter ? 1 : 0); + const paramCount = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); if (pos < paramCount) { return getTypeOfParameter(signature.parameters[pos]); } - if (signature.hasRestParameter) { + if (signatureHasRestParameter(signature)) { // We want to return the value undefined for an out of bounds parameter position, // so we need to check bounds here before calling getIndexedAccessType (which // otherwise would return the type 'undefined'). @@ -25358,7 +25369,7 @@ namespace ts { function getParameterCount(signature: Signature) { const length = signature.parameters.length; - if (signature.hasRestParameter) { + if (signatureHasRestParameter(signature)) { const restType = getTypeOfSymbol(signature.parameters[length - 1]); if (isTupleType(restType)) { return length + getTypeArguments(restType).length - 1; @@ -25368,7 +25379,7 @@ namespace ts { } function getMinArgumentCount(signature: Signature) { - if (signature.hasRestParameter) { + if (signatureHasRestParameter(signature)) { const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); if (isTupleType(restType)) { const minLength = restType.target.minLength; @@ -25381,7 +25392,7 @@ namespace ts { } function hasEffectiveRestParameter(signature: Signature) { - if (signature.hasRestParameter) { + if (signatureHasRestParameter(signature)) { const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); return !isTupleType(restType) || restType.target.hasRestElement; } @@ -25389,7 +25400,7 @@ namespace ts { } function getEffectiveRestType(signature: Signature) { - if (signature.hasRestParameter) { + if (signatureHasRestParameter(signature)) { const restType = getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]); return isTupleType(restType) ? getRestArrayTypeOfTupleType(restType) : restType; } @@ -25410,7 +25421,7 @@ namespace ts { } function inferFromAnnotatedParameters(signature: Signature, context: Signature, inferenceContext: InferenceContext) { - const len = signature.parameters.length - (signature.hasRestParameter ? 1 : 0); + const len = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); for (let i = 0; i < len; i++) { const declaration = signature.parameters[i].valueDeclaration; if (declaration.type) { @@ -25444,7 +25455,7 @@ namespace ts { assignTypeToParameterAndFixTypeParameters(signature.thisParameter!, getTypeOfSymbol(context.thisParameter)); } } - const len = signature.parameters.length - (signature.hasRestParameter ? 1 : 0); + const len = signature.parameters.length - (signatureHasRestParameter(signature) ? 1 : 0); for (let i = 0; i < len; i++) { const parameter = signature.parameters[i]; if (!getEffectiveTypeAnnotationNode(parameter.valueDeclaration)) { @@ -25452,7 +25463,7 @@ namespace ts { assignTypeToParameterAndFixTypeParameters(parameter, contextualParameterType); } } - if (signature.hasRestParameter) { + if (signatureHasRestParameter(signature)) { // parameter might be a transient symbol generated by use of `arguments` in the function body. const parameter = last(signature.parameters); if (isTransientSymbol(parameter) || !getEffectiveTypeAnnotationNode(parameter.valueDeclaration)) { @@ -25890,7 +25901,7 @@ namespace ts { return links.contextFreeType; } const returnType = getReturnTypeFromBody(node, checkMode); - const returnOnlySignature = createSignature(undefined, undefined, undefined, emptyArray, returnType, /*resolvedTypePredicate*/ undefined, 0, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); + const returnOnlySignature = createSignature(undefined, undefined, undefined, emptyArray, returnType, /*resolvedTypePredicate*/ undefined, 0, SignatureFlags.None); const returnOnlyType = createAnonymousType(node.symbol, emptySymbols, [returnOnlySignature], emptyArray, undefined, undefined); returnOnlyType.objectFlags |= ObjectFlags.NonInferrableType; return links.contextFreeType = returnOnlyType; @@ -27355,7 +27366,18 @@ namespace ts { // 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, /*checkArgumentIsStringLiteralLike*/ true) && !isSymbolOrSymbolForCall(expr)) { - const { isOptional, type: funcType } = checkOptionalExpression(expr, expr.expression); + let isOptional: boolean; + let funcType: Type; + if (isCallChain(expr)) { + funcType = checkExpression(expr.expression); + const nonOptionalType = getOptionalExpressionType(funcType, expr.expression); + isOptional = funcType !== nonOptionalType; + funcType = checkNonNullType(nonOptionalType, expr.expression); + } + else { + isOptional = false; + funcType = checkNonNullExpression(expr.expression); + } const signature = getSingleCallSignature(funcType); if (signature && !signature.typeParameters) { return propagateOptionalTypeMarker(getReturnTypeOfSignature(signature), isOptional); @@ -27630,7 +27652,7 @@ namespace ts { } else { if (typePredicate.parameterIndex >= 0) { - if (signature.hasRestParameter && typePredicate.parameterIndex === signature.parameters.length - 1) { + if (signatureHasRestParameter(signature) && typePredicate.parameterIndex === signature.parameters.length - 1) { error(parameterName, Diagnostics.A_type_predicate_cannot_reference_a_rest_parameter); } else { @@ -36062,4 +36084,16 @@ namespace ts { case IterationTypeKind.Next: return "nextType"; } } + + export function signatureHasRestParameter(s: Signature) { + return !!(s.flags & SignatureFlags.HasRestParameter); + } + + export function signatureHasLiteralTypes(s: Signature) { + return !!(s.flags & SignatureFlags.HasLiteralTypes); + } + + export function signatureIsOptionalCall(s: Signature) { + return !!(s.flags & SignatureFlags.IsOptionalCall); + } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f8f03dff44..94bfe7efc2 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3451,8 +3451,7 @@ namespace ts { resolvedReturnType: Type, typePredicate: TypePredicate | undefined, minArgumentCount: number, - hasRestParameter: boolean, - hasLiteralTypes: boolean, + flags: SignatureFlags ): Signature; /* @internal */ createSymbol(flags: SymbolFlags, name: __String): TransientSymbol; /* @internal */ createIndexInfo(type: Type, isReadonly: boolean, declaration?: SignatureDeclaration): IndexInfo; @@ -4671,7 +4670,22 @@ namespace ts { Construct, } + /* @internal */ + export const enum SignatureFlags { + None = 0, + HasRestParameter = 1 << 0, // Indicates last parameter is rest parameter + HasLiteralTypes = 1 << 1, // Indicates signature is specialized + IsOptionalCall = 1 << 2, // Indicates signature comes from a CallChain + + // We do not propagate `IsOptionalCall` to instantiated signatures, as that would result in us + // attempting to add `| undefined` on each recursive call to `getReturnTypeOfSignature` when + // instantiating the return type. + PropagatingFlags = HasRestParameter | HasLiteralTypes, + } + export interface Signature { + /* @internal */ flags: SignatureFlags; + /* @internal */ checker?: TypeChecker; declaration?: SignatureDeclaration | JSDocSignature; // Originating declaration typeParameters?: readonly TypeParameter[]; // Type parameters (undefined if non-generic) parameters: readonly Symbol[]; // Parameters @@ -4688,10 +4702,6 @@ namespace ts { /* @internal */ minArgumentCount: number; // Number of non-optional parameters /* @internal */ - hasRestParameter: boolean; // True if last parameter is rest parameter - /* @internal */ - hasLiteralTypes: boolean; // True if specialized - /* @internal */ target?: Signature; // Instantiation target /* @internal */ mapper?: TypeMapper; // Instantiation mapper @@ -4702,11 +4712,11 @@ namespace ts { /* @internal */ canonicalSignatureCache?: Signature; // Canonical version of signature (deferred) /* @internal */ + optionalCallSignatureCache?: Signature; // Optional chained call version of signature (deferred) + /* @internal */ isolatedSignatureType?: ObjectType; // A manufactured type that just contains the signature for purposes of signature comparison /* @internal */ instantiations?: Map; // Generic signature instantiation cache - /* @internal */ - isOptionalCall?: boolean; } export const enum IndexKind { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index a841007d9d..8733d07c95 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -5911,6 +5911,14 @@ namespace ts { || kind === SyntaxKind.CallExpression); } + /** + * Determines whether a node is the expression preceding an optional chain (i.e. `a` in `a?.b`). + */ + /* @internal */ + export function isExpressionOfOptionalChainRoot(node: Node): node is Expression & { parent: OptionalChainRoot } { + return isOptionalChainRoot(node.parent) && node.parent.expression === node; + } + export function isNewExpression(node: Node): node is NewExpression { return node.kind === SyntaxKind.NewExpression; } @@ -7310,7 +7318,7 @@ namespace ts { getSourceFileConstructor(): new (kind: SyntaxKind.SourceFile, pos?: number, end?: number) => SourceFile; getSymbolConstructor(): new (flags: SymbolFlags, name: __String) => Symbol; getTypeConstructor(): new (checker: TypeChecker, flags: TypeFlags) => Type; - getSignatureConstructor(): new (checker: TypeChecker) => Signature; + getSignatureConstructor(): new (checker: TypeChecker, flags: SignatureFlags) => Signature; getSourceMapSourceConstructor(): new (fileName: string, text: string, skipTrivia?: (pos: number) => number) => SourceMapSource; } @@ -7331,7 +7339,12 @@ namespace ts { } } - function Signature() {} + function Signature(this: Signature, checker: TypeChecker, flags: SignatureFlags) { + this.flags = flags; + if (Debug.isDebugging) { + this.checker = checker; + } + } function Node(this: Node, kind: SyntaxKind, pos: number, end: number) { this.pos = pos; diff --git a/src/services/codefixes/helpers.ts b/src/services/codefixes/helpers.ts index 307e49c0c4..b127fa2404 100644 --- a/src/services/codefixes/helpers.ts +++ b/src/services/codefixes/helpers.ts @@ -233,14 +233,14 @@ namespace ts.codefix { let someSigHasRestParameter = false; for (const sig of signatures) { minArgumentCount = Math.min(sig.minArgumentCount, minArgumentCount); - if (sig.hasRestParameter) { + if (signatureHasRestParameter(sig)) { someSigHasRestParameter = true; } - if (sig.parameters.length >= maxArgsSignature.parameters.length && (!sig.hasRestParameter || maxArgsSignature.hasRestParameter)) { + if (sig.parameters.length >= maxArgsSignature.parameters.length && (!signatureHasRestParameter(sig) || signatureHasRestParameter(maxArgsSignature))) { maxArgsSignature = sig; } } - const maxNonRestArgs = maxArgsSignature.parameters.length - (maxArgsSignature.hasRestParameter ? 1 : 0); + const maxNonRestArgs = maxArgsSignature.parameters.length - (signatureHasRestParameter(maxArgsSignature) ? 1 : 0); const maxArgsParameterSymbolNames = maxArgsSignature.parameters.map(symbol => symbol.name); const parameters = createDummyParameters(maxNonRestArgs, maxArgsParameterSymbolNames, /* types */ undefined, minArgumentCount, /*inJs*/ false); diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index 0262f83b59..acd2a295d5 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -1112,7 +1112,7 @@ namespace ts.codefix { } const returnType = combineFromUsage(combineUsages(calls.map(call => call.return_))); // TODO: GH#18217 - return checker.createSignature(/*declaration*/ undefined!, /*typeParameters*/ undefined, /*thisParameter*/ undefined, parameters, returnType, /*typePredicate*/ undefined, length, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false); + return checker.createSignature(/*declaration*/ undefined!, /*typeParameters*/ undefined, /*thisParameter*/ undefined, parameters, returnType, /*typePredicate*/ undefined, length, SignatureFlags.None); } function addCandidateType(usage: Usage, type: Type | undefined) { diff --git a/src/services/services.ts b/src/services/services.ts index e0a088db05..acd3688f33 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -464,6 +464,7 @@ namespace ts { } class SignatureObject implements Signature { + flags: SignatureFlags; checker: TypeChecker; declaration!: SignatureDeclaration; typeParameters?: TypeParameter[]; @@ -473,8 +474,6 @@ namespace ts { resolvedTypePredicate: TypePredicate | undefined; minTypeArgumentCount!: number; minArgumentCount!: number; - hasRestParameter!: boolean; - hasLiteralTypes!: boolean; // Undefined is used to indicate the value has not been computed. If, after computing, the // symbol has no doc comment, then the empty array will be returned. @@ -484,9 +483,11 @@ namespace ts { // symbol has no doc comment, then the empty array will be returned. jsDocTags?: JSDocTagInfo[]; - constructor(checker: TypeChecker) { + constructor(checker: TypeChecker, flags: SignatureFlags) { this.checker = checker; + this.flags = flags; } + getDeclaration(): SignatureDeclaration { return this.declaration; } diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts index 7e106ae54c..b474f39104 100644 --- a/src/services/stringCompletions.ts +++ b/src/services/stringCompletions.ts @@ -204,7 +204,7 @@ namespace ts.Completions.StringCompletions { const candidates: Signature[] = []; checker.getResolvedSignature(argumentInfo.invocation, candidates, argumentInfo.argumentCount); const types = flatMap(candidates, candidate => { - if (!candidate.hasRestParameter && argumentInfo.argumentCount > candidate.parameters.length) return; + if (!signatureHasRestParameter(candidate) && argumentInfo.argumentCount > candidate.parameters.length) return; const type = checker.getParameterType(candidate, argumentInfo.argumentIndex); isNewIdentifier = isNewIdentifier || !!(type.flags & TypeFlags.String); return getStringLiteralTypes(type, uniques); From 0a0833b376c6b0b1eb89ae4c7772520ecc81d02e Mon Sep 17 00:00:00 2001 From: Orta Therox Date: Fri, 18 Oct 2019 16:35:58 -0400 Subject: [PATCH 19/47] Improve the launch template --- .vscode/launch.template.json | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/.vscode/launch.template.json b/.vscode/launch.template.json index 4ba7f8ab88..ede5febb3b 100644 --- a/.vscode/launch.template.json +++ b/.vscode/launch.template.json @@ -1,5 +1,17 @@ -// Rename this file 'launch.json' or merge its -// contents into your existing configurations. +/* + + Copy this file into '.vscode/launch.json' or merge its + contents into your existing configurations. + + If you want to remove the errors in comments for all JSON + files, add this to your settings in ~/.vscode/User/settings.json + + "files.associations": { + "*.json": "jsonc" + }, + +*/ + { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. @@ -10,7 +22,7 @@ "type": "node", "protocol": "inspector", "request": "launch", - "name": "Mocha Tests (currently opened test)", + "name": "Mocha Tests (currently opened test)", "runtimeArgs": ["--nolazy"], "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", "args": [ @@ -20,6 +32,8 @@ "--colors", "built/local/run.js", "-f", + // You can change this to be the name of a specific test file (without the file extension) + // to consistently launch the same test "${fileBasenameNoExtension}", "--skip-percent", "0" @@ -34,6 +48,13 @@ "outFiles": [ "${workspaceRoot}/built/local/run.js" ] + }, + { + // See: https://github.com/microsoft/TypeScript/wiki/Debugging-Language-Service-in-VS-Code + "type": "node", + "request": "attach", + "name": "Attach to VS Code TS Server via Port", + "processId": "${command:PickProcess}" } ] -} \ No newline at end of file +} From 60b391507e0ef2fa8d88c93312ccc0931d1ca0f2 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 19 Oct 2019 07:04:24 -0700 Subject: [PATCH 20/47] And a few more --- src/compiler/checker.ts | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2c670397fe..7c6336276f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19187,13 +19187,18 @@ namespace ts { type = narrowBySwitchOnTypeOf(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd); } else { - if (strictNullChecks && optionalChainContainsReference(expr, reference)) { - type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd); + if (strictNullChecks) { + if (optionalChainContainsReference(expr, reference)) { + type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd, + t => !(t.flags & (TypeFlags.Undefined | TypeFlags.Never))); + } + else if (expr.kind === SyntaxKind.TypeOfExpression && optionalChainContainsReference((expr as TypeOfExpression).expression, reference)) { + type = narrowTypeBySwitchOptionalChainContainment(type, flow.switchStatement, flow.clauseStart, flow.clauseEnd, + t => !(t.flags & TypeFlags.Never || t.flags & TypeFlags.StringLiteral && (t).value === "undefined")); + } } if (isMatchingReferenceDiscriminant(expr, type)) { - type = narrowTypeByDiscriminant( - type, - expr as AccessExpression, + type = narrowTypeByDiscriminant(type, expr as AccessExpression, t => narrowTypeBySwitchOnDiscriminant(t, flow.switchStatement, flow.clauseStart, flow.clauseEnd)); } else if (containsMatchingReferenceDiscriminant(reference, expr)) { @@ -19539,10 +19544,9 @@ namespace ts { } } - function narrowTypeBySwitchOptionalChainContainment(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { - const noClauseIsDefaultOrUndefined = clauseStart !== clauseEnd && - every(getSwitchClauseTypes(switchStatement).slice(clauseStart, clauseEnd), t => !(t.flags & (TypeFlags.Undefined | TypeFlags.Never))); - return noClauseIsDefaultOrUndefined ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; + function narrowTypeBySwitchOptionalChainContainment(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number, clauseCheck: (type: Type) => boolean) { + const everyClauseChecks = clauseStart !== clauseEnd && every(getSwitchClauseTypes(switchStatement).slice(clauseStart, clauseEnd), clauseCheck); + return everyClauseChecks ? getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull) : type; } function narrowTypeBySwitchOnDiscriminant(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) { @@ -19771,8 +19775,9 @@ namespace ts { if (isMatchingReference(reference, predicateArgument)) { return getNarrowedType(type, predicate.type, assumeTrue, isTypeSubtypeOf); } - if (strictNullChecks && assumeTrue && !(getTypeFacts(predicate.type) & TypeFacts.EQUndefined)) { - type = getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); + if (strictNullChecks && assumeTrue && optionalChainContainsReference(predicateArgument, reference) && + !(getTypeFacts(predicate.type) & TypeFacts.EQUndefined)) { + return getTypeWithFacts(type, TypeFacts.NEUndefinedOrNull); } if (containsMatchingReference(reference, predicateArgument)) { return declaredType; From d218a31a7dc5ec5407ef7b3cbed4c1338b0855e7 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 19 Oct 2019 10:51:59 -0700 Subject: [PATCH 21/47] Add tests --- .../controlFlow/controlFlowOptionalChain.ts | 197 ++++++++++++++++++ 1 file changed, 197 insertions(+) diff --git a/tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts b/tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts index f54275f309..49ebd3807d 100644 --- a/tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts +++ b/tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts @@ -302,6 +302,60 @@ function f14(o: Thing | null) { } } +function f15(o: Thing | undefined, value: number) { + if (o?.foo === value) { + o.foo; + } + else { + o.foo; // Error + } + if (o?.foo !== value) { + o.foo; // Error + } + else { + o.foo; + } + if (o?.foo == value) { + o.foo; + } + else { + o.foo; // Error + } + if (o?.foo != value) { + o.foo; // Error + } + else { + o.foo; + } +} + +function f16(o: Thing | undefined) { + if (o?.foo === undefined) { + o.foo; // Error + } + else { + o.foo; + } + if (o?.foo !== undefined) { + o.foo; + } + else { + o.foo; // Error + } + if (o?.foo == undefined) { + o.foo; // Error + } + else { + o.foo; + } + if (o?.foo != undefined) { + o.foo; + } + else { + o.foo; // Error + } +} + function f20(o: Thing | undefined) { if (typeof o?.foo === "number") { o.foo; @@ -331,3 +385,146 @@ function f21(o: Thing | null) { o.baz; } } + +function f22(o: Thing | undefined) { + if (typeof o?.foo === "number") { + o.foo; + } + else { + o.foo; // Error + } + if (typeof o?.foo !== "number") { + o.foo; // Error + } + else { + o.foo; + } + if (typeof o?.foo == "number") { + o.foo; + } + else { + o.foo; // Error + } + if (typeof o?.foo != "number") { + o.foo; // Error + } + else { + o.foo; + } +} + +function f23(o: Thing | undefined) { + if (typeof o?.foo === "undefined") { + o.foo; // Error + } + else { + o.foo; + } + if (typeof o?.foo !== "undefined") { + o.foo; + } + else { + o.foo; // Error + } + if (typeof o?.foo == "undefined") { + o.foo; // Error + } + else { + o.foo; + } + if (typeof o?.foo != "undefined") { + o.foo; + } + else { + o.foo; // Error + } +} + +declare function assert(x: unknown): asserts x; +declare function assertNonNull(x: T): asserts x is NonNullable; + +function f30(o: Thing | undefined) { + if (!!true) { + assert(o?.foo); + o.foo; + } + if (!!true) { + assert(o?.foo === 42); + o.foo; + } + if (!!true) { + assert(typeof o?.foo === "number"); + o.foo; + } + if (!!true) { + assertNonNull(o?.foo); + o.foo; + } +} + +function f40(o: Thing | undefined) { + switch (o?.foo) { + case "abc": + o.foo; + break; + case 42: + o.foo; + break; + case undefined: + o.foo; // Error + break; + default: + o.foo; // Error + break; + } +} + +function f41(o: Thing | undefined) { + switch (typeof o?.foo) { + case "string": + o.foo; + break; + case "number": + o.foo; + break; + case "undefined": + o.foo; // Error + break; + default: + o.foo; // Error + break; + } +} + +// Repros from #34570 + +type Shape = + | { type: 'rectangle', width: number, height: number } + | { type: 'circle', radius: number } + +function getArea(shape?: Shape) { + switch (shape?.type) { + case 'circle': + return Math.PI * shape.radius ** 2 + case 'rectangle': + return shape.width * shape.height + default: + return 0 + } +} + +type Feature = { + id: string; + geometry?: { + type: string; + coordinates: number[]; + }; +}; + + +function extractCoordinates(f: Feature): number[] { + if (f.geometry?.type !== 'test') { + return []; + } + return f.geometry.coordinates; +} From ee846d95af9d55280fc5cc97741403268ef9b242 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 19 Oct 2019 10:52:04 -0700 Subject: [PATCH 22/47] Accept new baselines --- .../controlFlowOptionalChain.errors.txt | 259 +++++++- .../reference/controlFlowOptionalChain.js | 376 +++++++++++ .../controlFlowOptionalChain.symbols | 572 ++++++++++++++++- .../reference/controlFlowOptionalChain.types | 599 ++++++++++++++++++ 4 files changed, 1786 insertions(+), 20 deletions(-) diff --git a/tests/baselines/reference/controlFlowOptionalChain.errors.txt b/tests/baselines/reference/controlFlowOptionalChain.errors.txt index 987dca335d..ee77494d1c 100644 --- a/tests/baselines/reference/controlFlowOptionalChain.errors.txt +++ b/tests/baselines/reference/controlFlowOptionalChain.errors.txt @@ -31,9 +31,29 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(244,9): error TS tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(271,9): error TS2532: Object is possibly 'undefined'. tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(274,9): error TS2532: Object is possibly 'undefined'. tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(277,9): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(307,9): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(310,9): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(319,9): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(322,9): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(331,9): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(340,9): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(343,9): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(352,9): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(391,9): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(394,9): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(403,9): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(406,9): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(415,9): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(424,9): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(427,9): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(436,9): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(471,13): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(474,13): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(488,13): error TS2532: Object is possibly 'undefined'. +tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(491,13): error TS2532: Object is possibly 'undefined'. -==== tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts (33 errors) ==== +==== tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts (53 errors) ==== // assignments in shortcutting chain declare const o: undefined | { [key: string]: any; @@ -401,6 +421,76 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(277,9): error TS } } + function f15(o: Thing | undefined, value: number) { + if (o?.foo === value) { + o.foo; + } + else { + o.foo; // Error + ~ +!!! error TS2532: Object is possibly 'undefined'. + } + if (o?.foo !== value) { + o.foo; // Error + ~ +!!! error TS2532: Object is possibly 'undefined'. + } + else { + o.foo; + } + if (o?.foo == value) { + o.foo; + } + else { + o.foo; // Error + ~ +!!! error TS2532: Object is possibly 'undefined'. + } + if (o?.foo != value) { + o.foo; // Error + ~ +!!! error TS2532: Object is possibly 'undefined'. + } + else { + o.foo; + } + } + + function f16(o: Thing | undefined) { + if (o?.foo === undefined) { + o.foo; // Error + ~ +!!! error TS2532: Object is possibly 'undefined'. + } + else { + o.foo; + } + if (o?.foo !== undefined) { + o.foo; + } + else { + o.foo; // Error + ~ +!!! error TS2532: Object is possibly 'undefined'. + } + if (o?.foo == undefined) { + o.foo; // Error + ~ +!!! error TS2532: Object is possibly 'undefined'. + } + else { + o.foo; + } + if (o?.foo != undefined) { + o.foo; + } + else { + o.foo; // Error + ~ +!!! error TS2532: Object is possibly 'undefined'. + } + } + function f20(o: Thing | undefined) { if (typeof o?.foo === "number") { o.foo; @@ -430,4 +520,171 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(277,9): error TS o.baz; } } + + function f22(o: Thing | undefined) { + if (typeof o?.foo === "number") { + o.foo; + } + else { + o.foo; // Error + ~ +!!! error TS2532: Object is possibly 'undefined'. + } + if (typeof o?.foo !== "number") { + o.foo; // Error + ~ +!!! error TS2532: Object is possibly 'undefined'. + } + else { + o.foo; + } + if (typeof o?.foo == "number") { + o.foo; + } + else { + o.foo; // Error + ~ +!!! error TS2532: Object is possibly 'undefined'. + } + if (typeof o?.foo != "number") { + o.foo; // Error + ~ +!!! error TS2532: Object is possibly 'undefined'. + } + else { + o.foo; + } + } + + function f23(o: Thing | undefined) { + if (typeof o?.foo === "undefined") { + o.foo; // Error + ~ +!!! error TS2532: Object is possibly 'undefined'. + } + else { + o.foo; + } + if (typeof o?.foo !== "undefined") { + o.foo; + } + else { + o.foo; // Error + ~ +!!! error TS2532: Object is possibly 'undefined'. + } + if (typeof o?.foo == "undefined") { + o.foo; // Error + ~ +!!! error TS2532: Object is possibly 'undefined'. + } + else { + o.foo; + } + if (typeof o?.foo != "undefined") { + o.foo; + } + else { + o.foo; // Error + ~ +!!! error TS2532: Object is possibly 'undefined'. + } + } + + declare function assert(x: unknown): asserts x; + declare function assertNonNull(x: T): asserts x is NonNullable; + + function f30(o: Thing | undefined) { + if (!!true) { + assert(o?.foo); + o.foo; + } + if (!!true) { + assert(o?.foo === 42); + o.foo; + } + if (!!true) { + assert(typeof o?.foo === "number"); + o.foo; + } + if (!!true) { + assertNonNull(o?.foo); + o.foo; + } + } + + function f40(o: Thing | undefined) { + switch (o?.foo) { + case "abc": + o.foo; + break; + case 42: + o.foo; + break; + case undefined: + o.foo; // Error + ~ +!!! error TS2532: Object is possibly 'undefined'. + break; + default: + o.foo; // Error + ~ +!!! error TS2532: Object is possibly 'undefined'. + break; + } + } + + function f41(o: Thing | undefined) { + switch (typeof o?.foo) { + case "string": + o.foo; + break; + case "number": + o.foo; + break; + case "undefined": + o.foo; // Error + ~ +!!! error TS2532: Object is possibly 'undefined'. + break; + default: + o.foo; // Error + ~ +!!! error TS2532: Object is possibly 'undefined'. + break; + } + } + + // Repros from #34570 + + type Shape = + | { type: 'rectangle', width: number, height: number } + | { type: 'circle', radius: number } + + function getArea(shape?: Shape) { + switch (shape?.type) { + case 'circle': + return Math.PI * shape.radius ** 2 + case 'rectangle': + return shape.width * shape.height + default: + return 0 + } + } + + type Feature = { + id: string; + geometry?: { + type: string; + coordinates: number[]; + }; + }; + + + function extractCoordinates(f: Feature): number[] { + if (f.geometry?.type !== 'test') { + return []; + } + return f.geometry.coordinates; + } \ No newline at end of file diff --git a/tests/baselines/reference/controlFlowOptionalChain.js b/tests/baselines/reference/controlFlowOptionalChain.js index 2145152803..3bb2bab1c0 100644 --- a/tests/baselines/reference/controlFlowOptionalChain.js +++ b/tests/baselines/reference/controlFlowOptionalChain.js @@ -300,6 +300,60 @@ function f14(o: Thing | null) { } } +function f15(o: Thing | undefined, value: number) { + if (o?.foo === value) { + o.foo; + } + else { + o.foo; // Error + } + if (o?.foo !== value) { + o.foo; // Error + } + else { + o.foo; + } + if (o?.foo == value) { + o.foo; + } + else { + o.foo; // Error + } + if (o?.foo != value) { + o.foo; // Error + } + else { + o.foo; + } +} + +function f16(o: Thing | undefined) { + if (o?.foo === undefined) { + o.foo; // Error + } + else { + o.foo; + } + if (o?.foo !== undefined) { + o.foo; + } + else { + o.foo; // Error + } + if (o?.foo == undefined) { + o.foo; // Error + } + else { + o.foo; + } + if (o?.foo != undefined) { + o.foo; + } + else { + o.foo; // Error + } +} + function f20(o: Thing | undefined) { if (typeof o?.foo === "number") { o.foo; @@ -329,6 +383,149 @@ function f21(o: Thing | null) { o.baz; } } + +function f22(o: Thing | undefined) { + if (typeof o?.foo === "number") { + o.foo; + } + else { + o.foo; // Error + } + if (typeof o?.foo !== "number") { + o.foo; // Error + } + else { + o.foo; + } + if (typeof o?.foo == "number") { + o.foo; + } + else { + o.foo; // Error + } + if (typeof o?.foo != "number") { + o.foo; // Error + } + else { + o.foo; + } +} + +function f23(o: Thing | undefined) { + if (typeof o?.foo === "undefined") { + o.foo; // Error + } + else { + o.foo; + } + if (typeof o?.foo !== "undefined") { + o.foo; + } + else { + o.foo; // Error + } + if (typeof o?.foo == "undefined") { + o.foo; // Error + } + else { + o.foo; + } + if (typeof o?.foo != "undefined") { + o.foo; + } + else { + o.foo; // Error + } +} + +declare function assert(x: unknown): asserts x; +declare function assertNonNull(x: T): asserts x is NonNullable; + +function f30(o: Thing | undefined) { + if (!!true) { + assert(o?.foo); + o.foo; + } + if (!!true) { + assert(o?.foo === 42); + o.foo; + } + if (!!true) { + assert(typeof o?.foo === "number"); + o.foo; + } + if (!!true) { + assertNonNull(o?.foo); + o.foo; + } +} + +function f40(o: Thing | undefined) { + switch (o?.foo) { + case "abc": + o.foo; + break; + case 42: + o.foo; + break; + case undefined: + o.foo; // Error + break; + default: + o.foo; // Error + break; + } +} + +function f41(o: Thing | undefined) { + switch (typeof o?.foo) { + case "string": + o.foo; + break; + case "number": + o.foo; + break; + case "undefined": + o.foo; // Error + break; + default: + o.foo; // Error + break; + } +} + +// Repros from #34570 + +type Shape = + | { type: 'rectangle', width: number, height: number } + | { type: 'circle', radius: number } + +function getArea(shape?: Shape) { + switch (shape?.type) { + case 'circle': + return Math.PI * shape.radius ** 2 + case 'rectangle': + return shape.width * shape.height + default: + return 0 + } +} + +type Feature = { + id: string; + geometry?: { + type: string; + coordinates: number[]; + }; +}; + + +function extractCoordinates(f: Feature): number[] { + if (f.geometry?.type !== 'test') { + return []; + } + return f.geometry.coordinates; +} //// [controlFlowOptionalChain.js] @@ -594,6 +791,60 @@ function f14(o) { o.bar; } } +function f15(o, value) { + var _a, _b, _c, _d; + if (((_a = o) === null || _a === void 0 ? void 0 : _a.foo) === value) { + o.foo; + } + else { + o.foo; // Error + } + if (((_b = o) === null || _b === void 0 ? void 0 : _b.foo) !== value) { + o.foo; // Error + } + else { + o.foo; + } + if (((_c = o) === null || _c === void 0 ? void 0 : _c.foo) == value) { + o.foo; + } + else { + o.foo; // Error + } + if (((_d = o) === null || _d === void 0 ? void 0 : _d.foo) != value) { + o.foo; // Error + } + else { + o.foo; + } +} +function f16(o) { + var _a, _b, _c, _d; + if (((_a = o) === null || _a === void 0 ? void 0 : _a.foo) === undefined) { + o.foo; // Error + } + else { + o.foo; + } + if (((_b = o) === null || _b === void 0 ? void 0 : _b.foo) !== undefined) { + o.foo; + } + else { + o.foo; // Error + } + if (((_c = o) === null || _c === void 0 ? void 0 : _c.foo) == undefined) { + o.foo; // Error + } + else { + o.foo; + } + if (((_d = o) === null || _d === void 0 ? void 0 : _d.foo) != undefined) { + o.foo; + } + else { + o.foo; // Error + } +} function f20(o) { var _a, _b, _c, _d; if (typeof ((_a = o) === null || _a === void 0 ? void 0 : _a.foo) === "number") { @@ -624,3 +875,128 @@ function f21(o) { o.baz; } } +function f22(o) { + var _a, _b, _c, _d; + if (typeof ((_a = o) === null || _a === void 0 ? void 0 : _a.foo) === "number") { + o.foo; + } + else { + o.foo; // Error + } + if (typeof ((_b = o) === null || _b === void 0 ? void 0 : _b.foo) !== "number") { + o.foo; // Error + } + else { + o.foo; + } + if (typeof ((_c = o) === null || _c === void 0 ? void 0 : _c.foo) == "number") { + o.foo; + } + else { + o.foo; // Error + } + if (typeof ((_d = o) === null || _d === void 0 ? void 0 : _d.foo) != "number") { + o.foo; // Error + } + else { + o.foo; + } +} +function f23(o) { + var _a, _b, _c, _d; + if (typeof ((_a = o) === null || _a === void 0 ? void 0 : _a.foo) === "undefined") { + o.foo; // Error + } + else { + o.foo; + } + if (typeof ((_b = o) === null || _b === void 0 ? void 0 : _b.foo) !== "undefined") { + o.foo; + } + else { + o.foo; // Error + } + if (typeof ((_c = o) === null || _c === void 0 ? void 0 : _c.foo) == "undefined") { + o.foo; // Error + } + else { + o.foo; + } + if (typeof ((_d = o) === null || _d === void 0 ? void 0 : _d.foo) != "undefined") { + o.foo; + } + else { + o.foo; // Error + } +} +function f30(o) { + var _a, _b, _c, _d; + if (!!true) { + assert((_a = o) === null || _a === void 0 ? void 0 : _a.foo); + o.foo; + } + if (!!true) { + assert(((_b = o) === null || _b === void 0 ? void 0 : _b.foo) === 42); + o.foo; + } + if (!!true) { + assert(typeof ((_c = o) === null || _c === void 0 ? void 0 : _c.foo) === "number"); + o.foo; + } + if (!!true) { + assertNonNull((_d = o) === null || _d === void 0 ? void 0 : _d.foo); + o.foo; + } +} +function f40(o) { + var _a; + switch ((_a = o) === null || _a === void 0 ? void 0 : _a.foo) { + case "abc": + o.foo; + break; + case 42: + o.foo; + break; + case undefined: + o.foo; // Error + break; + default: + o.foo; // Error + break; + } +} +function f41(o) { + var _a; + switch (typeof ((_a = o) === null || _a === void 0 ? void 0 : _a.foo)) { + case "string": + o.foo; + break; + case "number": + o.foo; + break; + case "undefined": + o.foo; // Error + break; + default: + o.foo; // Error + break; + } +} +function getArea(shape) { + var _a; + switch ((_a = shape) === null || _a === void 0 ? void 0 : _a.type) { + case 'circle': + return Math.PI * Math.pow(shape.radius, 2); + case 'rectangle': + return shape.width * shape.height; + default: + return 0; + } +} +function extractCoordinates(f) { + var _a; + if (((_a = f.geometry) === null || _a === void 0 ? void 0 : _a.type) !== 'test') { + return []; + } + return f.geometry.coordinates; +} diff --git a/tests/baselines/reference/controlFlowOptionalChain.symbols b/tests/baselines/reference/controlFlowOptionalChain.symbols index 2665a960a5..cac0899cc8 100644 --- a/tests/baselines/reference/controlFlowOptionalChain.symbols +++ b/tests/baselines/reference/controlFlowOptionalChain.symbols @@ -1039,93 +1039,627 @@ function f14(o: Thing | null) { } } -function f20(o: Thing | undefined) { ->f20 : Symbol(f20, Decl(controlFlowOptionalChain.ts, 299, 1)) +function f15(o: Thing | undefined, value: number) { +>f15 : Symbol(f15, Decl(controlFlowOptionalChain.ts, 299, 1)) >o : Symbol(o, Decl(controlFlowOptionalChain.ts, 301, 13)) >Thing : Symbol(Thing, Decl(controlFlowOptionalChain.ts, 159, 1)) +>value : Symbol(value, Decl(controlFlowOptionalChain.ts, 301, 34)) - if (typeof o?.foo === "number") { + if (o?.foo === value) { >o?.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) >o : Symbol(o, Decl(controlFlowOptionalChain.ts, 301, 13)) >foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>value : Symbol(value, Decl(controlFlowOptionalChain.ts, 301, 34)) o.foo; >o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) >o : Symbol(o, Decl(controlFlowOptionalChain.ts, 301, 13)) >foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) } - if (typeof o?.["foo"] === "number") { + else { + o.foo; // Error +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) >o : Symbol(o, Decl(controlFlowOptionalChain.ts, 301, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + if (o?.foo !== value) { +>o?.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 301, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>value : Symbol(value, Decl(controlFlowOptionalChain.ts, 301, 34)) + + o.foo; // Error +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 301, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + else { + o.foo; +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 301, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + if (o?.foo == value) { +>o?.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 301, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>value : Symbol(value, Decl(controlFlowOptionalChain.ts, 301, 34)) + + o.foo; +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 301, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + else { + o.foo; // Error +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 301, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + if (o?.foo != value) { +>o?.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 301, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>value : Symbol(value, Decl(controlFlowOptionalChain.ts, 301, 34)) + + o.foo; // Error +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 301, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + else { + o.foo; +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 301, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } +} + +function f16(o: Thing | undefined) { +>f16 : Symbol(f16, Decl(controlFlowOptionalChain.ts, 326, 1)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 328, 13)) +>Thing : Symbol(Thing, Decl(controlFlowOptionalChain.ts, 159, 1)) + + if (o?.foo === undefined) { +>o?.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 328, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>undefined : Symbol(undefined) + + o.foo; // Error +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 328, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + else { + o.foo; +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 328, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + if (o?.foo !== undefined) { +>o?.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 328, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>undefined : Symbol(undefined) + + o.foo; +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 328, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + else { + o.foo; // Error +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 328, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + if (o?.foo == undefined) { +>o?.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 328, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>undefined : Symbol(undefined) + + o.foo; // Error +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 328, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + else { + o.foo; +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 328, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + if (o?.foo != undefined) { +>o?.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 328, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>undefined : Symbol(undefined) + + o.foo; +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 328, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + else { + o.foo; // Error +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 328, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } +} + +function f20(o: Thing | undefined) { +>f20 : Symbol(f20, Decl(controlFlowOptionalChain.ts, 353, 1)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 355, 13)) +>Thing : Symbol(Thing, Decl(controlFlowOptionalChain.ts, 159, 1)) + + if (typeof o?.foo === "number") { +>o?.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 355, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + + o.foo; +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 355, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + if (typeof o?.["foo"] === "number") { +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 355, 13)) o["foo"]; ->o : Symbol(o, Decl(controlFlowOptionalChain.ts, 301, 13)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 355, 13)) >"foo" : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) } if (typeof o?.bar() === "number") { >o?.bar : Symbol(bar, Decl(controlFlowOptionalChain.ts, 161, 36)) ->o : Symbol(o, Decl(controlFlowOptionalChain.ts, 301, 13)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 355, 13)) >bar : Symbol(bar, Decl(controlFlowOptionalChain.ts, 161, 36)) o.bar; >o.bar : Symbol(bar, Decl(controlFlowOptionalChain.ts, 161, 36)) ->o : Symbol(o, Decl(controlFlowOptionalChain.ts, 301, 13)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 355, 13)) >bar : Symbol(bar, Decl(controlFlowOptionalChain.ts, 161, 36)) } if (o?.baz instanceof Error) { >o?.baz : Symbol(baz, Decl(controlFlowOptionalChain.ts, 161, 51)) ->o : Symbol(o, Decl(controlFlowOptionalChain.ts, 301, 13)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 355, 13)) >baz : Symbol(baz, Decl(controlFlowOptionalChain.ts, 161, 51)) >Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) o.baz; >o.baz : Symbol(baz, Decl(controlFlowOptionalChain.ts, 161, 51)) ->o : Symbol(o, Decl(controlFlowOptionalChain.ts, 301, 13)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 355, 13)) >baz : Symbol(baz, Decl(controlFlowOptionalChain.ts, 161, 51)) } } function f21(o: Thing | null) { ->f21 : Symbol(f21, Decl(controlFlowOptionalChain.ts, 314, 1)) ->o : Symbol(o, Decl(controlFlowOptionalChain.ts, 316, 13)) +>f21 : Symbol(f21, Decl(controlFlowOptionalChain.ts, 368, 1)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 370, 13)) >Thing : Symbol(Thing, Decl(controlFlowOptionalChain.ts, 159, 1)) if (typeof o?.foo === "number") { >o?.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) ->o : Symbol(o, Decl(controlFlowOptionalChain.ts, 316, 13)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 370, 13)) >foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) o.foo; >o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) ->o : Symbol(o, Decl(controlFlowOptionalChain.ts, 316, 13)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 370, 13)) >foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) } if (typeof o?.["foo"] === "number") { ->o : Symbol(o, Decl(controlFlowOptionalChain.ts, 316, 13)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 370, 13)) o["foo"]; ->o : Symbol(o, Decl(controlFlowOptionalChain.ts, 316, 13)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 370, 13)) >"foo" : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) } if (typeof o?.bar() === "number") { >o?.bar : Symbol(bar, Decl(controlFlowOptionalChain.ts, 161, 36)) ->o : Symbol(o, Decl(controlFlowOptionalChain.ts, 316, 13)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 370, 13)) >bar : Symbol(bar, Decl(controlFlowOptionalChain.ts, 161, 36)) o.bar; >o.bar : Symbol(bar, Decl(controlFlowOptionalChain.ts, 161, 36)) ->o : Symbol(o, Decl(controlFlowOptionalChain.ts, 316, 13)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 370, 13)) >bar : Symbol(bar, Decl(controlFlowOptionalChain.ts, 161, 36)) } if (o?.baz instanceof Error) { >o?.baz : Symbol(baz, Decl(controlFlowOptionalChain.ts, 161, 51)) ->o : Symbol(o, Decl(controlFlowOptionalChain.ts, 316, 13)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 370, 13)) >baz : Symbol(baz, Decl(controlFlowOptionalChain.ts, 161, 51)) >Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) o.baz; >o.baz : Symbol(baz, Decl(controlFlowOptionalChain.ts, 161, 51)) ->o : Symbol(o, Decl(controlFlowOptionalChain.ts, 316, 13)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 370, 13)) >baz : Symbol(baz, Decl(controlFlowOptionalChain.ts, 161, 51)) } } +function f22(o: Thing | undefined) { +>f22 : Symbol(f22, Decl(controlFlowOptionalChain.ts, 383, 1)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 385, 13)) +>Thing : Symbol(Thing, Decl(controlFlowOptionalChain.ts, 159, 1)) + + if (typeof o?.foo === "number") { +>o?.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 385, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + + o.foo; +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 385, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + else { + o.foo; // Error +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 385, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + if (typeof o?.foo !== "number") { +>o?.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 385, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + + o.foo; // Error +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 385, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + else { + o.foo; +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 385, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + if (typeof o?.foo == "number") { +>o?.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 385, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + + o.foo; +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 385, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + else { + o.foo; // Error +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 385, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + if (typeof o?.foo != "number") { +>o?.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 385, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + + o.foo; // Error +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 385, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + else { + o.foo; +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 385, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } +} + +function f23(o: Thing | undefined) { +>f23 : Symbol(f23, Decl(controlFlowOptionalChain.ts, 410, 1)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 412, 13)) +>Thing : Symbol(Thing, Decl(controlFlowOptionalChain.ts, 159, 1)) + + if (typeof o?.foo === "undefined") { +>o?.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 412, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + + o.foo; // Error +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 412, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + else { + o.foo; +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 412, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + if (typeof o?.foo !== "undefined") { +>o?.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 412, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + + o.foo; +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 412, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + else { + o.foo; // Error +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 412, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + if (typeof o?.foo == "undefined") { +>o?.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 412, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + + o.foo; // Error +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 412, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + else { + o.foo; +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 412, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + if (typeof o?.foo != "undefined") { +>o?.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 412, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + + o.foo; +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 412, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + else { + o.foo; // Error +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 412, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } +} + +declare function assert(x: unknown): asserts x; +>assert : Symbol(assert, Decl(controlFlowOptionalChain.ts, 437, 1)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 439, 24)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 439, 24)) + +declare function assertNonNull(x: T): asserts x is NonNullable; +>assertNonNull : Symbol(assertNonNull, Decl(controlFlowOptionalChain.ts, 439, 47)) +>T : Symbol(T, Decl(controlFlowOptionalChain.ts, 440, 31)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 440, 34)) +>T : Symbol(T, Decl(controlFlowOptionalChain.ts, 440, 31)) +>x : Symbol(x, Decl(controlFlowOptionalChain.ts, 440, 34)) +>NonNullable : Symbol(NonNullable, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(controlFlowOptionalChain.ts, 440, 31)) + +function f30(o: Thing | undefined) { +>f30 : Symbol(f30, Decl(controlFlowOptionalChain.ts, 440, 69)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 442, 13)) +>Thing : Symbol(Thing, Decl(controlFlowOptionalChain.ts, 159, 1)) + + if (!!true) { + assert(o?.foo); +>assert : Symbol(assert, Decl(controlFlowOptionalChain.ts, 437, 1)) +>o?.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 442, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + + o.foo; +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 442, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + if (!!true) { + assert(o?.foo === 42); +>assert : Symbol(assert, Decl(controlFlowOptionalChain.ts, 437, 1)) +>o?.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 442, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + + o.foo; +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 442, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + if (!!true) { + assert(typeof o?.foo === "number"); +>assert : Symbol(assert, Decl(controlFlowOptionalChain.ts, 437, 1)) +>o?.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 442, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + + o.foo; +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 442, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } + if (!!true) { + assertNonNull(o?.foo); +>assertNonNull : Symbol(assertNonNull, Decl(controlFlowOptionalChain.ts, 439, 47)) +>o?.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 442, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + + o.foo; +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 442, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + } +} + +function f40(o: Thing | undefined) { +>f40 : Symbol(f40, Decl(controlFlowOptionalChain.ts, 459, 1)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 461, 13)) +>Thing : Symbol(Thing, Decl(controlFlowOptionalChain.ts, 159, 1)) + + switch (o?.foo) { +>o?.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 461, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + + case "abc": + o.foo; +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 461, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + + break; + case 42: + o.foo; +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 461, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + + break; + case undefined: +>undefined : Symbol(undefined) + + o.foo; // Error +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 461, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + + break; + default: + o.foo; // Error +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 461, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + + break; + } +} + +function f41(o: Thing | undefined) { +>f41 : Symbol(f41, Decl(controlFlowOptionalChain.ts, 476, 1)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 478, 13)) +>Thing : Symbol(Thing, Decl(controlFlowOptionalChain.ts, 159, 1)) + + switch (typeof o?.foo) { +>o?.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 478, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + + case "string": + o.foo; +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 478, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + + break; + case "number": + o.foo; +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 478, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + + break; + case "undefined": + o.foo; // Error +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 478, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + + break; + default: + o.foo; // Error +>o.foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) +>o : Symbol(o, Decl(controlFlowOptionalChain.ts, 478, 13)) +>foo : Symbol(foo, Decl(controlFlowOptionalChain.ts, 161, 14)) + + break; + } +} + +// Repros from #34570 + +type Shape = +>Shape : Symbol(Shape, Decl(controlFlowOptionalChain.ts, 493, 1)) + + | { type: 'rectangle', width: number, height: number } +>type : Symbol(type, Decl(controlFlowOptionalChain.ts, 498, 7)) +>width : Symbol(width, Decl(controlFlowOptionalChain.ts, 498, 26)) +>height : Symbol(height, Decl(controlFlowOptionalChain.ts, 498, 41)) + + | { type: 'circle', radius: number } +>type : Symbol(type, Decl(controlFlowOptionalChain.ts, 499, 7)) +>radius : Symbol(radius, Decl(controlFlowOptionalChain.ts, 499, 23)) + +function getArea(shape?: Shape) { +>getArea : Symbol(getArea, Decl(controlFlowOptionalChain.ts, 499, 40)) +>shape : Symbol(shape, Decl(controlFlowOptionalChain.ts, 501, 17)) +>Shape : Symbol(Shape, Decl(controlFlowOptionalChain.ts, 493, 1)) + + switch (shape?.type) { +>shape?.type : Symbol(type, Decl(controlFlowOptionalChain.ts, 498, 7), Decl(controlFlowOptionalChain.ts, 499, 7)) +>shape : Symbol(shape, Decl(controlFlowOptionalChain.ts, 501, 17)) +>type : Symbol(type, Decl(controlFlowOptionalChain.ts, 498, 7), Decl(controlFlowOptionalChain.ts, 499, 7)) + + case 'circle': + return Math.PI * shape.radius ** 2 +>Math.PI : Symbol(Math.PI, Decl(lib.es5.d.ts, --, --)) +>Math : Symbol(Math, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) +>PI : Symbol(Math.PI, Decl(lib.es5.d.ts, --, --)) +>shape.radius : Symbol(radius, Decl(controlFlowOptionalChain.ts, 499, 23)) +>shape : Symbol(shape, Decl(controlFlowOptionalChain.ts, 501, 17)) +>radius : Symbol(radius, Decl(controlFlowOptionalChain.ts, 499, 23)) + + case 'rectangle': + return shape.width * shape.height +>shape.width : Symbol(width, Decl(controlFlowOptionalChain.ts, 498, 26)) +>shape : Symbol(shape, Decl(controlFlowOptionalChain.ts, 501, 17)) +>width : Symbol(width, Decl(controlFlowOptionalChain.ts, 498, 26)) +>shape.height : Symbol(height, Decl(controlFlowOptionalChain.ts, 498, 41)) +>shape : Symbol(shape, Decl(controlFlowOptionalChain.ts, 501, 17)) +>height : Symbol(height, Decl(controlFlowOptionalChain.ts, 498, 41)) + + default: + return 0 + } +} + +type Feature = { +>Feature : Symbol(Feature, Decl(controlFlowOptionalChain.ts, 510, 1)) + + id: string; +>id : Symbol(id, Decl(controlFlowOptionalChain.ts, 512, 16)) + + geometry?: { +>geometry : Symbol(geometry, Decl(controlFlowOptionalChain.ts, 513, 13)) + + type: string; +>type : Symbol(type, Decl(controlFlowOptionalChain.ts, 514, 14)) + + coordinates: number[]; +>coordinates : Symbol(coordinates, Decl(controlFlowOptionalChain.ts, 515, 17)) + + }; +}; + + +function extractCoordinates(f: Feature): number[] { +>extractCoordinates : Symbol(extractCoordinates, Decl(controlFlowOptionalChain.ts, 518, 2)) +>f : Symbol(f, Decl(controlFlowOptionalChain.ts, 521, 28)) +>Feature : Symbol(Feature, Decl(controlFlowOptionalChain.ts, 510, 1)) + + if (f.geometry?.type !== 'test') { +>f.geometry?.type : Symbol(type, Decl(controlFlowOptionalChain.ts, 514, 14)) +>f.geometry : Symbol(geometry, Decl(controlFlowOptionalChain.ts, 513, 13)) +>f : Symbol(f, Decl(controlFlowOptionalChain.ts, 521, 28)) +>geometry : Symbol(geometry, Decl(controlFlowOptionalChain.ts, 513, 13)) +>type : Symbol(type, Decl(controlFlowOptionalChain.ts, 514, 14)) + + return []; + } + return f.geometry.coordinates; +>f.geometry.coordinates : Symbol(coordinates, Decl(controlFlowOptionalChain.ts, 515, 17)) +>f.geometry : Symbol(geometry, Decl(controlFlowOptionalChain.ts, 513, 13)) +>f : Symbol(f, Decl(controlFlowOptionalChain.ts, 521, 28)) +>geometry : Symbol(geometry, Decl(controlFlowOptionalChain.ts, 513, 13)) +>coordinates : Symbol(coordinates, Decl(controlFlowOptionalChain.ts, 515, 17)) +} + diff --git a/tests/baselines/reference/controlFlowOptionalChain.types b/tests/baselines/reference/controlFlowOptionalChain.types index ffec7d1525..13dd6cd5fd 100644 --- a/tests/baselines/reference/controlFlowOptionalChain.types +++ b/tests/baselines/reference/controlFlowOptionalChain.types @@ -1170,6 +1170,163 @@ function f14(o: Thing | null) { } } +function f15(o: Thing | undefined, value: number) { +>f15 : (o: Thing | undefined, value: number) => void +>o : Thing | undefined +>value : number + + if (o?.foo === value) { +>o?.foo === value : boolean +>o?.foo : string | number | undefined +>o : Thing | undefined +>foo : string | number | undefined +>value : number + + o.foo; +>o.foo : number +>o : Thing +>foo : number + } + else { + o.foo; // Error +>o.foo : string | number +>o : Thing | undefined +>foo : string | number + } + if (o?.foo !== value) { +>o?.foo !== value : boolean +>o?.foo : string | number | undefined +>o : Thing | undefined +>foo : string | number | undefined +>value : number + + o.foo; // Error +>o.foo : string | number +>o : Thing | undefined +>foo : string | number + } + else { + o.foo; +>o.foo : number +>o : Thing +>foo : number + } + if (o?.foo == value) { +>o?.foo == value : boolean +>o?.foo : string | number | undefined +>o : Thing | undefined +>foo : string | number | undefined +>value : number + + o.foo; +>o.foo : string | number +>o : Thing +>foo : string | number + } + else { + o.foo; // Error +>o.foo : string | number +>o : Thing | undefined +>foo : string | number + } + if (o?.foo != value) { +>o?.foo != value : boolean +>o?.foo : string | number | undefined +>o : Thing | undefined +>foo : string | number | undefined +>value : number + + o.foo; // Error +>o.foo : string | number +>o : Thing | undefined +>foo : string | number + } + else { + o.foo; +>o.foo : number +>o : Thing +>foo : number + } +} + +function f16(o: Thing | undefined) { +>f16 : (o: Thing | undefined) => void +>o : Thing | undefined + + if (o?.foo === undefined) { +>o?.foo === undefined : boolean +>o?.foo : string | number | undefined +>o : Thing | undefined +>foo : string | number | undefined +>undefined : undefined + + o.foo; // Error +>o.foo : never +>o : Thing | undefined +>foo : never + } + else { + o.foo; +>o.foo : string | number +>o : Thing +>foo : string | number + } + if (o?.foo !== undefined) { +>o?.foo !== undefined : boolean +>o?.foo : string | number | undefined +>o : Thing | undefined +>foo : string | number | undefined +>undefined : undefined + + o.foo; +>o.foo : string | number +>o : Thing +>foo : string | number + } + else { + o.foo; // Error +>o.foo : never +>o : Thing | undefined +>foo : never + } + if (o?.foo == undefined) { +>o?.foo == undefined : boolean +>o?.foo : string | number | undefined +>o : Thing | undefined +>foo : string | number | undefined +>undefined : undefined + + o.foo; // Error +>o.foo : never +>o : Thing | undefined +>foo : never + } + else { + o.foo; +>o.foo : string | number +>o : Thing +>foo : string | number + } + if (o?.foo != undefined) { +>o?.foo != undefined : boolean +>o?.foo : string | number | undefined +>o : Thing | undefined +>foo : string | number | undefined +>undefined : undefined + + o.foo; +>o.foo : string | number +>o : Thing +>foo : string | number + } + else { + o.foo; // Error +>o.foo : never +>o : Thing | undefined +>foo : never + } +} + function f20(o: Thing | undefined) { >f20 : (o: Thing | undefined) => void >o : Thing | undefined @@ -1287,3 +1444,445 @@ function f21(o: Thing | null) { } } +function f22(o: Thing | undefined) { +>f22 : (o: Thing | undefined) => void +>o : Thing | undefined + + if (typeof o?.foo === "number") { +>typeof o?.foo === "number" : boolean +>typeof o?.foo : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>o?.foo : string | number | undefined +>o : Thing | undefined +>foo : string | number | undefined +>"number" : "number" + + o.foo; +>o.foo : number +>o : Thing +>foo : number + } + else { + o.foo; // Error +>o.foo : string +>o : Thing | undefined +>foo : string + } + if (typeof o?.foo !== "number") { +>typeof o?.foo !== "number" : boolean +>typeof o?.foo : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>o?.foo : string | number | undefined +>o : Thing | undefined +>foo : string | number | undefined +>"number" : "number" + + o.foo; // Error +>o.foo : string +>o : Thing | undefined +>foo : string + } + else { + o.foo; +>o.foo : number +>o : Thing +>foo : number + } + if (typeof o?.foo == "number") { +>typeof o?.foo == "number" : boolean +>typeof o?.foo : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>o?.foo : string | number | undefined +>o : Thing | undefined +>foo : string | number | undefined +>"number" : "number" + + o.foo; +>o.foo : number +>o : Thing +>foo : number + } + else { + o.foo; // Error +>o.foo : string +>o : Thing | undefined +>foo : string + } + if (typeof o?.foo != "number") { +>typeof o?.foo != "number" : boolean +>typeof o?.foo : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>o?.foo : string | number | undefined +>o : Thing | undefined +>foo : string | number | undefined +>"number" : "number" + + o.foo; // Error +>o.foo : string +>o : Thing | undefined +>foo : string + } + else { + o.foo; +>o.foo : number +>o : Thing +>foo : number + } +} + +function f23(o: Thing | undefined) { +>f23 : (o: Thing | undefined) => void +>o : Thing | undefined + + if (typeof o?.foo === "undefined") { +>typeof o?.foo === "undefined" : boolean +>typeof o?.foo : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>o?.foo : string | number | undefined +>o : Thing | undefined +>foo : string | number | undefined +>"undefined" : "undefined" + + o.foo; // Error +>o.foo : never +>o : Thing | undefined +>foo : never + } + else { + o.foo; +>o.foo : string | number +>o : Thing +>foo : string | number + } + if (typeof o?.foo !== "undefined") { +>typeof o?.foo !== "undefined" : boolean +>typeof o?.foo : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>o?.foo : string | number | undefined +>o : Thing | undefined +>foo : string | number | undefined +>"undefined" : "undefined" + + o.foo; +>o.foo : string | number +>o : Thing +>foo : string | number + } + else { + o.foo; // Error +>o.foo : never +>o : Thing | undefined +>foo : never + } + if (typeof o?.foo == "undefined") { +>typeof o?.foo == "undefined" : boolean +>typeof o?.foo : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>o?.foo : string | number | undefined +>o : Thing | undefined +>foo : string | number | undefined +>"undefined" : "undefined" + + o.foo; // Error +>o.foo : never +>o : Thing | undefined +>foo : never + } + else { + o.foo; +>o.foo : string | number +>o : Thing +>foo : string | number + } + if (typeof o?.foo != "undefined") { +>typeof o?.foo != "undefined" : boolean +>typeof o?.foo : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>o?.foo : string | number | undefined +>o : Thing | undefined +>foo : string | number | undefined +>"undefined" : "undefined" + + o.foo; +>o.foo : string | number +>o : Thing +>foo : string | number + } + else { + o.foo; // Error +>o.foo : never +>o : Thing | undefined +>foo : never + } +} + +declare function assert(x: unknown): asserts x; +>assert : (x: unknown) => asserts x +>x : unknown + +declare function assertNonNull(x: T): asserts x is NonNullable; +>assertNonNull : (x: T) => asserts x is NonNullable +>x : T + +function f30(o: Thing | undefined) { +>f30 : (o: Thing | undefined) => void +>o : Thing | undefined + + if (!!true) { +>!!true : true +>!true : false +>true : true + + assert(o?.foo); +>assert(o?.foo) : void +>assert : (x: unknown) => asserts x +>o?.foo : string | number | undefined +>o : Thing | undefined +>foo : string | number | undefined + + o.foo; +>o.foo : string | number +>o : Thing +>foo : string | number + } + if (!!true) { +>!!true : true +>!true : false +>true : true + + assert(o?.foo === 42); +>assert(o?.foo === 42) : void +>assert : (x: unknown) => asserts x +>o?.foo === 42 : boolean +>o?.foo : string | number | undefined +>o : Thing | undefined +>foo : string | number | undefined +>42 : 42 + + o.foo; +>o.foo : 42 +>o : Thing +>foo : 42 + } + if (!!true) { +>!!true : true +>!true : false +>true : true + + assert(typeof o?.foo === "number"); +>assert(typeof o?.foo === "number") : void +>assert : (x: unknown) => asserts x +>typeof o?.foo === "number" : boolean +>typeof o?.foo : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>o?.foo : string | number | undefined +>o : Thing | undefined +>foo : string | number | undefined +>"number" : "number" + + o.foo; +>o.foo : number +>o : Thing +>foo : number + } + if (!!true) { +>!!true : true +>!true : false +>true : true + + assertNonNull(o?.foo); +>assertNonNull(o?.foo) : void +>assertNonNull : (x: T) => asserts x is NonNullable +>o?.foo : string | number | undefined +>o : Thing | undefined +>foo : string | number | undefined + + o.foo; +>o.foo : string | number +>o : Thing +>foo : string | number + } +} + +function f40(o: Thing | undefined) { +>f40 : (o: Thing | undefined) => void +>o : Thing | undefined + + switch (o?.foo) { +>o?.foo : string | number | undefined +>o : Thing | undefined +>foo : string | number | undefined + + case "abc": +>"abc" : "abc" + + o.foo; +>o.foo : "abc" +>o : Thing +>foo : "abc" + + break; + case 42: +>42 : 42 + + o.foo; +>o.foo : 42 +>o : Thing +>foo : 42 + + break; + case undefined: +>undefined : undefined + + o.foo; // Error +>o.foo : never +>o : Thing | undefined +>foo : never + + break; + default: + o.foo; // Error +>o.foo : string | number +>o : Thing | undefined +>foo : string | number + + break; + } +} + +function f41(o: Thing | undefined) { +>f41 : (o: Thing | undefined) => void +>o : Thing | undefined + + switch (typeof o?.foo) { +>typeof o?.foo : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>o?.foo : string | number | undefined +>o : Thing | undefined +>foo : string | number | undefined + + case "string": +>"string" : "string" + + o.foo; +>o.foo : string +>o : Thing +>foo : string + + break; + case "number": +>"number" : "number" + + o.foo; +>o.foo : number +>o : Thing +>foo : number + + break; + case "undefined": +>"undefined" : "undefined" + + o.foo; // Error +>o.foo : never +>o : Thing | undefined +>foo : never + + break; + default: + o.foo; // Error +>o.foo : never +>o : Thing | undefined +>foo : never + + break; + } +} + +// Repros from #34570 + +type Shape = +>Shape : Shape + + | { type: 'rectangle', width: number, height: number } +>type : "rectangle" +>width : number +>height : number + + | { type: 'circle', radius: number } +>type : "circle" +>radius : number + +function getArea(shape?: Shape) { +>getArea : (shape?: { type: "rectangle"; width: number; height: number; } | { type: "circle"; radius: number; } | undefined) => number +>shape : { type: "rectangle"; width: number; height: number; } | { type: "circle"; radius: number; } | undefined + + switch (shape?.type) { +>shape?.type : "rectangle" | "circle" | undefined +>shape : { type: "rectangle"; width: number; height: number; } | { type: "circle"; radius: number; } | undefined +>type : "rectangle" | "circle" | undefined + + case 'circle': +>'circle' : "circle" + + return Math.PI * shape.radius ** 2 +>Math.PI * shape.radius ** 2 : number +>Math.PI : number +>Math : Math +>PI : number +>shape.radius ** 2 : number +>shape.radius : number +>shape : { type: "circle"; radius: number; } +>radius : number +>2 : 2 + + case 'rectangle': +>'rectangle' : "rectangle" + + return shape.width * shape.height +>shape.width * shape.height : number +>shape.width : number +>shape : { type: "rectangle"; width: number; height: number; } +>width : number +>shape.height : number +>shape : { type: "rectangle"; width: number; height: number; } +>height : number + + default: + return 0 +>0 : 0 + } +} + +type Feature = { +>Feature : Feature + + id: string; +>id : string + + geometry?: { +>geometry : { type: string; coordinates: number[]; } | undefined + + type: string; +>type : string + + coordinates: number[]; +>coordinates : number[] + + }; +}; + + +function extractCoordinates(f: Feature): number[] { +>extractCoordinates : (f: Feature) => number[] +>f : Feature + + if (f.geometry?.type !== 'test') { +>f.geometry?.type !== 'test' : boolean +>f.geometry?.type : string | undefined +>f.geometry : { type: string; coordinates: number[]; } | undefined +>f : Feature +>geometry : { type: string; coordinates: number[]; } | undefined +>type : string | undefined +>'test' : "test" + + return []; +>[] : never[] + } + return f.geometry.coordinates; +>f.geometry.coordinates : number[] +>f.geometry : { type: string; coordinates: number[]; } +>f : Feature +>geometry : { type: string; coordinates: number[]; } +>coordinates : number[] +} + From 8f15a2e639ccc5182e922b0f9d1c8c698310ab32 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 20 Oct 2019 17:59:21 -0700 Subject: [PATCH 23/47] Fix type inference regression --- src/compiler/checker.ts | 58 ++++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b83fa543a8..02718c6aa9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17354,10 +17354,7 @@ namespace ts { // inferring a type parameter constraint. Instead, make a lower priority inference from // the full source to whatever remains in the target. For example, when inferring from // string to 'string | T', make a lower priority inference of string for T. - const savePriority = priority; - priority |= InferencePriority.NakedTypeVariable; - inferFromTypes(source, target); - priority = savePriority; + inferWithPriority(source, target, InferencePriority.NakedTypeVariable); return; } source = getUnionType(sources); @@ -17459,10 +17456,7 @@ namespace ts { else if ((isLiteralType(source) || source.flags & TypeFlags.String) && target.flags & TypeFlags.Index) { const empty = createEmptyObjectTypeFromStringLiteral(source); contravariant = !contravariant; - const savePriority = priority; - priority |= InferencePriority.LiteralKeyof; - inferFromTypes(empty, (target as IndexType).type); - priority = savePriority; + inferWithPriority(empty, (target as IndexType).type, InferencePriority.LiteralKeyof); contravariant = !contravariant; } else if (source.flags & TypeFlags.IndexedAccess && target.flags & TypeFlags.IndexedAccess) { @@ -17514,6 +17508,13 @@ namespace ts { } } + function inferWithPriority(source: Type, target: Type, newPriority: InferencePriority) { + const savePriority = priority; + priority |= newPriority; + inferFromTypes(source, target); + priority = savePriority; + } + function invokeOnce(source: Type, target: Type, action: (source: Type, target: Type) => void) { const key = source.id + "," + target.id; const status = visited && visited.get(key); @@ -17581,6 +17582,18 @@ namespace ts { return undefined; } + function getSingleTypeVariableFromIntersectionTypes(types: Type[]) { + let typeVariable: Type | undefined; + for (const type of types) { + const t = type.flags & TypeFlags.Intersection && find((type).types, t => !!getInferenceInfoForType(t)); + if (!t || typeVariable && t !== typeVariable) { + return undefined; + } + typeVariable = t; + } + return typeVariable; + } + function inferToMultipleTypes(source: Type, targets: Type[], targetFlags: TypeFlags) { let typeVariableCount = 0; if (targetFlags & TypeFlags.Union) { @@ -17608,6 +17621,16 @@ namespace ts { } } } + if (typeVariableCount === 0) { + // If every target is an intersection of types containing a single naked type variable, + // make a lower priority inference to that type variable. This handles inferring from + // 'A | B' to 'T & (X | Y)' where we want to infer 'A | B' for T. + const intersectionTypeVariable = getSingleTypeVariableFromIntersectionTypes(targets); + if (intersectionTypeVariable) { + inferWithPriority(source, intersectionTypeVariable, InferencePriority.NakedTypeVariable); + } + return; + } // If the target has a single naked type variable and no inference circularities were // encountered above (meaning we explored the types fully), create a union of the source // types from which no inferences have been made so far and infer from that union to the @@ -17638,14 +17661,11 @@ namespace ts { // we want to infer string for T, not Promise | string. For intersection types // we only infer to single naked type variables. if (targetFlags & TypeFlags.Intersection ? typeVariableCount === 1 : typeVariableCount > 0) { - const savePriority = priority; - priority |= InferencePriority.NakedTypeVariable; for (const t of targets) { if (getInferenceInfoForType(t)) { - inferFromTypes(source, t); + inferWithPriority(source, t, InferencePriority.NakedTypeVariable); } } - priority = savePriority; } } @@ -17666,14 +17686,13 @@ namespace ts { if (inference && !inference.isFixed) { const inferredType = inferTypeForHomomorphicMappedType(source, target, constraintType); if (inferredType) { - const savePriority = priority; // We assign a lower priority to inferences made from types containing non-inferrable // types because we may only have a partial result (i.e. we may have failed to make // reverse inferences for some properties). - priority |= getObjectFlags(source) & ObjectFlags.NonInferrableType ? - InferencePriority.PartialHomomorphicMappedType : InferencePriority.HomomorphicMappedType; - inferFromTypes(inferredType, inference.typeParameter); - priority = savePriority; + inferWithPriority(inferredType, inference.typeParameter, + getObjectFlags(source) & ObjectFlags.NonInferrableType ? + InferencePriority.PartialHomomorphicMappedType : + InferencePriority.HomomorphicMappedType); } } return true; @@ -17681,10 +17700,7 @@ namespace ts { if (constraintType.flags & TypeFlags.TypeParameter) { // We're inferring from some source type S to a mapped type { [P in K]: X }, where K is a type // parameter. First infer from 'keyof S' to K. - const savePriority = priority; - priority |= InferencePriority.MappedTypeConstraint; - inferFromTypes(getIndexType(source), constraintType); - priority = savePriority; + inferWithPriority(getIndexType(source), constraintType, InferencePriority.MappedTypeConstraint); // If K is constrained to a type C, also infer to C. Thus, for a mapped type { [P in K]: X }, // where K extends keyof T, we make the same inferences as for a homomorphic mapped type // { [P in keyof T]: X }. This enables us to make meaningful inferences when the target is a From 56520da6f0af3ed013f8557207cb3c04d2a1158e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 20 Oct 2019 18:00:08 -0700 Subject: [PATCH 24/47] Add regression tests --- .../unionAndIntersectionInference3.ts | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference3.ts b/tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference3.ts index d64b197369..12a5e8b17c 100644 --- a/tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference3.ts +++ b/tests/cases/conformance/types/typeRelationships/typeInference/unionAndIntersectionInference3.ts @@ -54,3 +54,37 @@ let y1 = foo1(sx); // string let x2 = foo2(sa); // unknown let y2 = foo2(sx); // { extra: number } + +// Repro from #33490 + +declare class Component

{ props: P } + +export type ComponentClass

= new (props: P) => Component

; +export type FunctionComponent

= (props: P) => null; + +export type ComponentType

= FunctionComponent

| ComponentClass

; + +export interface RouteComponentProps { route: string } + +declare function withRouter< + P extends RouteComponentProps, + C extends ComponentType

+>( + component: C & ComponentType

+): ComponentClass>; + +interface Props extends RouteComponentProps { username: string } + +declare const MyComponent: ComponentType; + +withRouter(MyComponent); + +// Repro from #33490 + +type AB = { a: T } | { b: T }; + +// T & AB normalizes to T & { a: U } | T & { b: U } below +declare function foo(obj: T & AB): [T, U]; +declare let ab: AB; + +let z = foo(ab); // [AB, string] From 82019d616dc8a56290d1b22b5d32cd12598c214e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sun, 20 Oct 2019 18:01:41 -0700 Subject: [PATCH 25/47] Accept new baselines --- .../unionAndIntersectionInference3.js | 37 ++++++- .../unionAndIntersectionInference3.symbols | 104 ++++++++++++++++++ .../unionAndIntersectionInference3.types | 64 +++++++++++ 3 files changed, 204 insertions(+), 1 deletion(-) diff --git a/tests/baselines/reference/unionAndIntersectionInference3.js b/tests/baselines/reference/unionAndIntersectionInference3.js index 415d98220a..12ce5a9a19 100644 --- a/tests/baselines/reference/unionAndIntersectionInference3.js +++ b/tests/baselines/reference/unionAndIntersectionInference3.js @@ -52,10 +52,43 @@ let y1 = foo1(sx); // string let x2 = foo2(sa); // unknown let y2 = foo2(sx); // { extra: number } + +// Repro from #33490 + +declare class Component

{ props: P } + +export type ComponentClass

= new (props: P) => Component

; +export type FunctionComponent

= (props: P) => null; + +export type ComponentType

= FunctionComponent

| ComponentClass

; + +export interface RouteComponentProps { route: string } + +declare function withRouter< + P extends RouteComponentProps, + C extends ComponentType

+>( + component: C & ComponentType

+): ComponentClass>; + +interface Props extends RouteComponentProps { username: string } + +declare const MyComponent: ComponentType; + +withRouter(MyComponent); + +// Repro from #33490 + +type AB = { a: T } | { b: T }; + +// T & AB normalizes to T & { a: U } | T & { b: U } below +declare function foo(obj: T & AB): [T, U]; +declare let ab: AB; + +let z = foo(ab); // [AB, string] //// [unionAndIntersectionInference3.js] -"use strict"; // Repro from #30720 concatMaybe([1, 2, 3], 4); // Repros from #32247 @@ -70,3 +103,5 @@ let x1 = foo1(sa); // string let y1 = foo1(sx); // string let x2 = foo2(sa); // unknown let y2 = foo2(sx); // { extra: number } +withRouter(MyComponent); +let z = foo(ab); // [AB, string] diff --git a/tests/baselines/reference/unionAndIntersectionInference3.symbols b/tests/baselines/reference/unionAndIntersectionInference3.symbols index 1d237951b4..43079f9e8a 100644 --- a/tests/baselines/reference/unionAndIntersectionInference3.symbols +++ b/tests/baselines/reference/unionAndIntersectionInference3.symbols @@ -205,3 +205,107 @@ let y2 = foo2(sx); // { extra: number } >foo2 : Symbol(foo2, Decl(unionAndIntersectionInference3.ts, 42, 57)) >sx : Symbol(sx, Decl(unionAndIntersectionInference3.ts, 46, 11)) +// Repro from #33490 + +declare class Component

{ props: P } +>Component : Symbol(Component, Decl(unionAndIntersectionInference3.ts, 52, 18)) +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 56, 24)) +>props : Symbol(Component.props, Decl(unionAndIntersectionInference3.ts, 56, 28)) +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 56, 24)) + +export type ComponentClass

= new (props: P) => Component

; +>ComponentClass : Symbol(ComponentClass, Decl(unionAndIntersectionInference3.ts, 56, 39)) +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 58, 27)) +>props : Symbol(props, Decl(unionAndIntersectionInference3.ts, 58, 37)) +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 58, 27)) +>Component : Symbol(Component, Decl(unionAndIntersectionInference3.ts, 52, 18)) +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 58, 27)) + +export type FunctionComponent

= (props: P) => null; +>FunctionComponent : Symbol(FunctionComponent, Decl(unionAndIntersectionInference3.ts, 58, 63)) +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 59, 30)) +>props : Symbol(props, Decl(unionAndIntersectionInference3.ts, 59, 36)) +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 59, 30)) + +export type ComponentType

= FunctionComponent

| ComponentClass

; +>ComponentType : Symbol(ComponentType, Decl(unionAndIntersectionInference3.ts, 59, 54)) +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 61, 26)) +>FunctionComponent : Symbol(FunctionComponent, Decl(unionAndIntersectionInference3.ts, 58, 63)) +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 61, 26)) +>ComponentClass : Symbol(ComponentClass, Decl(unionAndIntersectionInference3.ts, 56, 39)) +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 61, 26)) + +export interface RouteComponentProps { route: string } +>RouteComponentProps : Symbol(RouteComponentProps, Decl(unionAndIntersectionInference3.ts, 61, 72)) +>route : Symbol(RouteComponentProps.route, Decl(unionAndIntersectionInference3.ts, 63, 38)) + +declare function withRouter< +>withRouter : Symbol(withRouter, Decl(unionAndIntersectionInference3.ts, 63, 54)) + + P extends RouteComponentProps, +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 65, 28)) +>RouteComponentProps : Symbol(RouteComponentProps, Decl(unionAndIntersectionInference3.ts, 61, 72)) + + C extends ComponentType

+>C : Symbol(C, Decl(unionAndIntersectionInference3.ts, 66, 32)) +>ComponentType : Symbol(ComponentType, Decl(unionAndIntersectionInference3.ts, 59, 54)) +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 65, 28)) + +>( + component: C & ComponentType

+>component : Symbol(component, Decl(unionAndIntersectionInference3.ts, 68, 2)) +>C : Symbol(C, Decl(unionAndIntersectionInference3.ts, 66, 32)) +>ComponentType : Symbol(ComponentType, Decl(unionAndIntersectionInference3.ts, 59, 54)) +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 65, 28)) + +): ComponentClass>; +>ComponentClass : Symbol(ComponentClass, Decl(unionAndIntersectionInference3.ts, 56, 39)) +>Omit : Symbol(Omit, Decl(lib.es5.d.ts, --, --)) +>P : Symbol(P, Decl(unionAndIntersectionInference3.ts, 65, 28)) +>RouteComponentProps : Symbol(RouteComponentProps, Decl(unionAndIntersectionInference3.ts, 61, 72)) + +interface Props extends RouteComponentProps { username: string } +>Props : Symbol(Props, Decl(unionAndIntersectionInference3.ts, 70, 54)) +>RouteComponentProps : Symbol(RouteComponentProps, Decl(unionAndIntersectionInference3.ts, 61, 72)) +>username : Symbol(Props.username, Decl(unionAndIntersectionInference3.ts, 72, 45)) + +declare const MyComponent: ComponentType; +>MyComponent : Symbol(MyComponent, Decl(unionAndIntersectionInference3.ts, 74, 13)) +>ComponentType : Symbol(ComponentType, Decl(unionAndIntersectionInference3.ts, 59, 54)) +>Props : Symbol(Props, Decl(unionAndIntersectionInference3.ts, 70, 54)) + +withRouter(MyComponent); +>withRouter : Symbol(withRouter, Decl(unionAndIntersectionInference3.ts, 63, 54)) +>MyComponent : Symbol(MyComponent, Decl(unionAndIntersectionInference3.ts, 74, 13)) + +// Repro from #33490 + +type AB = { a: T } | { b: T }; +>AB : Symbol(AB, Decl(unionAndIntersectionInference3.ts, 76, 24)) +>T : Symbol(T, Decl(unionAndIntersectionInference3.ts, 80, 8)) +>a : Symbol(a, Decl(unionAndIntersectionInference3.ts, 80, 14)) +>T : Symbol(T, Decl(unionAndIntersectionInference3.ts, 80, 8)) +>b : Symbol(b, Decl(unionAndIntersectionInference3.ts, 80, 25)) +>T : Symbol(T, Decl(unionAndIntersectionInference3.ts, 80, 8)) + +// T & AB normalizes to T & { a: U } | T & { b: U } below +declare function foo(obj: T & AB): [T, U]; +>foo : Symbol(foo, Decl(unionAndIntersectionInference3.ts, 80, 33)) +>T : Symbol(T, Decl(unionAndIntersectionInference3.ts, 83, 21)) +>U : Symbol(U, Decl(unionAndIntersectionInference3.ts, 83, 23)) +>obj : Symbol(obj, Decl(unionAndIntersectionInference3.ts, 83, 27)) +>T : Symbol(T, Decl(unionAndIntersectionInference3.ts, 83, 21)) +>AB : Symbol(AB, Decl(unionAndIntersectionInference3.ts, 76, 24)) +>U : Symbol(U, Decl(unionAndIntersectionInference3.ts, 83, 23)) +>T : Symbol(T, Decl(unionAndIntersectionInference3.ts, 83, 21)) +>U : Symbol(U, Decl(unionAndIntersectionInference3.ts, 83, 23)) + +declare let ab: AB; +>ab : Symbol(ab, Decl(unionAndIntersectionInference3.ts, 84, 11)) +>AB : Symbol(AB, Decl(unionAndIntersectionInference3.ts, 76, 24)) + +let z = foo(ab); // [AB, string] +>z : Symbol(z, Decl(unionAndIntersectionInference3.ts, 86, 3)) +>foo : Symbol(foo, Decl(unionAndIntersectionInference3.ts, 80, 33)) +>ab : Symbol(ab, Decl(unionAndIntersectionInference3.ts, 84, 11)) + diff --git a/tests/baselines/reference/unionAndIntersectionInference3.types b/tests/baselines/reference/unionAndIntersectionInference3.types index fb50747432..05cb9368f8 100644 --- a/tests/baselines/reference/unionAndIntersectionInference3.types +++ b/tests/baselines/reference/unionAndIntersectionInference3.types @@ -135,3 +135,67 @@ let y2 = foo2(sx); // { extra: number } >foo2 : (obj: string[] & T) => T >sx : string[] & { extra: number; } +// Repro from #33490 + +declare class Component

{ props: P } +>Component : Component

+>props : P + +export type ComponentClass

= new (props: P) => Component

; +>ComponentClass : ComponentClass

+>props : P + +export type FunctionComponent

= (props: P) => null; +>FunctionComponent : FunctionComponent

+>props : P +>null : null + +export type ComponentType

= FunctionComponent

| ComponentClass

; +>ComponentType : ComponentType

+ +export interface RouteComponentProps { route: string } +>route : string + +declare function withRouter< +>withRouter :

>(component: (C & FunctionComponent

) | (C & ComponentClass

)) => ComponentClass>> + + P extends RouteComponentProps, + C extends ComponentType

+>( + component: C & ComponentType

+>component : (C & FunctionComponent

) | (C & ComponentClass

) + +): ComponentClass>; + +interface Props extends RouteComponentProps { username: string } +>username : string + +declare const MyComponent: ComponentType; +>MyComponent : ComponentType + +withRouter(MyComponent); +>withRouter(MyComponent) : ComponentClass> +>withRouter :

>(component: (C & FunctionComponent

) | (C & ComponentClass

)) => ComponentClass>> +>MyComponent : ComponentType + +// Repro from #33490 + +type AB = { a: T } | { b: T }; +>AB : AB +>a : T +>b : T + +// T & AB normalizes to T & { a: U } | T & { b: U } below +declare function foo(obj: T & AB): [T, U]; +>foo : (obj: (T & { a: U; }) | (T & { b: U; })) => [T, U] +>obj : (T & { a: U; }) | (T & { b: U; }) + +declare let ab: AB; +>ab : AB + +let z = foo(ab); // [AB, string] +>z : [AB, string] +>foo(ab) : [AB, string] +>foo : (obj: (T & { a: U; }) | (T & { b: U; })) => [T, U] +>ab : AB + From 1d3ecc061088e67777ccfd22c89b77ef940f1343 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 21 Oct 2019 09:56:02 -0700 Subject: [PATCH 26/47] Ensure export= symbol from JavaScript always has a valueDeclaration (#34553) --- src/compiler/binder.ts | 3 ++- ...avascriptImportDefaultBadExport.errors.txt | 16 ++++++++++++++++ .../javascriptImportDefaultBadExport.symbols | 17 +++++++++++++++++ .../javascriptImportDefaultBadExport.types | 19 +++++++++++++++++++ .../javascriptImportDefaultBadExport.ts | 12 ++++++++++++ 5 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/javascriptImportDefaultBadExport.errors.txt create mode 100644 tests/baselines/reference/javascriptImportDefaultBadExport.symbols create mode 100644 tests/baselines/reference/javascriptImportDefaultBadExport.types create mode 100644 tests/cases/compiler/javascriptImportDefaultBadExport.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 9646aabe0a..e1fdfc258e 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2690,7 +2690,8 @@ namespace ts { const flags = exportAssignmentIsAlias(node) ? SymbolFlags.Alias // An export= with an EntityNameExpression or a ClassExpression exports all meanings of that identifier or class : SymbolFlags.Property | SymbolFlags.ExportValue | SymbolFlags.ValueModule; - declareSymbol(file.symbol.exports!, file.symbol, node, flags | SymbolFlags.Assignment, SymbolFlags.None); + const symbol = declareSymbol(file.symbol.exports!, file.symbol, node, flags | SymbolFlags.Assignment, SymbolFlags.None); + setValueDeclaration(symbol, node); } function bindThisPropertyAssignment(node: BindablePropertyAssignmentExpression | PropertyAccessExpression | LiteralLikeElementAccessExpression) { diff --git a/tests/baselines/reference/javascriptImportDefaultBadExport.errors.txt b/tests/baselines/reference/javascriptImportDefaultBadExport.errors.txt new file mode 100644 index 0000000000..71ff85e215 --- /dev/null +++ b/tests/baselines/reference/javascriptImportDefaultBadExport.errors.txt @@ -0,0 +1,16 @@ +/b.js(1,8): error TS1259: Module '"/a"' can only be default-imported using the 'esModuleInterop' flag + + +==== /a.js (0 errors) ==== + // https://github.com/microsoft/TypeScript/issues/34481 + + + const alias = {}; + module.exports = alias; + +==== /b.js (1 errors) ==== + import a from "./a"; + ~ +!!! error TS1259: Module '"/a"' can only be default-imported using the 'esModuleInterop' flag +!!! related TS2594 /a.js:5:1: This module is declared with using 'export =', and can only be used with a default import when using the 'esModuleInterop' flag. + \ No newline at end of file diff --git a/tests/baselines/reference/javascriptImportDefaultBadExport.symbols b/tests/baselines/reference/javascriptImportDefaultBadExport.symbols new file mode 100644 index 0000000000..b5506996af --- /dev/null +++ b/tests/baselines/reference/javascriptImportDefaultBadExport.symbols @@ -0,0 +1,17 @@ +=== /a.js === +// https://github.com/microsoft/TypeScript/issues/34481 + + +const alias = {}; +>alias : Symbol(alias, Decl(a.js, 3, 5)) + +module.exports = alias; +>module.exports : Symbol("/a", Decl(a.js, 0, 0)) +>module : Symbol(export=, Decl(a.js, 3, 17)) +>exports : Symbol(export=, Decl(a.js, 3, 17)) +>alias : Symbol(alias, Decl(a.js, 3, 5)) + +=== /b.js === +import a from "./a"; +>a : Symbol(a, Decl(b.js, 0, 6)) + diff --git a/tests/baselines/reference/javascriptImportDefaultBadExport.types b/tests/baselines/reference/javascriptImportDefaultBadExport.types new file mode 100644 index 0000000000..81d68cbdf9 --- /dev/null +++ b/tests/baselines/reference/javascriptImportDefaultBadExport.types @@ -0,0 +1,19 @@ +=== /a.js === +// https://github.com/microsoft/TypeScript/issues/34481 + + +const alias = {}; +>alias : {} +>{} : {} + +module.exports = alias; +>module.exports = alias : {} +>module.exports : {} +>module : { "/a": {}; } +>exports : {} +>alias : {} + +=== /b.js === +import a from "./a"; +>a : any + diff --git a/tests/cases/compiler/javascriptImportDefaultBadExport.ts b/tests/cases/compiler/javascriptImportDefaultBadExport.ts new file mode 100644 index 0000000000..c1f2455e11 --- /dev/null +++ b/tests/cases/compiler/javascriptImportDefaultBadExport.ts @@ -0,0 +1,12 @@ +// https://github.com/microsoft/TypeScript/issues/34481 + +// @allowJs: true +// @checkJs: true +// @noEmit: true + +// @Filename: /a.js +const alias = {}; +module.exports = alias; + +// @Filename: /b.js +import a from "./a"; From b167012561586a405407d6949738a5ea4cc36e7e Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Mon, 21 Oct 2019 10:46:41 -0700 Subject: [PATCH 27/47] Extend the correct tsconfig.json. --- src/tsc/tsconfig.release.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tsc/tsconfig.release.json b/src/tsc/tsconfig.release.json index 2d2d28ac66..5d6e1687c2 100644 --- a/src/tsc/tsconfig.release.json +++ b/src/tsc/tsconfig.release.json @@ -1,5 +1,5 @@ { - "extends": "../tsconfig.json", + "extends": "./tsconfig.json", "compilerOptions": { "outFile": "../../built/local/tsc.release.js", "stripInternal": true, @@ -8,7 +8,7 @@ "declarationMap": false, "sourceMap": false, "composite": false, - "incremental": true + "incremental": true }, "references": [ { "path": "../compiler/tsconfig.release.json", "prepend": true } From 6429e4cd36f136ac11cda325ecdfdd8ea4d0dc9e Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Mon, 21 Oct 2019 13:32:42 -0700 Subject: [PATCH 28/47] Fix undefined `this` --- src/compiler/sys.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 9de6193d73..772eb148fc 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -535,8 +535,8 @@ namespace ts { data, writeBom, (p, d, w) => originalWriteFile.call(sys, p, d, w), - sys.createDirectory, - sys.directoryExists); + p => sys.createDirectory.call(sys, p), + p => sys.directoryExists.call(sys, p)); } /*@internal*/ From ca31f008a83801d60d564fd4f6194ac1aa352302 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Mon, 21 Oct 2019 14:01:12 -0700 Subject: [PATCH 29/47] Address more potential `this` issues --- src/compiler/program.ts | 6 +++--- src/compiler/sys.ts | 6 +++--- src/compiler/utilities.ts | 4 ++-- src/compiler/watch.ts | 8 +++++++- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 0be96b7d55..0b22d78f35 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -114,9 +114,9 @@ namespace ts { fileName, data, writeByteOrderMark, - writeFileWorker, - compilerHost.createDirectory || system.createDirectory, - directoryExists); + (f, d, w) => writeFileWorker(f, d, w), + p => (compilerHost.createDirectory || system.createDirectory)(p), + p => directoryExists(p)); performance.mark("afterIOWrite"); performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 772eb148fc..933cea7b72 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -533,10 +533,10 @@ namespace ts { writeFileEnsuringDirectories( path, data, - writeBom, + !!writeBom, (p, d, w) => originalWriteFile.call(sys, p, d, w), - p => sys.createDirectory.call(sys, p), - p => sys.directoryExists.call(sys, p)); + p => sys.createDirectory(p), + p => sys.directoryExists(p)); } /*@internal*/ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 8f7602cbe2..ac29da9389 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3714,8 +3714,8 @@ namespace ts { export function writeFileEnsuringDirectories( path: string, data: string, - writeByteOrderMark: boolean | undefined, - writeFile: (path: string, data: string, writeByteOrderMark?: boolean) => void, + writeByteOrderMark: boolean, + writeFile: (path: string, data: string, writeByteOrderMark: boolean) => void, createDirectory: (path: string) => void, directoryExists: (path: string) => boolean): void { diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index cc6bd0bcce..eef4a03c07 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -303,7 +303,13 @@ namespace ts { // NOTE: If patchWriteFileEnsuringDirectory has been called, // the host.writeFile will do its own directory creation and // the ensureDirectoriesExist call will always be redundant. - writeFileEnsuringDirectories(fileName, text, writeByteOrderMark, host.writeFile!, host.createDirectory!, host.directoryExists!); + writeFileEnsuringDirectories( + fileName, + text, + writeByteOrderMark, + (f, w, d) => host.writeFile!(f, w, d), + p => host.createDirectory!(p), + p => host.directoryExists!(p)); performance.mark("afterIOWrite"); performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); From 1b0fca5df5dcc757b995341b3fe479e182c3e265 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Mon, 21 Oct 2019 14:51:21 -0700 Subject: [PATCH 30/47] Update version to 3.8. --- package.json | 2 +- src/compiler/core.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 1e314c216c..bbba0c28e9 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "typescript", "author": "Microsoft Corp.", "homepage": "https://www.typescriptlang.org/", - "version": "3.7.0", + "version": "3.8.0", "license": "Apache-2.0", "description": "TypeScript is a language for application scale JavaScript development", "keywords": [ diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 4941257cab..d03b7e1c85 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1,7 +1,7 @@ namespace ts { // WARNING: The script `configureNightly.ts` uses a regexp to parse out these values. // If changing the text in this section, be sure to test `configureNightly` too. - export const versionMajorMinor = "3.7"; + export const versionMajorMinor = "3.8"; /** The version of the TypeScript compiler release */ export const version = `${versionMajorMinor}.0-dev`; } From def1e7163daf5af3544aaaf70c3667a8c9404a2b Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Mon, 21 Oct 2019 15:11:16 -0700 Subject: [PATCH 31/47] Accepted baselines. --- tests/baselines/reference/api/tsserverlibrary.d.ts | 2 +- tests/baselines/reference/api/typescript.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index b02074e75e..c43f28c6e9 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -14,7 +14,7 @@ and limitations under the License. ***************************************************************************** */ declare namespace ts { - const versionMajorMinor = "3.7"; + const versionMajorMinor = "3.8"; /** The version of the TypeScript compiler release */ const version: string; } diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 7361ccb676..e84b948c93 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -14,7 +14,7 @@ and limitations under the License. ***************************************************************************** */ declare namespace ts { - const versionMajorMinor = "3.7"; + const versionMajorMinor = "3.8"; /** The version of the TypeScript compiler release */ const version: string; } From af2f46e8996476053e3e6d0c64d2f9d972191564 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Mon, 21 Oct 2019 16:22:10 -0700 Subject: [PATCH 32/47] Use longer lambda parameter names --- src/compiler/program.ts | 6 +++--- src/compiler/sys.ts | 6 +++--- src/compiler/watch.ts | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 0b22d78f35..59b57bfa99 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -114,9 +114,9 @@ namespace ts { fileName, data, writeByteOrderMark, - (f, d, w) => writeFileWorker(f, d, w), - p => (compilerHost.createDirectory || system.createDirectory)(p), - p => directoryExists(p)); + (path, data, writeByteOrderMark) => writeFileWorker(path, data, writeByteOrderMark), + path => (compilerHost.createDirectory || system.createDirectory)(path), + path => directoryExists(path)); performance.mark("afterIOWrite"); performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 933cea7b72..31b422a12b 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -534,9 +534,9 @@ namespace ts { path, data, !!writeBom, - (p, d, w) => originalWriteFile.call(sys, p, d, w), - p => sys.createDirectory(p), - p => sys.directoryExists(p)); + (path, data, writeByteOrderMark) => originalWriteFile.call(sys, path, data, writeByteOrderMark), + path => sys.createDirectory(path), + path => sys.directoryExists(path)); } /*@internal*/ diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index eef4a03c07..5e8991dbb6 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -307,9 +307,9 @@ namespace ts { fileName, text, writeByteOrderMark, - (f, w, d) => host.writeFile!(f, w, d), - p => host.createDirectory!(p), - p => host.directoryExists!(p)); + (path, data, writeByteOrderMark) => host.writeFile!(path, data, writeByteOrderMark), + path => host.createDirectory!(path), + path => host.directoryExists!(path)); performance.mark("afterIOWrite"); performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); From 7862e58354888d5ccf9c378ac74863d5febabc21 Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Mon, 21 Oct 2019 17:41:06 -0700 Subject: [PATCH 33/47] Handle undefined from getPropertyNameForPropertyNameNode ...which can be returned when the property name is computed. Part of #34404 --- src/services/navigationBar.ts | 3 +- .../navigationBarComputedPropertyName.ts | 57 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 tests/cases/fourslash/navigationBarComputedPropertyName.ts diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index 03b9d6216c..49310ef827 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -648,7 +648,8 @@ namespace ts.NavigationBar { const declName = getNameOfDeclaration(node); if (declName && isPropertyName(declName)) { - return unescapeLeadingUnderscores(getPropertyNameForPropertyNameNode(declName)!); // TODO: GH#18217 + const propertyName = getPropertyNameForPropertyNameNode(declName); + return propertyName && unescapeLeadingUnderscores(propertyName); } switch (node.kind) { case SyntaxKind.FunctionExpression: diff --git a/tests/cases/fourslash/navigationBarComputedPropertyName.ts b/tests/cases/fourslash/navigationBarComputedPropertyName.ts new file mode 100644 index 0000000000..fc6fb808f7 --- /dev/null +++ b/tests/cases/fourslash/navigationBarComputedPropertyName.ts @@ -0,0 +1,57 @@ +/// + +////function F(key, value) { +//// return { +//// [key]: value, +//// "prop": true +//// } +////} + +verify.navigationTree({ + "text": "", + "kind": "script", + "childItems": [ + { + "text": "F", + "kind": "function", + "childItems": [ + { + "text": "[key]", + "kind": "property" + }, + { + "text": "\"prop\"", + "kind": "property" + } + ] + } + ] +}); + +verify.navigationBar([ + { + "text": "", + "kind": "script", + "childItems": [ + { + "text": "F", + "kind": "function" + } + ] + }, + { + "text": "F", + "kind": "function", + "childItems": [ + { + "text": "[key]", + "kind": "property" + }, + { + "text": "\"prop\"", + "kind": "property" + } + ], + "indent": 1 + } +]); From 73a206b47f02a41650851ae616cda52ef375fa3d Mon Sep 17 00:00:00 2001 From: Orta Therox Date: Tue, 22 Oct 2019 13:43:52 -0400 Subject: [PATCH 34/47] Update the README links --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 459cebe95b..a422999ce1 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,15 @@ # TypeScript -[![Join the chat at https://gitter.im/microsoft/TypeScript](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/microsoft/TypeScript?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Build Status](https://travis-ci.org/microsoft/TypeScript.svg?branch=master)](https://travis-ci.org/microsoft/TypeScript) [![VSTS Build Status](https://dev.azure.com/typescript/TypeScript/_apis/build/status/Typescript/node10)](https://dev.azure.com/typescript/TypeScript/_build/latest?definitionId=4&view=logs) [![npm version](https://badge.fury.io/js/typescript.svg)](https://www.npmjs.com/package/typescript) [![Downloads](https://img.shields.io/npm/dm/typescript.svg)](https://www.npmjs.com/package/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/). + ## Installing For the latest stable version: @@ -31,6 +30,7 @@ There are many ways to [contribute](https://github.com/microsoft/TypeScript/blob * [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/master/CONTRIBUTING.md). * Read the language specification ([docx](https://github.com/microsoft/TypeScript/blob/master/doc/TypeScript%20Language%20Specification.docx?raw=true), From 1cbbe288ac6e67f1b50e0a239ae36fe0105d4458 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Tue, 22 Oct 2019 11:28:28 -0700 Subject: [PATCH 35/47] Treat any mix of element/prop access as declaration in JS (#34649) Fixes #34642 but, notably, doesn't actually make the assignment into a working declaration. It just fixes the crash. --- src/compiler/utilities.ts | 20 +++++++++++-------- ...pertyElementAccessAssignmentDeclaration.js | 10 ++++++++++ ...ElementAccessAssignmentDeclaration.symbols | 8 ++++++++ ...tyElementAccessAssignmentDeclaration.types | 17 ++++++++++++++++ ...pertyElementAccessAssignmentDeclaration.ts | 3 +++ 5 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 tests/baselines/reference/mixedPropertyElementAccessAssignmentDeclaration.js create mode 100644 tests/baselines/reference/mixedPropertyElementAccessAssignmentDeclaration.symbols create mode 100644 tests/baselines/reference/mixedPropertyElementAccessAssignmentDeclaration.types create mode 100644 tests/cases/conformance/salsa/mixedPropertyElementAccessAssignmentDeclaration.ts diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 12c83b7169..e86f79b360 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2061,26 +2061,30 @@ namespace ts { isBindableStaticNameExpression(expr.arguments[0], /*excludeThisKeyword*/ true); } - export function isBindableStaticElementAccessExpression(node: Node, excludeThisKeyword?: boolean): node is BindableStaticElementAccessExpression { - return isLiteralLikeElementAccess(node) - && ((!excludeThisKeyword && node.expression.kind === SyntaxKind.ThisKeyword) || - isEntityNameExpression(node.expression) || - isBindableStaticElementAccessExpression(node.expression, /*excludeThisKeyword*/ true)); - } - + /** x.y OR x[0] */ export function isLiteralLikeAccess(node: Node): node is LiteralLikeElementAccessExpression | PropertyAccessExpression { return isPropertyAccessExpression(node) || isLiteralLikeElementAccess(node); } + /** x[0] OR x['a'] OR x[Symbol.y] */ export function isLiteralLikeElementAccess(node: Node): node is LiteralLikeElementAccessExpression { return isElementAccessExpression(node) && ( isStringOrNumericLiteralLike(node.argumentExpression) || isWellKnownSymbolSyntactically(node.argumentExpression)); } + /** Any series of property and element accesses. */ export function isBindableStaticAccessExpression(node: Node, excludeThisKeyword?: boolean): node is BindableStaticAccessExpression { return isPropertyAccessExpression(node) && (!excludeThisKeyword && node.expression.kind === SyntaxKind.ThisKeyword || isBindableStaticNameExpression(node.expression, /*excludeThisKeyword*/ true)) - || isBindableStaticElementAccessExpression(node, excludeThisKeyword); + || isBindableStaticElementAccessExpression(node, excludeThisKeyword); + } + + /** Any series of property and element accesses, ending in a literal element access */ + export function isBindableStaticElementAccessExpression(node: Node, excludeThisKeyword?: boolean): node is BindableStaticElementAccessExpression { + return isLiteralLikeElementAccess(node) + && ((!excludeThisKeyword && node.expression.kind === SyntaxKind.ThisKeyword) || + isEntityNameExpression(node.expression) || + isBindableStaticAccessExpression(node.expression, /*excludeThisKeyword*/ true)); } export function isBindableStaticNameExpression(node: Node, excludeThisKeyword?: boolean): node is BindableStaticNameExpression { diff --git a/tests/baselines/reference/mixedPropertyElementAccessAssignmentDeclaration.js b/tests/baselines/reference/mixedPropertyElementAccessAssignmentDeclaration.js new file mode 100644 index 0000000000..ee5b3d9e89 --- /dev/null +++ b/tests/baselines/reference/mixedPropertyElementAccessAssignmentDeclaration.js @@ -0,0 +1,10 @@ +//// [mixedPropertyElementAccessAssignmentDeclaration.ts] +// Should not crash: #34642 +var arr = []; +arr[0].prop[2] = {}; + + +//// [mixedPropertyElementAccessAssignmentDeclaration.js] +// Should not crash: #34642 +var arr = []; +arr[0].prop[2] = {}; diff --git a/tests/baselines/reference/mixedPropertyElementAccessAssignmentDeclaration.symbols b/tests/baselines/reference/mixedPropertyElementAccessAssignmentDeclaration.symbols new file mode 100644 index 0000000000..d414e979f1 --- /dev/null +++ b/tests/baselines/reference/mixedPropertyElementAccessAssignmentDeclaration.symbols @@ -0,0 +1,8 @@ +=== tests/cases/conformance/salsa/mixedPropertyElementAccessAssignmentDeclaration.ts === +// Should not crash: #34642 +var arr = []; +>arr : Symbol(arr, Decl(mixedPropertyElementAccessAssignmentDeclaration.ts, 1, 3)) + +arr[0].prop[2] = {}; +>arr : Symbol(arr, Decl(mixedPropertyElementAccessAssignmentDeclaration.ts, 1, 3)) + diff --git a/tests/baselines/reference/mixedPropertyElementAccessAssignmentDeclaration.types b/tests/baselines/reference/mixedPropertyElementAccessAssignmentDeclaration.types new file mode 100644 index 0000000000..18d20dfd93 --- /dev/null +++ b/tests/baselines/reference/mixedPropertyElementAccessAssignmentDeclaration.types @@ -0,0 +1,17 @@ +=== tests/cases/conformance/salsa/mixedPropertyElementAccessAssignmentDeclaration.ts === +// Should not crash: #34642 +var arr = []; +>arr : any[] +>[] : undefined[] + +arr[0].prop[2] = {}; +>arr[0].prop[2] = {} : {} +>arr[0].prop[2] : any +>arr[0].prop : any +>arr[0] : any +>arr : any[] +>0 : 0 +>prop : any +>2 : 2 +>{} : {} + diff --git a/tests/cases/conformance/salsa/mixedPropertyElementAccessAssignmentDeclaration.ts b/tests/cases/conformance/salsa/mixedPropertyElementAccessAssignmentDeclaration.ts new file mode 100644 index 0000000000..8dd13383df --- /dev/null +++ b/tests/cases/conformance/salsa/mixedPropertyElementAccessAssignmentDeclaration.ts @@ -0,0 +1,3 @@ +// Should not crash: #34642 +var arr = []; +arr[0].prop[2] = {}; From 479d30646f16e02c294fe5963dc8a59487796218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C4=81rlis=20Ga=C5=86=C4=A3is?= Date: Wed, 23 Oct 2019 00:33:45 +0300 Subject: [PATCH 36/47] Fix crash in assigning function with this ref to alias (#34650) --- src/compiler/binder.ts | 2 +- .../importAliasModuleExports.errors.txt | 15 +++++++++++-- .../importAliasModuleExports.symbols | 9 ++++++++ .../reference/importAliasModuleExports.types | 21 +++++++++++++++++++ .../salsa/importAliasModuleExports.ts | 2 ++ 5 files changed, 46 insertions(+), 3 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index e1fdfc258e..1070020401 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2709,7 +2709,7 @@ namespace ts { } } - if (constructorSymbol) { + if (constructorSymbol && constructorSymbol.valueDeclaration) { // Declare a 'member' if the container is an ES5 class or ES6 constructor constructorSymbol.members = constructorSymbol.members || createSymbolTable(); // It's acceptable for multiple 'this' assignments of the same identifier to occur diff --git a/tests/baselines/reference/importAliasModuleExports.errors.txt b/tests/baselines/reference/importAliasModuleExports.errors.txt index ea3dd9ef0e..9cd2cd9768 100644 --- a/tests/baselines/reference/importAliasModuleExports.errors.txt +++ b/tests/baselines/reference/importAliasModuleExports.errors.txt @@ -1,5 +1,8 @@ tests/cases/conformance/salsa/main.js(2,13): error TS2339: Property 'foo' does not exist on type 'Alias'. -tests/cases/conformance/salsa/main.js(4,9): error TS2339: Property 'foo' does not exist on type 'Alias'. +tests/cases/conformance/salsa/main.js(3,13): error TS2339: Property 'func' does not exist on type 'Alias'. +tests/cases/conformance/salsa/main.js(3,38): error TS2339: Property '_func' does not exist on type 'Alias'. +tests/cases/conformance/salsa/main.js(5,9): error TS2339: Property 'foo' does not exist on type 'Alias'. +tests/cases/conformance/salsa/main.js(6,9): error TS2339: Property 'func' does not exist on type 'Alias'. ==== tests/cases/conformance/salsa/mod1.js (0 errors) ==== @@ -8,13 +11,21 @@ tests/cases/conformance/salsa/main.js(4,9): error TS2339: Property 'foo' does no } module.exports = Alias; -==== tests/cases/conformance/salsa/main.js (2 errors) ==== +==== tests/cases/conformance/salsa/main.js (5 errors) ==== import A from './mod1' A.prototype.foo = 0 ~~~ !!! error TS2339: Property 'foo' does not exist on type 'Alias'. + A.prototype.func = function() { this._func = 0; } + ~~~~ +!!! error TS2339: Property 'func' does not exist on type 'Alias'. + ~~~~~ +!!! error TS2339: Property '_func' does not exist on type 'Alias'. new A().bar new A().foo ~~~ !!! error TS2339: Property 'foo' does not exist on type 'Alias'. + new A().func() + ~~~~ +!!! error TS2339: Property 'func' does not exist on type 'Alias'. \ No newline at end of file diff --git a/tests/baselines/reference/importAliasModuleExports.symbols b/tests/baselines/reference/importAliasModuleExports.symbols index 4ab081d608..4d67eb8e19 100644 --- a/tests/baselines/reference/importAliasModuleExports.symbols +++ b/tests/baselines/reference/importAliasModuleExports.symbols @@ -20,6 +20,12 @@ A.prototype.foo = 0 >A : Symbol(A, Decl(main.js, 0, 6)) >prototype : Symbol(A.prototype) +A.prototype.func = function() { this._func = 0; } +>A.prototype : Symbol(A.prototype) +>A : Symbol(A, Decl(main.js, 0, 6)) +>prototype : Symbol(A.prototype) +>this : Symbol(A, Decl(mod1.js, 0, 0)) + new A().bar >new A().bar : Symbol(A.bar, Decl(mod1.js, 0, 13)) >A : Symbol(A, Decl(main.js, 0, 6)) @@ -28,3 +34,6 @@ new A().bar new A().foo >A : Symbol(A, Decl(main.js, 0, 6)) +new A().func() +>A : Symbol(A, Decl(main.js, 0, 6)) + diff --git a/tests/baselines/reference/importAliasModuleExports.types b/tests/baselines/reference/importAliasModuleExports.types index 1303ba6e8c..6c9b050954 100644 --- a/tests/baselines/reference/importAliasModuleExports.types +++ b/tests/baselines/reference/importAliasModuleExports.types @@ -26,6 +26,20 @@ A.prototype.foo = 0 >foo : any >0 : 0 +A.prototype.func = function() { this._func = 0; } +>A.prototype.func = function() { this._func = 0; } : () => void +>A.prototype.func : any +>A.prototype : A +>A : typeof A +>prototype : A +>func : any +>function() { this._func = 0; } : () => void +>this._func = 0 : 0 +>this._func : any +>this : A +>_func : any +>0 : 0 + new A().bar >new A().bar : () => number >new A() : A @@ -38,3 +52,10 @@ new A().foo >A : typeof A >foo : any +new A().func() +>new A().func() : any +>new A().func : any +>new A() : A +>A : typeof A +>func : any + diff --git a/tests/cases/conformance/salsa/importAliasModuleExports.ts b/tests/cases/conformance/salsa/importAliasModuleExports.ts index 5e85020667..691dde2026 100644 --- a/tests/cases/conformance/salsa/importAliasModuleExports.ts +++ b/tests/cases/conformance/salsa/importAliasModuleExports.ts @@ -11,5 +11,7 @@ module.exports = Alias; // @filename: main.js import A from './mod1' A.prototype.foo = 0 +A.prototype.func = function() { this._func = 0; } new A().bar new A().foo +new A().func() From 590fd3f2a28a72b7d3520768b01b9d355e5fc54c Mon Sep 17 00:00:00 2001 From: Andrew Casey Date: Tue, 22 Oct 2019 15:00:30 -0700 Subject: [PATCH 37/47] Don't assume that all symbols have declarations ...in Find All References. For example, global `this` doesn't in modules. Part of #34404 --- src/services/findAllReferences.ts | 2 +- .../fourslash/findAllRefsGlobalThisKeywordInModule.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 tests/cases/fourslash/findAllRefsGlobalThisKeywordInModule.ts diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index bd0e63561d..8d41211439 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -584,7 +584,7 @@ namespace ts.FindAllReferences.Core { } function getReferencedSymbolsForModuleIfDeclaredBySourceFile(symbol: Symbol, program: Program, sourceFiles: readonly SourceFile[], cancellationToken: CancellationToken, options: Options, sourceFilesSet: ReadonlyMap) { - const moduleSourceFile = symbol.flags & SymbolFlags.Module ? find(symbol.declarations, isSourceFile) : undefined; + const moduleSourceFile = (symbol.flags & SymbolFlags.Module) && symbol.declarations && find(symbol.declarations, isSourceFile); if (!moduleSourceFile) return undefined; const exportEquals = symbol.exports!.get(InternalSymbolName.ExportEquals); // If !!exportEquals, we're about to add references to `import("mod")` anyway, so don't double-count them. diff --git a/tests/cases/fourslash/findAllRefsGlobalThisKeywordInModule.ts b/tests/cases/fourslash/findAllRefsGlobalThisKeywordInModule.ts new file mode 100644 index 0000000000..40222b2530 --- /dev/null +++ b/tests/cases/fourslash/findAllRefsGlobalThisKeywordInModule.ts @@ -0,0 +1,8 @@ +/// +// @noLib: true + +////[|this|]; +////export const c = 1; + +const [glob] = test.ranges(); +verify.referenceGroups(glob, undefined); From f689982c9f2081bc90d2192eee96b404f75c4705 Mon Sep 17 00:00:00 2001 From: Keen Yee Liau Date: Wed, 23 Oct 2019 11:28:44 -0700 Subject: [PATCH 38/47] Prioritize loading plugin from probeLocations over peer node_modules This commit reoroders the loading sequence of a tsserver plugin. It should first check `pluginProbeLocations` before checking peer node_modules. PR closes https://github.com/microsoft/TypeScript/issues/34616 --- src/server/project.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/server/project.ts b/src/server/project.ts index c98e7660b3..28f9166064 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -1359,9 +1359,12 @@ namespace ts.server { return; } - // Search our peer node_modules, then any globally-specified probe paths - // ../../.. to walk from X/node_modules/typescript/lib/tsserver.js to X/node_modules/ - const searchPaths = [combinePaths(this.projectService.getExecutingFilePath(), "../../.."), ...this.projectService.pluginProbeLocations]; + // Search any globally-specified probe paths, then our peer node_modules + const searchPaths = [ + ...this.projectService.pluginProbeLocations, + // ../../.. to walk from X/node_modules/typescript/lib/tsserver.js to X/node_modules/ + combinePaths(this.projectService.getExecutingFilePath(), "../../.."), + ]; if (this.projectService.globalPlugins) { // Enable global plugins with synthetic configuration entries From eb08ee6848125ccaabcf0ebb34d2e44081df7f1b Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 23 Oct 2019 21:57:49 +0100 Subject: [PATCH 39/47] Add GitHub Actions (#34614) --- .github/workflows/ci.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000..69f2bf06f8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: CI + +on: + push: + branches: + - master + - release-* + pull_request: + branches: + - master + - release-* + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [8.x, 10.x, 12.x] + + steps: + - uses: actions/checkout@v1 + with: + fetch-depth: 5 + - name: Use node version ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: Remove existing TypeScript + run: | + npm uninstall typescript --no-save + npm uninstall tslint --no-save + - name: npm install and test + run: | + npm install + npm update + npm test From 8223c0752773ed6d4d42934adadd3d2cb15d5c3d Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Wed, 23 Oct 2019 15:53:38 -0700 Subject: [PATCH 40/47] getTypeFromJSDocValueReference: handle import types (#34683) Previously it only handled types whose declaration was from `require`, but now it handles types whose reference is an import type as well. --- src/compiler/checker.ts | 4 +-- ...docImportTypeReferenceToClassAlias.symbols | 28 ++++++++++++++++++ ...jsdocImportTypeReferenceToClassAlias.types | 29 +++++++++++++++++++ .../jsdocImportTypeReferenceToClassAlias.ts | 15 ++++++++++ 4 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/jsdocImportTypeReferenceToClassAlias.symbols create mode 100644 tests/baselines/reference/jsdocImportTypeReferenceToClassAlias.types create mode 100644 tests/cases/conformance/jsdoc/jsdocImportTypeReferenceToClassAlias.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 647a5e8f57..f30e765ab4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10754,7 +10754,7 @@ namespace ts { /** * A JSdoc TypeReference may be to a value, but resolve it as a type anyway. * Note: If the value is imported from commonjs, it should really be an alias, - * but this function fakes special-case code fakes alias resolution as well. + * but this function's special-case code fakes alias resolution as well. */ function getTypeFromJSDocValueReference(node: NodeWithTypeArguments, symbol: Symbol): Type | undefined { const valueType = getTypeOfSymbol(symbol); @@ -10766,7 +10766,7 @@ namespace ts { && isCallExpression(decl.initializer) && isRequireCall(decl.initializer, /*requireStringLiteralLikeArgument*/ true) && valueType.symbol; - if (isRequireAlias) { + if (isRequireAlias || node.kind === SyntaxKind.ImportType) { typeType = getTypeReferenceType(node, valueType.symbol); } } diff --git a/tests/baselines/reference/jsdocImportTypeReferenceToClassAlias.symbols b/tests/baselines/reference/jsdocImportTypeReferenceToClassAlias.symbols new file mode 100644 index 0000000000..c62d8e0676 --- /dev/null +++ b/tests/baselines/reference/jsdocImportTypeReferenceToClassAlias.symbols @@ -0,0 +1,28 @@ +=== tests/cases/conformance/jsdoc/mod1.js === +class C { +>C : Symbol(C, Decl(mod1.js, 0, 0)) + + s() { } +>s : Symbol(C.s, Decl(mod1.js, 0, 9)) +} +module.exports.C = C +>module.exports.C : Symbol(C, Decl(mod1.js, 2, 1)) +>module.exports : Symbol(C, Decl(mod1.js, 2, 1)) +>module : Symbol(module, Decl(mod1.js, 2, 1)) +>exports : Symbol("tests/cases/conformance/jsdoc/mod1", Decl(mod1.js, 0, 0)) +>C : Symbol(C, Decl(mod1.js, 2, 1)) +>C : Symbol(C, Decl(mod1.js, 0, 0)) + +=== tests/cases/conformance/jsdoc/test.js === +/** @typedef {import('./mod1').C} X */ +/** @param {X} c */ +function demo(c) { +>demo : Symbol(demo, Decl(test.js, 0, 0)) +>c : Symbol(c, Decl(test.js, 2, 14)) + + c.s +>c.s : Symbol(C.s, Decl(mod1.js, 0, 9)) +>c : Symbol(c, Decl(test.js, 2, 14)) +>s : Symbol(C.s, Decl(mod1.js, 0, 9)) +} + diff --git a/tests/baselines/reference/jsdocImportTypeReferenceToClassAlias.types b/tests/baselines/reference/jsdocImportTypeReferenceToClassAlias.types new file mode 100644 index 0000000000..880288d719 --- /dev/null +++ b/tests/baselines/reference/jsdocImportTypeReferenceToClassAlias.types @@ -0,0 +1,29 @@ +=== tests/cases/conformance/jsdoc/mod1.js === +class C { +>C : C + + s() { } +>s : () => void +} +module.exports.C = C +>module.exports.C = C : typeof C +>module.exports.C : typeof C +>module.exports : typeof import("tests/cases/conformance/jsdoc/mod1") +>module : { "tests/cases/conformance/jsdoc/mod1": typeof import("tests/cases/conformance/jsdoc/mod1"); } +>exports : typeof import("tests/cases/conformance/jsdoc/mod1") +>C : typeof C +>C : typeof C + +=== tests/cases/conformance/jsdoc/test.js === +/** @typedef {import('./mod1').C} X */ +/** @param {X} c */ +function demo(c) { +>demo : (c: C) => void +>c : C + + c.s +>c.s : () => void +>c : C +>s : () => void +} + diff --git a/tests/cases/conformance/jsdoc/jsdocImportTypeReferenceToClassAlias.ts b/tests/cases/conformance/jsdoc/jsdocImportTypeReferenceToClassAlias.ts new file mode 100644 index 0000000000..d53da96003 --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocImportTypeReferenceToClassAlias.ts @@ -0,0 +1,15 @@ +// @noEmit: true +// @allowJs: true +// @checkJs: true +// @Filename: mod1.js +class C { + s() { } +} +module.exports.C = C + +// @Filename: test.js +/** @typedef {import('./mod1').C} X */ +/** @param {X} c */ +function demo(c) { + c.s +} From 07d3a2ec7e4bba39df98a9f1bd16bc04112b9716 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Wed, 23 Oct 2019 16:01:25 -0700 Subject: [PATCH 41/47] Do not consider element accesses which are neither statically bindable nor late bound as special assignments (#34679) --- src/compiler/utilities.ts | 8 +++++--- .../jsNegativeElementAccessNotBound.symbols | 7 +++++++ .../reference/jsNegativeElementAccessNotBound.types | 13 +++++++++++++ .../compiler/jsNegativeElementAccessNotBound.ts | 6 ++++++ 4 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 tests/baselines/reference/jsNegativeElementAccessNotBound.symbols create mode 100644 tests/baselines/reference/jsNegativeElementAccessNotBound.types create mode 100644 tests/cases/compiler/jsNegativeElementAccessNotBound.ts diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index e86f79b360..12b11eeae2 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2115,7 +2115,7 @@ namespace ts { if (expr.operatorToken.kind !== SyntaxKind.EqualsToken || !isAccessExpression(expr.left)) { return AssignmentDeclarationKind.None; } - if (isBindableStaticNameExpression(expr.left.expression) && getElementOrPropertyAccessName(expr.left) === "prototype" && isObjectLiteralExpression(getInitializerOfBinaryExpression(expr))) { + if (isBindableStaticNameExpression(expr.left.expression, /*excludeThisKeyword*/ true) && getElementOrPropertyAccessName(expr.left) === "prototype" && isObjectLiteralExpression(getInitializerOfBinaryExpression(expr))) { // F.prototype = { ... } return AssignmentDeclarationKind.Prototype; } @@ -2183,8 +2183,10 @@ namespace ts { // exports.name = expr OR module.exports.name = expr OR exports["name"] = expr ... return AssignmentDeclarationKind.ExportsProperty; } - // F.G...x = expr - return AssignmentDeclarationKind.Property; + if (isBindableStaticNameExpression(lhs, /*excludeThisKeyword*/ true) || (isElementAccessExpression(lhs) && isDynamicName(lhs) && lhs.expression.kind !== SyntaxKind.ThisKeyword)) { + // F.G...x = expr + return AssignmentDeclarationKind.Property; + } } return AssignmentDeclarationKind.None; diff --git a/tests/baselines/reference/jsNegativeElementAccessNotBound.symbols b/tests/baselines/reference/jsNegativeElementAccessNotBound.symbols new file mode 100644 index 0000000000..d8ff3f52e2 --- /dev/null +++ b/tests/baselines/reference/jsNegativeElementAccessNotBound.symbols @@ -0,0 +1,7 @@ +=== tests/cases/compiler/jsNegativeELementAccessNotBound.js === +var indexMap = {}; +>indexMap : Symbol(indexMap, Decl(jsNegativeELementAccessNotBound.js, 0, 3)) + +indexMap[-1] = 0; +>indexMap : Symbol(indexMap, Decl(jsNegativeELementAccessNotBound.js, 0, 3)) + diff --git a/tests/baselines/reference/jsNegativeElementAccessNotBound.types b/tests/baselines/reference/jsNegativeElementAccessNotBound.types new file mode 100644 index 0000000000..dbd5d4c005 --- /dev/null +++ b/tests/baselines/reference/jsNegativeElementAccessNotBound.types @@ -0,0 +1,13 @@ +=== tests/cases/compiler/jsNegativeELementAccessNotBound.js === +var indexMap = {}; +>indexMap : {} +>{} : {} + +indexMap[-1] = 0; +>indexMap[-1] = 0 : 0 +>indexMap[-1] : any +>indexMap : {} +>-1 : -1 +>1 : 1 +>0 : 0 + diff --git a/tests/cases/compiler/jsNegativeElementAccessNotBound.ts b/tests/cases/compiler/jsNegativeElementAccessNotBound.ts new file mode 100644 index 0000000000..c7a204e578 --- /dev/null +++ b/tests/cases/compiler/jsNegativeElementAccessNotBound.ts @@ -0,0 +1,6 @@ +// @allowJs: true +// @checkJs: true +// @noEmit: true +// @filename: jsNegativeELementAccessNotBound.js +var indexMap = {}; +indexMap[-1] = 0; From 969634b97c22d73eccb6cda96fcb036c13ad0601 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 24 Oct 2019 09:24:58 -0700 Subject: [PATCH 42/47] Restore delayed merge check to getTypeFromJSDocValueReference (#34706) * Restore delayed merge check to getTypeFromJSDocValueReference This is needed when a function merges with a prototype assignment. The resulting *merged* symbol is a constructor function marked with SymbolFlags.Class. However, the merge doesn't happen until getTypeOfFuncClassEnumModule is called, which, in the getTypeReferenceType code path, doesn't happen until getTypeFromJSDocValueReference. That means the check for SymbolFlags.Class is missed. Previously, getTypeFromJSDocValueReference had a weird check `symbol !== getTypeOfSymbol(symbol).symbol`, which, if true, ran getTypeReferenceType again on `getTypeOfSymbol(symbol).symbol`. For JS "aliases", this had the effect of dereferencing the alias, and for function-prototype merges, this had the effect of ... just trying again after the merge had happened. This is a confusing way to run things. getTypeReferenceType should instead detect a function-prototype merge, cause it to happen, and *then* run the rest of its code instead of relying on try-again logic at the very end. However, for the RC, I want to fix this code by restoring the old check, with an additional check to make sure that #33106 doesn't break again: ```ts const valueType = getTypeOfSymbol(symbol) symbol !== valueType.symbol && getMergedSymbol(symbol) === valueType.symbol ``` I'll work on the real fix afterwards and put it into 3.8. * Add bug number --- src/compiler/checker.ts | 4 +- .../jsdocTypeReferenceToMergedClass.symbols | 35 ++++++++++++++++ .../jsdocTypeReferenceToMergedClass.types | 41 +++++++++++++++++++ .../jsdoc/jsdocTypeReferenceToMergedClass.ts | 17 ++++++++ 4 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/jsdocTypeReferenceToMergedClass.symbols create mode 100644 tests/baselines/reference/jsdocTypeReferenceToMergedClass.types create mode 100644 tests/cases/conformance/jsdoc/jsdocTypeReferenceToMergedClass.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f30e765ab4..ed3057895f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -10766,7 +10766,9 @@ namespace ts { && isCallExpression(decl.initializer) && isRequireCall(decl.initializer, /*requireStringLiteralLikeArgument*/ true) && valueType.symbol; - if (isRequireAlias || node.kind === SyntaxKind.ImportType) { + const isImportType = node.kind === SyntaxKind.ImportType; + const isDelayedMergeClass = symbol !== valueType.symbol && getMergedSymbol(symbol) === valueType.symbol; + if (isRequireAlias || isImportType || isDelayedMergeClass) { typeType = getTypeReferenceType(node, valueType.symbol); } } diff --git a/tests/baselines/reference/jsdocTypeReferenceToMergedClass.symbols b/tests/baselines/reference/jsdocTypeReferenceToMergedClass.symbols new file mode 100644 index 0000000000..229163c5d7 --- /dev/null +++ b/tests/baselines/reference/jsdocTypeReferenceToMergedClass.symbols @@ -0,0 +1,35 @@ +=== tests/cases/conformance/jsdoc/test.js === +// https://github.com/microsoft/TypeScript/issues/34685 + +/** @param {Workspace.Project} p */ +function demo(p) { +>demo : Symbol(demo, Decl(test.js, 0, 0)) +>p : Symbol(p, Decl(test.js, 3, 14)) + + p.isServiceProject() +>p.isServiceProject : Symbol(isServiceProject, Decl(mod1.js, 3, 31)) +>p : Symbol(p, Decl(test.js, 3, 14)) +>isServiceProject : Symbol(isServiceProject, Decl(mod1.js, 3, 31)) +} +=== tests/cases/conformance/jsdoc/mod1.js === +// Note: mod1.js needs to appear second to trigger the bug +var Workspace = {} +>Workspace : Symbol(Workspace, Decl(mod1.js, 1, 3), Decl(mod1.js, 1, 18), Decl(mod1.js, 2, 37)) + +Workspace.Project = function wp() { } +>Workspace.Project : Symbol(Workspace.Project, Decl(mod1.js, 1, 18), Decl(mod1.js, 3, 10)) +>Workspace : Symbol(Workspace, Decl(mod1.js, 1, 3), Decl(mod1.js, 1, 18), Decl(mod1.js, 2, 37)) +>Project : Symbol(Workspace.Project, Decl(mod1.js, 1, 18), Decl(mod1.js, 3, 10)) +>wp : Symbol(wp, Decl(mod1.js, 2, 19)) + +Workspace.Project.prototype = { +>Workspace.Project.prototype : Symbol(Workspace.Project.prototype, Decl(mod1.js, 2, 37)) +>Workspace.Project : Symbol(Workspace.Project, Decl(mod1.js, 1, 18), Decl(mod1.js, 3, 10)) +>Workspace : Symbol(Workspace, Decl(mod1.js, 1, 3), Decl(mod1.js, 1, 18), Decl(mod1.js, 2, 37)) +>Project : Symbol(Workspace.Project, Decl(mod1.js, 1, 18), Decl(mod1.js, 3, 10)) +>prototype : Symbol(Workspace.Project.prototype, Decl(mod1.js, 2, 37)) + + isServiceProject() {} +>isServiceProject : Symbol(isServiceProject, Decl(mod1.js, 3, 31)) +} + diff --git a/tests/baselines/reference/jsdocTypeReferenceToMergedClass.types b/tests/baselines/reference/jsdocTypeReferenceToMergedClass.types new file mode 100644 index 0000000000..c4e380f8c0 --- /dev/null +++ b/tests/baselines/reference/jsdocTypeReferenceToMergedClass.types @@ -0,0 +1,41 @@ +=== tests/cases/conformance/jsdoc/test.js === +// https://github.com/microsoft/TypeScript/issues/34685 + +/** @param {Workspace.Project} p */ +function demo(p) { +>demo : (p: wp) => void +>p : wp + + p.isServiceProject() +>p.isServiceProject() : void +>p.isServiceProject : () => void +>p : wp +>isServiceProject : () => void +} +=== tests/cases/conformance/jsdoc/mod1.js === +// Note: mod1.js needs to appear second to trigger the bug +var Workspace = {} +>Workspace : typeof Workspace +>{} : {} + +Workspace.Project = function wp() { } +>Workspace.Project = function wp() { } : typeof wp +>Workspace.Project : typeof wp +>Workspace : typeof Workspace +>Project : typeof wp +>function wp() { } : typeof wp +>wp : typeof wp + +Workspace.Project.prototype = { +>Workspace.Project.prototype = { isServiceProject() {}} : { isServiceProject(): void; } +>Workspace.Project.prototype : { isServiceProject(): void; } +>Workspace.Project : typeof wp +>Workspace : typeof Workspace +>Project : typeof wp +>prototype : { isServiceProject(): void; } +>{ isServiceProject() {}} : { isServiceProject(): void; } + + isServiceProject() {} +>isServiceProject : () => void +} + diff --git a/tests/cases/conformance/jsdoc/jsdocTypeReferenceToMergedClass.ts b/tests/cases/conformance/jsdoc/jsdocTypeReferenceToMergedClass.ts new file mode 100644 index 0000000000..e559ffd27c --- /dev/null +++ b/tests/cases/conformance/jsdoc/jsdocTypeReferenceToMergedClass.ts @@ -0,0 +1,17 @@ +// https://github.com/microsoft/TypeScript/issues/34685 +// @noEmit: true +// @allowJs: true +// @checkJs: true + +// @Filename: test.js +/** @param {Workspace.Project} p */ +function demo(p) { + p.isServiceProject() +} +// @Filename: mod1.js +// Note: mod1.js needs to appear second to trigger the bug +var Workspace = {} +Workspace.Project = function wp() { } +Workspace.Project.prototype = { + isServiceProject() {} +} From d892fd408fecc86cf2f262821344b8c2429be392 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com> Date: Thu, 24 Oct 2019 13:12:44 -0700 Subject: [PATCH 43/47] Fix expando handling in getTypeReferenceType (#34712) * Fix expando handling in getTypeReferenceType getExpandoSymbol looks for the initialiser of a symbol when it is an expando value (IIFEs, function exprs, class exprs and empty object literals) and returns the symbol. Previously, however, it returned the symbol of the initialiser without merging with the declaration symbol itself. This missed, in particular, the prototype assignment in the following pattern: ```js var x = function x() { this.y = 1 } x.prototype = { z() { } } /** @type {x} */ var xx; xx.z // missed! ``` getJSDocValueReference had weird try-again code that relied on calling getTypeOfSymbol, which *does* correctly merge the symbols. This PR re-removes that code and instead makes getExpandoSymbol call mergeJSSymbols itself. * Remove extra newline --- src/compiler/checker.ts | 13 +++--- .../jsdocTypeReferenceToMergedClass.symbols | 45 +++++++++---------- .../jsdocTypeReferenceToMergedClass.types | 19 ++++---- .../jsdoc/jsdocTypeReferenceToMergedClass.ts | 12 +++-- 4 files changed, 42 insertions(+), 47 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ed3057895f..052c89a7b0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2629,7 +2629,12 @@ namespace ts { return undefined; } const init = isVariableDeclaration(decl) ? getDeclaredExpandoInitializer(decl) : getAssignedExpandoInitializer(decl); - return init && getSymbolOfNode(init) || undefined; + if (init) { + const initSymbol = getSymbolOfNode(init); + if (initSymbol) { + return mergeJSSymbols(initSymbol, symbol); + } + } } @@ -10766,9 +10771,7 @@ namespace ts { && isCallExpression(decl.initializer) && isRequireCall(decl.initializer, /*requireStringLiteralLikeArgument*/ true) && valueType.symbol; - const isImportType = node.kind === SyntaxKind.ImportType; - const isDelayedMergeClass = symbol !== valueType.symbol && getMergedSymbol(symbol) === valueType.symbol; - if (isRequireAlias || isImportType || isDelayedMergeClass) { + if (isRequireAlias || node.kind === SyntaxKind.ImportType) { typeType = getTypeReferenceType(node, valueType.symbol); } } @@ -24997,7 +25000,7 @@ namespace ts { } function mergeJSSymbols(target: Symbol, source: Symbol | undefined) { - if (source && (hasEntries(source.exports) || hasEntries(source.members))) { + if (source) { const links = getSymbolLinks(source); if (!links.inferredClassSymbol || !links.inferredClassSymbol.has("" + getSymbolId(target))) { const inferred = isTransientSymbol(target) ? target : cloneSymbol(target) as TransientSymbol; diff --git a/tests/baselines/reference/jsdocTypeReferenceToMergedClass.symbols b/tests/baselines/reference/jsdocTypeReferenceToMergedClass.symbols index 229163c5d7..86dcb53641 100644 --- a/tests/baselines/reference/jsdocTypeReferenceToMergedClass.symbols +++ b/tests/baselines/reference/jsdocTypeReferenceToMergedClass.symbols @@ -1,35 +1,32 @@ -=== tests/cases/conformance/jsdoc/test.js === +=== tests/cases/conformance/jsdoc/jsdocTypeReferenceToMergedClass.js === // https://github.com/microsoft/TypeScript/issues/34685 -/** @param {Workspace.Project} p */ -function demo(p) { ->demo : Symbol(demo, Decl(test.js, 0, 0)) ->p : Symbol(p, Decl(test.js, 3, 14)) - - p.isServiceProject() ->p.isServiceProject : Symbol(isServiceProject, Decl(mod1.js, 3, 31)) ->p : Symbol(p, Decl(test.js, 3, 14)) ->isServiceProject : Symbol(isServiceProject, Decl(mod1.js, 3, 31)) -} -=== tests/cases/conformance/jsdoc/mod1.js === -// Note: mod1.js needs to appear second to trigger the bug var Workspace = {} ->Workspace : Symbol(Workspace, Decl(mod1.js, 1, 3), Decl(mod1.js, 1, 18), Decl(mod1.js, 2, 37)) +>Workspace : Symbol(Workspace, Decl(jsdocTypeReferenceToMergedClass.js, 2, 3), Decl(jsdocTypeReferenceToMergedClass.js, 5, 20), Decl(jsdocTypeReferenceToMergedClass.js, 7, 37)) + +/** @type {Workspace.Project} */ +var p; +>p : Symbol(p, Decl(jsdocTypeReferenceToMergedClass.js, 4, 3)) + +p.isServiceProject() +>p.isServiceProject : Symbol(isServiceProject, Decl(jsdocTypeReferenceToMergedClass.js, 8, 31)) +>p : Symbol(p, Decl(jsdocTypeReferenceToMergedClass.js, 4, 3)) +>isServiceProject : Symbol(isServiceProject, Decl(jsdocTypeReferenceToMergedClass.js, 8, 31)) Workspace.Project = function wp() { } ->Workspace.Project : Symbol(Workspace.Project, Decl(mod1.js, 1, 18), Decl(mod1.js, 3, 10)) ->Workspace : Symbol(Workspace, Decl(mod1.js, 1, 3), Decl(mod1.js, 1, 18), Decl(mod1.js, 2, 37)) ->Project : Symbol(Workspace.Project, Decl(mod1.js, 1, 18), Decl(mod1.js, 3, 10)) ->wp : Symbol(wp, Decl(mod1.js, 2, 19)) +>Workspace.Project : Symbol(Workspace.Project, Decl(jsdocTypeReferenceToMergedClass.js, 5, 20), Decl(jsdocTypeReferenceToMergedClass.js, 8, 10)) +>Workspace : Symbol(Workspace, Decl(jsdocTypeReferenceToMergedClass.js, 2, 3), Decl(jsdocTypeReferenceToMergedClass.js, 5, 20), Decl(jsdocTypeReferenceToMergedClass.js, 7, 37)) +>Project : Symbol(Workspace.Project, Decl(jsdocTypeReferenceToMergedClass.js, 5, 20), Decl(jsdocTypeReferenceToMergedClass.js, 8, 10)) +>wp : Symbol(wp, Decl(jsdocTypeReferenceToMergedClass.js, 7, 19)) Workspace.Project.prototype = { ->Workspace.Project.prototype : Symbol(Workspace.Project.prototype, Decl(mod1.js, 2, 37)) ->Workspace.Project : Symbol(Workspace.Project, Decl(mod1.js, 1, 18), Decl(mod1.js, 3, 10)) ->Workspace : Symbol(Workspace, Decl(mod1.js, 1, 3), Decl(mod1.js, 1, 18), Decl(mod1.js, 2, 37)) ->Project : Symbol(Workspace.Project, Decl(mod1.js, 1, 18), Decl(mod1.js, 3, 10)) ->prototype : Symbol(Workspace.Project.prototype, Decl(mod1.js, 2, 37)) +>Workspace.Project.prototype : Symbol(Workspace.Project.prototype, Decl(jsdocTypeReferenceToMergedClass.js, 7, 37)) +>Workspace.Project : Symbol(Workspace.Project, Decl(jsdocTypeReferenceToMergedClass.js, 5, 20), Decl(jsdocTypeReferenceToMergedClass.js, 8, 10)) +>Workspace : Symbol(Workspace, Decl(jsdocTypeReferenceToMergedClass.js, 2, 3), Decl(jsdocTypeReferenceToMergedClass.js, 5, 20), Decl(jsdocTypeReferenceToMergedClass.js, 7, 37)) +>Project : Symbol(Workspace.Project, Decl(jsdocTypeReferenceToMergedClass.js, 5, 20), Decl(jsdocTypeReferenceToMergedClass.js, 8, 10)) +>prototype : Symbol(Workspace.Project.prototype, Decl(jsdocTypeReferenceToMergedClass.js, 7, 37)) isServiceProject() {} ->isServiceProject : Symbol(isServiceProject, Decl(mod1.js, 3, 31)) +>isServiceProject : Symbol(isServiceProject, Decl(jsdocTypeReferenceToMergedClass.js, 8, 31)) } diff --git a/tests/baselines/reference/jsdocTypeReferenceToMergedClass.types b/tests/baselines/reference/jsdocTypeReferenceToMergedClass.types index c4e380f8c0..3fd1661fa2 100644 --- a/tests/baselines/reference/jsdocTypeReferenceToMergedClass.types +++ b/tests/baselines/reference/jsdocTypeReferenceToMergedClass.types @@ -1,22 +1,19 @@ -=== tests/cases/conformance/jsdoc/test.js === +=== tests/cases/conformance/jsdoc/jsdocTypeReferenceToMergedClass.js === // https://github.com/microsoft/TypeScript/issues/34685 -/** @param {Workspace.Project} p */ -function demo(p) { ->demo : (p: wp) => void +var Workspace = {} +>Workspace : typeof Workspace +>{} : {} + +/** @type {Workspace.Project} */ +var p; >p : wp - p.isServiceProject() +p.isServiceProject() >p.isServiceProject() : void >p.isServiceProject : () => void >p : wp >isServiceProject : () => void -} -=== tests/cases/conformance/jsdoc/mod1.js === -// Note: mod1.js needs to appear second to trigger the bug -var Workspace = {} ->Workspace : typeof Workspace ->{} : {} Workspace.Project = function wp() { } >Workspace.Project = function wp() { } : typeof wp diff --git a/tests/cases/conformance/jsdoc/jsdocTypeReferenceToMergedClass.ts b/tests/cases/conformance/jsdoc/jsdocTypeReferenceToMergedClass.ts index e559ffd27c..931ad69cfb 100644 --- a/tests/cases/conformance/jsdoc/jsdocTypeReferenceToMergedClass.ts +++ b/tests/cases/conformance/jsdoc/jsdocTypeReferenceToMergedClass.ts @@ -3,14 +3,12 @@ // @allowJs: true // @checkJs: true -// @Filename: test.js -/** @param {Workspace.Project} p */ -function demo(p) { - p.isServiceProject() -} -// @Filename: mod1.js -// Note: mod1.js needs to appear second to trigger the bug +// @Filename: jsdocTypeReferenceToMergedClass.js var Workspace = {} +/** @type {Workspace.Project} */ +var p; +p.isServiceProject() + Workspace.Project = function wp() { } Workspace.Project.prototype = { isServiceProject() {} From 7cfa1dfb8a16d62d6df6f422c1efefd252f27012 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 24 Oct 2019 14:02:37 -0700 Subject: [PATCH 44/47] Fix regression in mixin emit by removing unneeded line of code (#34715) * Fix regression in mixin emit by removing unneeded line of code * Double the test, double the fun --- src/compiler/checker.ts | 2 - .../anonClassDeclarationEmitIsAnon.js | 142 ++++++++++++++++++ .../anonClassDeclarationEmitIsAnon.symbols | 68 +++++++++ .../anonClassDeclarationEmitIsAnon.types | 72 +++++++++ .../anonClassDeclarationEmitIsAnon.ts | 34 +++++ 5 files changed, 316 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/anonClassDeclarationEmitIsAnon.js create mode 100644 tests/baselines/reference/anonClassDeclarationEmitIsAnon.symbols create mode 100644 tests/baselines/reference/anonClassDeclarationEmitIsAnon.types create mode 100644 tests/cases/compiler/anonClassDeclarationEmitIsAnon.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 052c89a7b0..bf50e8b15c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4092,8 +4092,6 @@ namespace ts { else if (context.flags & NodeBuilderFlags.WriteClassExpressionAsTypeLiteral && type.symbol.valueDeclaration && isClassLike(type.symbol.valueDeclaration) && - // Use `import` types for refs to other scopes, only anonymize something defined in the same scope - findAncestor(type.symbol.valueDeclaration, d => d === getSourceFileOfNode(context.enclosingDeclaration)) && !isValueSymbolAccessible(type.symbol, context.enclosingDeclaration) ) { return createAnonymousTypeNode(type); diff --git a/tests/baselines/reference/anonClassDeclarationEmitIsAnon.js b/tests/baselines/reference/anonClassDeclarationEmitIsAnon.js new file mode 100644 index 0000000000..697348e1ba --- /dev/null +++ b/tests/baselines/reference/anonClassDeclarationEmitIsAnon.js @@ -0,0 +1,142 @@ +//// [tests/cases/compiler/anonClassDeclarationEmitIsAnon.ts] //// + +//// [wrapClass.ts] +export function wrapClass(param: any) { + return class Wrapped { + foo() { + return param; + } + } +} + +export type Constructor = new (...args: any[]) => T; + +export function Timestamped(Base: TBase) { + return class extends Base { + timestamp = Date.now(); + }; +} + +//// [index.ts] +import { wrapClass, Timestamped } from "./wrapClass"; + +export default wrapClass(0); + +// Simple class +export class User { + name = ''; +} + +// User that is Timestamped +export class TimestampedUser extends Timestamped(User) { + constructor() { + super(); + } +} + +//// [wrapClass.js] +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +exports.__esModule = true; +function wrapClass(param) { + return /** @class */ (function () { + function Wrapped() { + } + Wrapped.prototype.foo = function () { + return param; + }; + return Wrapped; + }()); +} +exports.wrapClass = wrapClass; +function Timestamped(Base) { + return /** @class */ (function (_super) { + __extends(class_1, _super); + function class_1() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.timestamp = Date.now(); + return _this; + } + return class_1; + }(Base)); +} +exports.Timestamped = Timestamped; +//// [index.js] +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +exports.__esModule = true; +var wrapClass_1 = require("./wrapClass"); +exports["default"] = wrapClass_1.wrapClass(0); +// Simple class +var User = /** @class */ (function () { + function User() { + this.name = ''; + } + return User; +}()); +exports.User = User; +// User that is Timestamped +var TimestampedUser = /** @class */ (function (_super) { + __extends(TimestampedUser, _super); + function TimestampedUser() { + return _super.call(this) || this; + } + return TimestampedUser; +}(wrapClass_1.Timestamped(User))); +exports.TimestampedUser = TimestampedUser; + + +//// [wrapClass.d.ts] +export declare function wrapClass(param: any): { + new (): { + foo(): any; + }; +}; +export declare type Constructor = new (...args: any[]) => T; +export declare function Timestamped(Base: TBase): { + new (...args: any[]): { + timestamp: number; + }; +} & TBase; +//// [index.d.ts] +declare const _default: { + new (): { + foo(): any; + }; +}; +export default _default; +export declare class User { + name: string; +} +declare const TimestampedUser_base: { + new (...args: any[]): { + timestamp: number; + }; +} & typeof User; +export declare class TimestampedUser extends TimestampedUser_base { + constructor(); +} diff --git a/tests/baselines/reference/anonClassDeclarationEmitIsAnon.symbols b/tests/baselines/reference/anonClassDeclarationEmitIsAnon.symbols new file mode 100644 index 0000000000..f2988d8cb2 --- /dev/null +++ b/tests/baselines/reference/anonClassDeclarationEmitIsAnon.symbols @@ -0,0 +1,68 @@ +=== tests/cases/compiler/wrapClass.ts === +export function wrapClass(param: any) { +>wrapClass : Symbol(wrapClass, Decl(wrapClass.ts, 0, 0)) +>param : Symbol(param, Decl(wrapClass.ts, 0, 26)) + + return class Wrapped { +>Wrapped : Symbol(Wrapped, Decl(wrapClass.ts, 1, 10)) + + foo() { +>foo : Symbol(Wrapped.foo, Decl(wrapClass.ts, 1, 26)) + + return param; +>param : Symbol(param, Decl(wrapClass.ts, 0, 26)) + } + } +} + +export type Constructor = new (...args: any[]) => T; +>Constructor : Symbol(Constructor, Decl(wrapClass.ts, 6, 1)) +>T : Symbol(T, Decl(wrapClass.ts, 8, 24)) +>args : Symbol(args, Decl(wrapClass.ts, 8, 39)) +>T : Symbol(T, Decl(wrapClass.ts, 8, 24)) + +export function Timestamped(Base: TBase) { +>Timestamped : Symbol(Timestamped, Decl(wrapClass.ts, 8, 60)) +>TBase : Symbol(TBase, Decl(wrapClass.ts, 10, 28)) +>Constructor : Symbol(Constructor, Decl(wrapClass.ts, 6, 1)) +>Base : Symbol(Base, Decl(wrapClass.ts, 10, 55)) +>TBase : Symbol(TBase, Decl(wrapClass.ts, 10, 28)) + + return class extends Base { +>Base : Symbol(Base, Decl(wrapClass.ts, 10, 55)) + + timestamp = Date.now(); +>timestamp : Symbol((Anonymous class).timestamp, Decl(wrapClass.ts, 11, 31)) +>Date.now : Symbol(DateConstructor.now, Decl(lib.es5.d.ts, --, --)) +>Date : Symbol(Date, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(lib.scripthost.d.ts, --, --)) +>now : Symbol(DateConstructor.now, Decl(lib.es5.d.ts, --, --)) + + }; +} + +=== tests/cases/compiler/index.ts === +import { wrapClass, Timestamped } from "./wrapClass"; +>wrapClass : Symbol(wrapClass, Decl(index.ts, 0, 8)) +>Timestamped : Symbol(Timestamped, Decl(index.ts, 0, 19)) + +export default wrapClass(0); +>wrapClass : Symbol(wrapClass, Decl(index.ts, 0, 8)) + +// Simple class +export class User { +>User : Symbol(User, Decl(index.ts, 2, 28)) + + name = ''; +>name : Symbol(User.name, Decl(index.ts, 5, 19)) +} + +// User that is Timestamped +export class TimestampedUser extends Timestamped(User) { +>TimestampedUser : Symbol(TimestampedUser, Decl(index.ts, 7, 1)) +>Timestamped : Symbol(Timestamped, Decl(index.ts, 0, 19)) +>User : Symbol(User, Decl(index.ts, 2, 28)) + + constructor() { + super(); + } +} diff --git a/tests/baselines/reference/anonClassDeclarationEmitIsAnon.types b/tests/baselines/reference/anonClassDeclarationEmitIsAnon.types new file mode 100644 index 0000000000..038b0a49bf --- /dev/null +++ b/tests/baselines/reference/anonClassDeclarationEmitIsAnon.types @@ -0,0 +1,72 @@ +=== tests/cases/compiler/wrapClass.ts === +export function wrapClass(param: any) { +>wrapClass : (param: any) => typeof Wrapped +>param : any + + return class Wrapped { +>class Wrapped { foo() { return param; } } : typeof Wrapped +>Wrapped : typeof Wrapped + + foo() { +>foo : () => any + + return param; +>param : any + } + } +} + +export type Constructor = new (...args: any[]) => T; +>Constructor : Constructor +>args : any[] + +export function Timestamped(Base: TBase) { +>Timestamped : >(Base: TBase) => { new (...args: any[]): (Anonymous class); prototype: Timestamped.(Anonymous class); } & TBase +>Base : TBase + + return class extends Base { +>class extends Base { timestamp = Date.now(); } : { new (...args: any[]): (Anonymous class); prototype: Timestamped.(Anonymous class); } & TBase +>Base : {} + + timestamp = Date.now(); +>timestamp : number +>Date.now() : number +>Date.now : () => number +>Date : DateConstructor +>now : () => number + + }; +} + +=== tests/cases/compiler/index.ts === +import { wrapClass, Timestamped } from "./wrapClass"; +>wrapClass : (param: any) => typeof Wrapped +>Timestamped : >(Base: TBase) => { new (...args: any[]): (Anonymous class); prototype: Timestamped.(Anonymous class); } & TBase + +export default wrapClass(0); +>wrapClass(0) : typeof Wrapped +>wrapClass : (param: any) => typeof Wrapped +>0 : 0 + +// Simple class +export class User { +>User : User + + name = ''; +>name : string +>'' : "" +} + +// User that is Timestamped +export class TimestampedUser extends Timestamped(User) { +>TimestampedUser : TimestampedUser +>Timestamped(User) : Timestamped.(Anonymous class) & User +>Timestamped : >(Base: TBase) => { new (...args: any[]): (Anonymous class); prototype: Timestamped.(Anonymous class); } & TBase +>User : typeof User + + constructor() { + super(); +>super() : void +>super : { new (...args: any[]): Timestamped.(Anonymous class); prototype: Timestamped.(Anonymous class); } & typeof User + } +} diff --git a/tests/cases/compiler/anonClassDeclarationEmitIsAnon.ts b/tests/cases/compiler/anonClassDeclarationEmitIsAnon.ts new file mode 100644 index 0000000000..e7718e5c01 --- /dev/null +++ b/tests/cases/compiler/anonClassDeclarationEmitIsAnon.ts @@ -0,0 +1,34 @@ +// @declaration: true +// @filename: wrapClass.ts +export function wrapClass(param: any) { + return class Wrapped { + foo() { + return param; + } + } +} + +export type Constructor = new (...args: any[]) => T; + +export function Timestamped(Base: TBase) { + return class extends Base { + timestamp = Date.now(); + }; +} + +// @filename: index.ts +import { wrapClass, Timestamped } from "./wrapClass"; + +export default wrapClass(0); + +// Simple class +export class User { + name = ''; +} + +// User that is Timestamped +export class TimestampedUser extends Timestamped(User) { + constructor() { + super(); + } +} \ No newline at end of file From a03227d60e2da470a8a964903016e2b3d0106950 Mon Sep 17 00:00:00 2001 From: Martin Probst Date: Thu, 24 Oct 2019 23:04:42 +0200 Subject: [PATCH 45/47] Avoid a crash with `@typedef` in a script file. (#33847) * Avoid a crash with `@typedef` in a script file. Scripts (as opposed to modules) do not have a symbol object. If a script contains a `@typdef` defined on a namespace called `exports`, TypeScript crashes because it attempts to create an exported symbol on the (non-existent) symbol of the SourceFile. This change avoids the crash by explicitly checking if the source file has a symbol object, i.e. whether it is a module. * Add usage of exports.SomeName typedef. * Fix bug at bind site rather than in declare func --- src/compiler/binder.ts | 11 +++++++-- ...checkJsdocTypedefOnlySourceFile.errors.txt | 18 +++++++++++++++ .../checkJsdocTypedefOnlySourceFile.js | 23 +++++++++++++++++++ .../checkJsdocTypedefOnlySourceFile.symbols | 16 +++++++++++++ .../checkJsdocTypedefOnlySourceFile.types | 20 ++++++++++++++++ .../jsdoc/checkJsdocTypedefOnlySourceFile.ts | 15 ++++++++++++ 6 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/checkJsdocTypedefOnlySourceFile.errors.txt create mode 100644 tests/baselines/reference/checkJsdocTypedefOnlySourceFile.js create mode 100644 tests/baselines/reference/checkJsdocTypedefOnlySourceFile.symbols create mode 100644 tests/baselines/reference/checkJsdocTypedefOnlySourceFile.types create mode 100644 tests/cases/conformance/jsdoc/checkJsdocTypedefOnlySourceFile.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 1070020401..5f36d42955 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2003,7 +2003,12 @@ namespace ts { switch (getAssignmentDeclarationPropertyAccessKind(declName.parent)) { case AssignmentDeclarationKind.ExportsProperty: case AssignmentDeclarationKind.ModuleExports: - container = file; + if (!isExternalOrCommonJsModule(file)) { + container = undefined!; + } + else { + container = file; + } break; case AssignmentDeclarationKind.ThisProperty: container = declName.parent.expression; @@ -2017,7 +2022,9 @@ namespace ts { case AssignmentDeclarationKind.None: return Debug.fail("Shouldn't have detected typedef or enum on non-assignment declaration"); } - declareModuleMember(typeAlias, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); + if (container) { + declareModuleMember(typeAlias, SymbolFlags.TypeAlias, SymbolFlags.TypeAliasExcludes); + } container = oldContainer; } } diff --git a/tests/baselines/reference/checkJsdocTypedefOnlySourceFile.errors.txt b/tests/baselines/reference/checkJsdocTypedefOnlySourceFile.errors.txt new file mode 100644 index 0000000000..52b43068c0 --- /dev/null +++ b/tests/baselines/reference/checkJsdocTypedefOnlySourceFile.errors.txt @@ -0,0 +1,18 @@ +tests/cases/conformance/jsdoc/0.js(10,20): error TS2694: Namespace 'exports' has no exported member 'SomeName'. + + +==== tests/cases/conformance/jsdoc/0.js (1 errors) ==== + // @ts-check + + var exports = {}; + + /** + * @typedef {string} + */ + exports.SomeName; + + /** @type {exports.SomeName} */ + ~~~~~~~~ +!!! error TS2694: Namespace 'exports' has no exported member 'SomeName'. + const myString = 'str'; + \ No newline at end of file diff --git a/tests/baselines/reference/checkJsdocTypedefOnlySourceFile.js b/tests/baselines/reference/checkJsdocTypedefOnlySourceFile.js new file mode 100644 index 0000000000..7f4d7f4bcf --- /dev/null +++ b/tests/baselines/reference/checkJsdocTypedefOnlySourceFile.js @@ -0,0 +1,23 @@ +//// [0.js] +// @ts-check + +var exports = {}; + +/** + * @typedef {string} + */ +exports.SomeName; + +/** @type {exports.SomeName} */ +const myString = 'str'; + + +//// [0.js] +// @ts-check +var exports = {}; +/** + * @typedef {string} + */ +exports.SomeName; +/** @type {exports.SomeName} */ +var myString = 'str'; diff --git a/tests/baselines/reference/checkJsdocTypedefOnlySourceFile.symbols b/tests/baselines/reference/checkJsdocTypedefOnlySourceFile.symbols new file mode 100644 index 0000000000..0adf9a9344 --- /dev/null +++ b/tests/baselines/reference/checkJsdocTypedefOnlySourceFile.symbols @@ -0,0 +1,16 @@ +=== tests/cases/conformance/jsdoc/0.js === +// @ts-check + +var exports = {}; +>exports : Symbol(exports, Decl(0.js, 2, 3)) + +/** + * @typedef {string} + */ +exports.SomeName; +>exports : Symbol(exports, Decl(0.js, 2, 3)) + +/** @type {exports.SomeName} */ +const myString = 'str'; +>myString : Symbol(myString, Decl(0.js, 10, 5)) + diff --git a/tests/baselines/reference/checkJsdocTypedefOnlySourceFile.types b/tests/baselines/reference/checkJsdocTypedefOnlySourceFile.types new file mode 100644 index 0000000000..fbb4f3ed0b --- /dev/null +++ b/tests/baselines/reference/checkJsdocTypedefOnlySourceFile.types @@ -0,0 +1,20 @@ +=== tests/cases/conformance/jsdoc/0.js === +// @ts-check + +var exports = {}; +>exports : {} +>{} : {} + +/** + * @typedef {string} + */ +exports.SomeName; +>exports.SomeName : any +>exports : {} +>SomeName : any + +/** @type {exports.SomeName} */ +const myString = 'str'; +>myString : any +>'str' : "str" + diff --git a/tests/cases/conformance/jsdoc/checkJsdocTypedefOnlySourceFile.ts b/tests/cases/conformance/jsdoc/checkJsdocTypedefOnlySourceFile.ts new file mode 100644 index 0000000000..d991d2f6e1 --- /dev/null +++ b/tests/cases/conformance/jsdoc/checkJsdocTypedefOnlySourceFile.ts @@ -0,0 +1,15 @@ +// @allowJS: true +// @suppressOutputPathCheck: true + +// @filename: 0.js +// @ts-check + +var exports = {}; + +/** + * @typedef {string} + */ +exports.SomeName; + +/** @type {exports.SomeName} */ +const myString = 'str'; From c404c08fdefde5306e49df2d87f4d55435ddad44 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 24 Oct 2019 15:57:14 -0700 Subject: [PATCH 46/47] Fix reachability analysis for ?? operator (#34702) * Exclude ?? operator from true/false literal check in createFlowCondition * Accept new API baselines * Add tests * Accept new baselines * Address CR feedback * Accept new API baselines --- src/compiler/binder.ts | 10 ++-- src/compiler/utilities.ts | 4 ++ .../reference/api/tsserverlibrary.d.ts | 1 + tests/baselines/reference/api/typescript.d.ts | 1 + .../reference/nullishCoalescingOperator1.js | 44 +++++++++++++++- .../nullishCoalescingOperator1.symbols | 35 +++++++++++++ .../nullishCoalescingOperator1.types | 51 +++++++++++++++++++ .../nullishCoalescingOperator1.ts | 27 +++++++++- 8 files changed, 166 insertions(+), 7 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 5f36d42955..1cc2932666 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1,3 +1,4 @@ + /* @internal */ namespace ts { export const enum ModuleInstanceState { @@ -951,11 +952,10 @@ namespace ts { if (!expression) { return flags & FlowFlags.TrueCondition ? antecedent : unreachableFlow; } - if (expression.kind === SyntaxKind.TrueKeyword && flags & FlowFlags.FalseCondition || - expression.kind === SyntaxKind.FalseKeyword && flags & FlowFlags.TrueCondition) { - if (!isExpressionOfOptionalChainRoot(expression)) { - return unreachableFlow; - } + if ((expression.kind === SyntaxKind.TrueKeyword && flags & FlowFlags.FalseCondition || + expression.kind === SyntaxKind.FalseKeyword && flags & FlowFlags.TrueCondition) && + !isExpressionOfOptionalChainRoot(expression) && !isNullishCoalesce(expression.parent)) { + return unreachableFlow; } if (!isNarrowingExpression(expression)) { return antecedent; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 12b11eeae2..4bff443754 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -5955,6 +5955,10 @@ namespace ts { return isOptionalChainRoot(node.parent) && node.parent.expression === node; } + export function isNullishCoalesce(node: Node) { + return node.kind === SyntaxKind.BinaryExpression && (node).operatorToken.kind === SyntaxKind.QuestionQuestionToken; + } + export function isNewExpression(node: Node): node is NewExpression { return node.kind === SyntaxKind.NewExpression; } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index c43f28c6e9..c06d040800 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3525,6 +3525,7 @@ declare namespace ts { function isCallExpression(node: Node): node is CallExpression; function isCallChain(node: Node): node is CallChain; function isOptionalChain(node: Node): node is PropertyAccessChain | ElementAccessChain | CallChain; + function isNullishCoalesce(node: Node): boolean; function isNewExpression(node: Node): node is NewExpression; function isTaggedTemplateExpression(node: Node): node is TaggedTemplateExpression; function isTypeAssertion(node: Node): node is TypeAssertion; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index e84b948c93..fe23df054d 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3525,6 +3525,7 @@ declare namespace ts { function isCallExpression(node: Node): node is CallExpression; function isCallChain(node: Node): node is CallChain; function isOptionalChain(node: Node): node is PropertyAccessChain | ElementAccessChain | CallChain; + function isNullishCoalesce(node: Node): boolean; function isNewExpression(node: Node): node is NewExpression; function isTaggedTemplateExpression(node: Node): node is TaggedTemplateExpression; function isTypeAssertion(node: Node): node is TypeAssertion; diff --git a/tests/baselines/reference/nullishCoalescingOperator1.js b/tests/baselines/reference/nullishCoalescingOperator1.js index 87a149c185..9b857a1473 100644 --- a/tests/baselines/reference/nullishCoalescingOperator1.js +++ b/tests/baselines/reference/nullishCoalescingOperator1.js @@ -38,10 +38,36 @@ const cc4 = c4 ?? true; const dd1 = d1 ?? {b: 1}; const dd2 = d2 ?? {b: 1}; const dd3 = d3 ?? {b: 1}; -const dd4 = d4 ?? {b: 1}; +const dd4 = d4 ?? {b: 1}; + +// Repro from #34635 + +declare function foo(): void; + +const maybeBool = false; + +if (!(maybeBool ?? true)) { + foo(); +} + +if (maybeBool ?? true) { + foo(); +} +else { + foo(); +} + +if (false ?? true) { + foo(); +} +else { + foo(); +} + //// [nullishCoalescingOperator1.js] "use strict"; +var _a; var aa1 = (a1 !== null && a1 !== void 0 ? a1 : 'whatever'); var aa2 = (a2 !== null && a2 !== void 0 ? a2 : 'whatever'); var aa3 = (a3 !== null && a3 !== void 0 ? a3 : 'whatever'); @@ -58,3 +84,19 @@ var dd1 = (d1 !== null && d1 !== void 0 ? d1 : { b: 1 }); var dd2 = (d2 !== null && d2 !== void 0 ? d2 : { b: 1 }); var dd3 = (d3 !== null && d3 !== void 0 ? d3 : { b: 1 }); var dd4 = (d4 !== null && d4 !== void 0 ? d4 : { b: 1 }); +var maybeBool = false; +if (!((maybeBool !== null && maybeBool !== void 0 ? maybeBool : true))) { + foo(); +} +if ((maybeBool !== null && maybeBool !== void 0 ? maybeBool : true)) { + foo(); +} +else { + foo(); +} +if (_a = false, (_a !== null && _a !== void 0 ? _a : true)) { + foo(); +} +else { + foo(); +} diff --git a/tests/baselines/reference/nullishCoalescingOperator1.symbols b/tests/baselines/reference/nullishCoalescingOperator1.symbols index fb95dee509..83bd516228 100644 --- a/tests/baselines/reference/nullishCoalescingOperator1.symbols +++ b/tests/baselines/reference/nullishCoalescingOperator1.symbols @@ -123,3 +123,38 @@ const dd4 = d4 ?? {b: 1}; >d4 : Symbol(d4, Decl(nullishCoalescingOperator1.ts, 19, 13)) >b : Symbol(b, Decl(nullishCoalescingOperator1.ts, 39, 19)) +// Repro from #34635 + +declare function foo(): void; +>foo : Symbol(foo, Decl(nullishCoalescingOperator1.ts, 39, 25)) + +const maybeBool = false; +>maybeBool : Symbol(maybeBool, Decl(nullishCoalescingOperator1.ts, 45, 5)) + +if (!(maybeBool ?? true)) { +>maybeBool : Symbol(maybeBool, Decl(nullishCoalescingOperator1.ts, 45, 5)) + + foo(); +>foo : Symbol(foo, Decl(nullishCoalescingOperator1.ts, 39, 25)) +} + +if (maybeBool ?? true) { +>maybeBool : Symbol(maybeBool, Decl(nullishCoalescingOperator1.ts, 45, 5)) + + foo(); +>foo : Symbol(foo, Decl(nullishCoalescingOperator1.ts, 39, 25)) +} +else { + foo(); +>foo : Symbol(foo, Decl(nullishCoalescingOperator1.ts, 39, 25)) +} + +if (false ?? true) { + foo(); +>foo : Symbol(foo, Decl(nullishCoalescingOperator1.ts, 39, 25)) +} +else { + foo(); +>foo : Symbol(foo, Decl(nullishCoalescingOperator1.ts, 39, 25)) +} + diff --git a/tests/baselines/reference/nullishCoalescingOperator1.types b/tests/baselines/reference/nullishCoalescingOperator1.types index 0e800540ee..81aa6a7404 100644 --- a/tests/baselines/reference/nullishCoalescingOperator1.types +++ b/tests/baselines/reference/nullishCoalescingOperator1.types @@ -170,3 +170,54 @@ const dd4 = d4 ?? {b: 1}; >b : number >1 : 1 +// Repro from #34635 + +declare function foo(): void; +>foo : () => void + +const maybeBool = false; +>maybeBool : false +>false : false + +if (!(maybeBool ?? true)) { +>!(maybeBool ?? true) : true +>(maybeBool ?? true) : false +>maybeBool ?? true : false +>maybeBool : false +>true : true + + foo(); +>foo() : void +>foo : () => void +} + +if (maybeBool ?? true) { +>maybeBool ?? true : false +>maybeBool : false +>true : true + + foo(); +>foo() : void +>foo : () => void +} +else { + foo(); +>foo() : void +>foo : () => void +} + +if (false ?? true) { +>false ?? true : false +>false : false +>true : true + + foo(); +>foo() : void +>foo : () => void +} +else { + foo(); +>foo() : void +>foo : () => void +} + diff --git a/tests/cases/conformance/expressions/nullishCoalescingOperator/nullishCoalescingOperator1.ts b/tests/cases/conformance/expressions/nullishCoalescingOperator/nullishCoalescingOperator1.ts index 80f0c64e7d..f7024fd829 100644 --- a/tests/cases/conformance/expressions/nullishCoalescingOperator/nullishCoalescingOperator1.ts +++ b/tests/cases/conformance/expressions/nullishCoalescingOperator/nullishCoalescingOperator1.ts @@ -1,4 +1,5 @@ // @strict: true +// @allowUnreachableCode: false declare const a1: string | undefined | null declare const a2: string | undefined | null @@ -39,4 +40,28 @@ const cc4 = c4 ?? true; const dd1 = d1 ?? {b: 1}; const dd2 = d2 ?? {b: 1}; const dd3 = d3 ?? {b: 1}; -const dd4 = d4 ?? {b: 1}; \ No newline at end of file +const dd4 = d4 ?? {b: 1}; + +// Repro from #34635 + +declare function foo(): void; + +const maybeBool = false; + +if (!(maybeBool ?? true)) { + foo(); +} + +if (maybeBool ?? true) { + foo(); +} +else { + foo(); +} + +if (false ?? true) { + foo(); +} +else { + foo(); +} From 5e4cd0594e885c3e950cb0b2046c930ed8032837 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Thu, 24 Oct 2019 16:49:51 -0700 Subject: [PATCH 47/47] Update CONTRIBUTING.md --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dda3ab28a7..85fd6ddb23 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,11 +59,11 @@ Run `gulp` to build a version of the compiler/language service that reflects cha ## Contributing bug fixes -TypeScript is currently accepting contributions in the form of bug fixes. A bug must have an issue tracking it in the issue tracker that has been approved (labelled ["help wanted"](https://github.com/Microsoft/TypeScript/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22)) by the TypeScript team. Your pull request should include a link to the bug that you are fixing. If you've submitted a PR for a bug, please post a comment in the bug to avoid duplication of effort. +TypeScript is currently accepting contributions in the form of bug fixes. A bug must have an issue tracking it in the issue tracker that has been approved (labelled ["help wanted"](https://github.com/Microsoft/TypeScript/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) or in the "Backlog milestone") by the TypeScript team. Your pull request should include a link to the bug that you are fixing. If you've submitted a PR for a bug, please post a comment in the bug to avoid duplication of effort. ## Contributing features -Features (things that add new or improved functionality to TypeScript) may be accepted, but will need to first be approved ([labelled "help wanted"](https://github.com/Microsoft/TypeScript/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) by a TypeScript project maintainer) in the suggestion issue. Features with language design impact, or that are adequately satisfied with external tools, will not be accepted. +Features (things that add new or improved functionality to TypeScript) may be accepted, but will need to first be approved ([labelled "help wanted"](https://github.com/Microsoft/TypeScript/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22 or in the "Backlog" milestone) by a TypeScript project maintainer) in the suggestion issue. Features with language design impact, or that are adequately satisfied with external tools, will not be accepted. Design changes will not be accepted at this time. If you have a design change proposal, please log a suggestion issue.