Skip to content

Commit a7e6c36

Browse files
committed
Merge branch 'master' into fixIntersectionTypeInference
2 parents 63f7029 + a8d3cd6 commit a7e6c36

62 files changed

Lines changed: 1331 additions & 1077 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Gulpfile.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1106,7 +1106,7 @@ gulp.task("lint", "Runs tslint on the compiler sources. Optional arguments are:
11061106
const fileMatcher = cmdLineOptions.files;
11071107
const files = fileMatcher
11081108
? `src/**/${fileMatcher}`
1109-
: "Gulpfile.ts 'scripts/generateLocalizedDiagnosticMessages.ts' 'scripts/tslint/**/*.ts' 'src/**/*.ts' --exclude src/lib/es5.d.ts --exclude 'src/lib/*.generated.d.ts'";
1109+
: "Gulpfile.ts 'scripts/generateLocalizedDiagnosticMessages.ts' 'scripts/tslint/**/*.ts' 'src/**/*.ts' --exclude 'src/lib/*.d.ts'";
11101110
const cmd = `node node_modules/tslint/bin/tslint ${files} --formatters-dir ./built/local/tslint/formatters --format autolinkableStylish`;
11111111
console.log("Linting: " + cmd);
11121112
child_process.execSync(cmd, { stdio: [0, 1, 2] });

Jakefile.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1283,7 +1283,7 @@ task("lint", ["build-rules"], () => {
12831283
const fileMatcher = process.env.f || process.env.file || process.env.files;
12841284
const files = fileMatcher
12851285
? `src/**/${fileMatcher}`
1286-
: "Gulpfile.ts 'scripts/generateLocalizedDiagnosticMessages.ts' 'scripts/tslint/**/*.ts' 'src/**/*.ts' --exclude src/lib/es5.d.ts --exclude 'src/lib/*.generated.d.ts'";
1286+
: "Gulpfile.ts 'scripts/generateLocalizedDiagnosticMessages.ts' 'scripts/tslint/**/*.ts' 'src/**/*.ts' --exclude 'src/lib/*.d.ts'";
12871287
const cmd = `node node_modules/tslint/bin/tslint ${files} --formatters-dir ./built/local/tslint/formatters --format autolinkableStylish`;
12881288
console.log("Linting: " + cmd);
12891289
jake.exec([cmd], { interactive: true }, () => {

src/compiler/checker.ts

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7440,9 +7440,12 @@ namespace ts {
74407440
return false;
74417441
}
74427442

7443-
function isSubtypeOfAny(candidate: Type, types: Type[]): boolean {
7444-
for (const type of types) {
7445-
if (candidate !== type && isTypeSubtypeOf(candidate, type)) {
7443+
function isSubtypeOfAny(source: Type, targets: Type[]): boolean {
7444+
for (const target of targets) {
7445+
if (source !== target && isTypeSubtypeOf(source, target) && (
7446+
!(getObjectFlags(getTargetType(source)) & ObjectFlags.Class) ||
7447+
!(getObjectFlags(getTargetType(target)) & ObjectFlags.Class) ||
7448+
isTypeDerivedFrom(source, target))) {
74467449
return true;
74477450
}
74487451
}
@@ -7589,11 +7592,11 @@ namespace ts {
75897592
}
75907593
}
75917594

7592-
// Add the given types to the given type set. Order is preserved, duplicates are removed,
7593-
// and nested types of the given kind are flattened into the set.
7595+
// Add the given types to the given type set. Order is preserved, freshness is removed from literal
7596+
// types, duplicates are removed, and nested types of the given kind are flattened into the set.
75947597
function addTypesToIntersection(typeSet: TypeSet, types: Type[]) {
75957598
for (const type of types) {
7596-
addTypeToIntersection(typeSet, type);
7599+
addTypeToIntersection(typeSet, getRegularTypeOfLiteralType(type));
75977600
}
75987601
}
75997602

@@ -8565,12 +8568,19 @@ namespace ts {
85658568
return isTypeRelatedTo(source, target, assignableRelation);
85668569
}
85678570

8568-
// A type S is considered to be an instance of a type T if S and T are the same type or if S is a
8569-
// subtype of T but not structurally identical to T. This specifically means that two distinct but
8570-
// structurally identical types (such as two classes) are not considered instances of each other.
8571-
function isTypeInstanceOf(source: Type, target: Type): boolean {
8572-
return getTargetType(source) === getTargetType(target) || isTypeSubtypeOf(source, target) && !isTypeIdenticalTo(source, target);
8573-
}
8571+
// An object type S is considered to be derived from an object type T if
8572+
// S is a union type and every constituent of S is derived from T,
8573+
// T is a union type and S is derived from at least one constituent of T, or
8574+
// T is one of the global types Object and Function and S is a subtype of T, or
8575+
// T occurs directly or indirectly in an 'extends' clause of S.
8576+
// Note that this check ignores type parameters and only considers the
8577+
// inheritance hierarchy.
8578+
function isTypeDerivedFrom(source: Type, target: Type): boolean {
8579+
return source.flags & TypeFlags.Union ? every((<UnionType>source).types, t => isTypeDerivedFrom(t, target)) :
8580+
target.flags & TypeFlags.Union ? some((<UnionType>target).types, t => isTypeDerivedFrom(source, t)) :
8581+
target === globalObjectType || target === globalFunctionType ? isTypeSubtypeOf(source, target) :
8582+
hasBaseType(source, getTargetType(target));
8583+
}
85748584

85758585
/**
85768586
* This is *not* a bi-directional relationship.
@@ -9604,7 +9614,7 @@ namespace ts {
96049614
if (relation === identityRelation) {
96059615
return propertiesIdenticalTo(source, target);
96069616
}
9607-
const requireOptionalProperties = relation === subtypeRelation && !isObjectLiteralType(source);
9617+
const requireOptionalProperties = relation === subtypeRelation && !isObjectLiteralType(source) && !isEmptyArrayLiteralType(source);
96089618
const unmatchedProperty = getUnmatchedProperty(source, target, requireOptionalProperties);
96099619
if (unmatchedProperty) {
96109620
if (reportErrors) {
@@ -10312,6 +10322,11 @@ namespace ts {
1031210322
!(type.flags & TypeFlags.Nullable) && isTypeAssignableTo(type, anyReadonlyArrayType);
1031310323
}
1031410324

10325+
function isEmptyArrayLiteralType(type: Type): boolean {
10326+
const elementType = isArrayType(type) ? (<TypeReference>type).typeArguments[0] : undefined;
10327+
return elementType === undefinedWideningType || elementType === neverType;
10328+
}
10329+
1031510330
function isTupleLikeType(type: Type): boolean {
1031610331
return !!getPropertyOfType(type, "0" as __String);
1031710332
}
@@ -12415,7 +12430,7 @@ namespace ts {
1241512430
}
1241612431

1241712432
if (targetType) {
12418-
return getNarrowedType(type, targetType, assumeTrue, isTypeInstanceOf);
12433+
return getNarrowedType(type, targetType, assumeTrue, isTypeDerivedFrom);
1241912434
}
1242012435

1242112436
return type;
@@ -13883,7 +13898,6 @@ namespace ts {
1388313898
type.pattern = node;
1388413899
return type;
1388513900
}
13886-
const contextualType = getApparentTypeOfContextualType(node);
1388713901
if (contextualType && contextualTypeIsTupleLikeType(contextualType)) {
1388813902
const pattern = contextualType.pattern;
1388913903
// If array literal is contextually typed by a binding pattern or an assignment pattern, pad the resulting
@@ -18087,14 +18101,6 @@ namespace ts {
1808718101
return (target.flags & TypeFlags.Nullable) !== 0 || isTypeComparableTo(source, target);
1808818102
}
1808918103

18090-
function getBestChoiceType(type1: Type, type2: Type): Type {
18091-
const firstAssignableToSecond = isTypeAssignableTo(type1, type2);
18092-
const secondAssignableToFirst = isTypeAssignableTo(type2, type1);
18093-
return secondAssignableToFirst && !firstAssignableToSecond ? type1 :
18094-
firstAssignableToSecond && !secondAssignableToFirst ? type2 :
18095-
getUnionType([type1, type2], /*subtypeReduction*/ true);
18096-
}
18097-
1809818104
function checkBinaryExpression(node: BinaryExpression, checkMode?: CheckMode) {
1809918105
return checkBinaryLikeExpression(node.left, node.operatorToken, node.right, checkMode, node);
1810018106
}
@@ -18231,7 +18237,7 @@ namespace ts {
1823118237
leftType;
1823218238
case SyntaxKind.BarBarToken:
1823318239
return getTypeFacts(leftType) & TypeFacts.Falsy ?
18234-
getBestChoiceType(removeDefinitelyFalsyTypes(leftType), rightType) :
18240+
getUnionType([removeDefinitelyFalsyTypes(leftType), rightType], /*subtypeReduction*/ true) :
1823518241
leftType;
1823618242
case SyntaxKind.EqualsToken:
1823718243
checkAssignmentOperator(rightType);
@@ -18391,7 +18397,7 @@ namespace ts {
1839118397
checkExpression(node.condition);
1839218398
const type1 = checkExpression(node.whenTrue, checkMode);
1839318399
const type2 = checkExpression(node.whenFalse, checkMode);
18394-
return getBestChoiceType(type1, type2);
18400+
return getUnionType([type1, type2], /*subtypeReduction*/ true);
1839518401
}
1839618402

1839718403
function checkTemplateExpression(node: TemplateExpression): Type {
@@ -23638,6 +23644,8 @@ namespace ts {
2363823644
return objectType && getPropertyOfType(objectType, escapeLeadingUnderscores((node as StringLiteral | NumericLiteral).text));
2363923645

2364023646
case SyntaxKind.DefaultKeyword:
23647+
case SyntaxKind.FunctionKeyword:
23648+
case SyntaxKind.EqualsGreaterThanToken:
2364123649
return getSymbolOfNode(node.parent);
2364223650

2364323651
default:

src/compiler/factory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2641,7 +2641,7 @@ namespace ts {
26412641
/**
26422642
* Gets a custom text range to use when emitting source maps.
26432643
*/
2644-
export function getSourceMapRange(node: Node) {
2644+
export function getSourceMapRange(node: Node): SourceMapRange {
26452645
const emitNode = node.emitNode;
26462646
return (emitNode && emitNode.sourceMapRange) || node;
26472647
}

src/compiler/resolutionCache.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,8 @@ namespace ts {
6969

7070
export const maxNumberOfFilesToIterateForInvalidation = 256;
7171

72-
interface GetResolutionWithResolvedFileName<T extends ResolutionWithFailedLookupLocations = ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName = ResolutionWithResolvedFileName> {
73-
(resolution: T): R;
74-
}
72+
type GetResolutionWithResolvedFileName<T extends ResolutionWithFailedLookupLocations = ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName = ResolutionWithResolvedFileName> =
73+
(resolution: T) => R;
7574

7675
export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootDirForResolution: string): ResolutionCache {
7776
let filesWithChangedSetOfUnresolvedImports: Path[] | undefined;
@@ -320,6 +319,10 @@ namespace ts {
320319
return endsWith(dirPath, "/node_modules");
321320
}
322321

322+
function isNodeModulesAtTypesDirectory(dirPath: Path) {
323+
return endsWith(dirPath, "/node_modules/@types");
324+
}
325+
323326
function isDirectoryAtleastAtLevelFromFSRoot(dirPath: Path, minLevels: number) {
324327
for (let searchIndex = getRootLength(dirPath); minLevels > 0; minLevels--) {
325328
searchIndex = dirPath.indexOf(directorySeparator, searchIndex) + 1;
@@ -560,11 +563,21 @@ namespace ts {
560563
else {
561564
// Some file or directory in the watching directory is created
562565
// Return early if it does not have any of the watching extension or not the custom failed lookup path
563-
if (!isPathWithDefaultFailedLookupExtension(fileOrDirectoryPath) && !customFailedLookupPaths.has(fileOrDirectoryPath)) {
564-
return false;
566+
const dirOfFileOrDirectory = getDirectoryPath(fileOrDirectoryPath);
567+
if (isNodeModulesAtTypesDirectory(dirOfFileOrDirectory) || isNodeModulesDirectory(dirOfFileOrDirectory)) {
568+
// Invalidate any resolution from this directory
569+
isChangedFailedLookupLocation = location => {
570+
const locationPath = resolutionHost.toPath(location);
571+
return locationPath === fileOrDirectoryPath || startsWith(resolutionHost.toPath(location), fileOrDirectoryPath);
572+
};
573+
}
574+
else {
575+
if (!isPathWithDefaultFailedLookupExtension(fileOrDirectoryPath) && !customFailedLookupPaths.has(fileOrDirectoryPath)) {
576+
return false;
577+
}
578+
// Resolution need to be invalidated if failed lookup location is same as the file or directory getting created
579+
isChangedFailedLookupLocation = location => resolutionHost.toPath(location) === fileOrDirectoryPath;
565580
}
566-
// Resolution need to be invalidated if failed lookup location is same as the file or directory getting created
567-
isChangedFailedLookupLocation = location => resolutionHost.toPath(location) === fileOrDirectoryPath;
568581
}
569582
const hasChangedFailedLookupLocation = (resolution: ResolutionWithFailedLookupLocations) => some(resolution.failedLookupLocations, isChangedFailedLookupLocation);
570583
const invalidatedFilesCount = filesWithInvalidatedResolutions && filesWithInvalidatedResolutions.size;

src/compiler/scanner.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22
/// <reference path="diagnosticInformationMap.generated.ts"/>
33

44
namespace ts {
5-
export interface ErrorCallback {
6-
(message: DiagnosticMessage, length: number): void;
7-
}
5+
export type ErrorCallback = (message: DiagnosticMessage, length: number) => void;
86

97
/* @internal */
108
export function tokenIsIdentifierOrKeyword(token: SyntaxKind): boolean {

src/compiler/transformers/ts.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,13 @@ namespace ts {
225225
if (parsed !== node) {
226226
// If the node has been transformed by a `before` transformer, perform no ellision on it
227227
// As the type information we would attempt to lookup to perform ellision is potentially unavailable for the synthesized nodes
228+
// We do not reuse `visitorWorker`, as the ellidable statement syntax kinds are technically unrecognized by the switch-case in `visitTypeScript`,
229+
// and will trigger debug failures when debug verbosity is turned up
230+
if (node.transformFlags & TransformFlags.ContainsTypeScript) {
231+
// This node contains TypeScript, so we should visit its children.
232+
return visitEachChild(node, visitor, context);
233+
}
234+
// Otherwise, we can just return the node
228235
return node;
229236
}
230237
switch (node.kind) {

src/compiler/types.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2471,9 +2471,13 @@ namespace ts {
24712471
readFile(path: string): string | undefined;
24722472
}
24732473

2474-
export interface WriteFileCallback {
2475-
(fileName: string, data: string, writeByteOrderMark: boolean, onError: ((message: string) => void) | undefined, sourceFiles: ReadonlyArray<SourceFile>): void;
2476-
}
2474+
export type WriteFileCallback = (
2475+
fileName: string,
2476+
data: string,
2477+
writeByteOrderMark: boolean,
2478+
onError: ((message: string) => void) | undefined,
2479+
sourceFiles: ReadonlyArray<SourceFile>,
2480+
) => void;
24772481

24782482
export class OperationCanceledException { }
24792483

@@ -3582,9 +3586,7 @@ namespace ts {
35823586
}
35833587

35843588
/* @internal */
3585-
export interface TypeMapper {
3586-
(t: TypeParameter): Type;
3587-
}
3589+
export type TypeMapper = (t: TypeParameter) => Type;
35883590

35893591
export const enum InferencePriority {
35903592
Contravariant = 1 << 0, // Inference from contravariant position
@@ -4192,9 +4194,7 @@ namespace ts {
41924194
}
41934195

41944196
/* @internal */
4195-
export interface HasInvalidatedResolution {
4196-
(sourceFile: Path): boolean;
4197-
}
4197+
export type HasInvalidatedResolution = (sourceFile: Path) => boolean;
41984198

41994199
export interface CompilerHost extends ModuleResolutionHost {
42004200
getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void, shouldCreateNewSourceFile?: boolean): SourceFile | undefined;

src/harness/fourslash.ts

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -873,37 +873,34 @@ namespace FourSlash {
873873
* @param spanIndex the index of the range that the completion item's replacement text span should match
874874
*/
875875
public verifyCompletionListDoesNotContain(entryId: ts.Completions.CompletionEntryIdentifier, expectedText?: string, expectedDocumentation?: string, expectedKind?: string, spanIndex?: number) {
876-
const that = this;
877876
let replacementSpan: ts.TextSpan;
878877
if (spanIndex !== undefined) {
879878
replacementSpan = this.getTextSpanForRangeAtIndex(spanIndex);
880879
}
881880

882-
function filterByTextOrDocumentation(entry: ts.CompletionEntry) {
883-
const details = that.getCompletionEntryDetails(entry.name);
884-
const documentation = details && ts.displayPartsToString(details.documentation);
885-
const text = details && ts.displayPartsToString(details.displayParts);
886-
887-
// If any of the expected values are undefined, assume that users don't
888-
// care about them.
889-
if (replacementSpan && !TestState.textSpansEqual(replacementSpan, entry.replacementSpan)) {
890-
return false;
891-
}
892-
else if (expectedText && text !== expectedText) {
893-
return false;
894-
}
895-
else if (expectedDocumentation && documentation !== expectedDocumentation) {
896-
return false;
897-
}
898-
899-
return true;
900-
}
901-
902881
const completions = this.getCompletionListAtCaret();
903882
if (completions) {
904883
let filterCompletions = completions.entries.filter(e => e.name === entryId.name && e.source === entryId.source);
905884
filterCompletions = expectedKind ? filterCompletions.filter(e => e.kind === expectedKind) : filterCompletions;
906-
filterCompletions = filterCompletions.filter(filterByTextOrDocumentation);
885+
filterCompletions = filterCompletions.filter(entry => {
886+
const details = this.getCompletionEntryDetails(entry.name);
887+
const documentation = details && ts.displayPartsToString(details.documentation);
888+
const text = details && ts.displayPartsToString(details.displayParts);
889+
890+
// If any of the expected values are undefined, assume that users don't
891+
// care about them.
892+
if (replacementSpan && !TestState.textSpansEqual(replacementSpan, entry.replacementSpan)) {
893+
return false;
894+
}
895+
else if (expectedText && text !== expectedText) {
896+
return false;
897+
}
898+
else if (expectedDocumentation && documentation !== expectedDocumentation) {
899+
return false;
900+
}
901+
902+
return true;
903+
});
907904
if (filterCompletions.length !== 0) {
908905
// After filtered using all present criterion, if there are still symbol left in the list
909906
// then these symbols must meet the criterion for Not supposed to be in the list. So we

0 commit comments

Comments
 (0)