Skip to content

Commit

Permalink
Merge branch 'master' into jsx-element-realtypes
Browse files Browse the repository at this point in the history
  • Loading branch information
weswigham committed Feb 28, 2019
2 parents f74105a + 5ec5e04 commit 3c6dc4e
Show file tree
Hide file tree
Showing 185 changed files with 1,813 additions and 695 deletions.
9 changes: 7 additions & 2 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2499,8 +2499,13 @@ namespace ts {
declareSymbol(symbolTable, containingClass.symbol, node, SymbolFlags.Property, SymbolFlags.None, /*isReplaceableByMethod*/ true);
break;
case SyntaxKind.SourceFile:
// this.foo assignment in a source file
// Do not bind. It would be nice to support this someday though.
// this.property = assignment in a source file -- declare symbol in exports for a module, in locals for a script
if ((thisContainer as SourceFile).commonJsModuleIndicator) {
declareSymbol(thisContainer.symbol.exports!, thisContainer.symbol, node, SymbolFlags.Property | SymbolFlags.ExportValue, SymbolFlags.None);
}
else {
declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.FunctionScopedVariableExcludes);
}
break;

default:
Expand Down
75 changes: 55 additions & 20 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,16 @@ namespace ts {
const emitResolver = createResolver();
const nodeBuilder = createNodeBuilder();

const globals = createSymbolTable();
const undefinedSymbol = createSymbol(SymbolFlags.Property, "undefined" as __String);
undefinedSymbol.declarations = [];

const globalThisSymbol = createSymbol(SymbolFlags.Module, "globalThis" as __String, CheckFlags.Readonly);
globalThisSymbol.exports = globals;
globalThisSymbol.valueDeclaration = createNode(SyntaxKind.Identifier) as Identifier;
(globalThisSymbol.valueDeclaration as Identifier).escapedText = "globalThis" as __String;
globals.set(globalThisSymbol.escapedName, globalThisSymbol);

const argumentsSymbol = createSymbol(SymbolFlags.Property, "arguments" as __String);
const requireSymbol = createSymbol(SymbolFlags.Property, "require" as __String);

Expand Down Expand Up @@ -310,9 +318,9 @@ namespace ts {
getAccessibleSymbolChain,
getTypePredicateOfSignature: getTypePredicateOfSignature as (signature: Signature) => TypePredicate, // TODO: GH#18217
resolveExternalModuleSymbol,
tryGetThisTypeAt: node => {
tryGetThisTypeAt: (node, includeGlobalThis) => {
node = getParseTreeNode(node);
return node && tryGetThisTypeAt(node);
return node && tryGetThisTypeAt(node, includeGlobalThis);
},
getTypeArgumentConstraint: nodeIn => {
const node = getParseTreeNode(nodeIn, isTypeNode);
Expand Down Expand Up @@ -459,7 +467,6 @@ namespace ts {

const enumNumberIndexInfo = createIndexInfo(stringType, /*isReadonly*/ true);

const globals = createSymbolTable();
interface DuplicateInfoForSymbol {
readonly firstFileLocations: Node[];
readonly secondFileLocations: Node[];
Expand Down Expand Up @@ -9711,7 +9718,7 @@ namespace ts {
}

function getLiteralTypeFromProperties(type: Type, include: TypeFlags) {
return getUnionType(map(getPropertiesOfType(type), t => getLiteralTypeFromProperty(t, include)));
return getUnionType(map(getPropertiesOfType(type), p => getLiteralTypeFromProperty(p, include)));
}

function getNonEnumNumberIndexInfo(type: Type) {
Expand Down Expand Up @@ -12830,20 +12837,30 @@ namespace ts {
}
return Ternary.False;

function isNonGeneric(type: Type) {
// If we're already in identity relationship checking, we should use `isRelatedTo`
// to catch the `Maybe` from an excessively deep type (which we then assume means
// that the type could possibly contain a generic)
if (relation === identityRelation) {
return isRelatedTo(type, getPermissiveInstantiation(type)) === Ternary.True;
}
return isTypeIdenticalTo(type, getPermissiveInstantiation(type));
}

function relateVariances(sourceTypeArguments: ReadonlyArray<Type> | undefined, targetTypeArguments: ReadonlyArray<Type> | undefined, variances: Variance[]) {
if (result = typeArgumentsRelatedTo(sourceTypeArguments, targetTypeArguments, variances, reportErrors)) {
return result;
}
const isCovariantVoid = targetTypeArguments && hasCovariantVoidArgument(targetTypeArguments, variances);
varianceCheckFailed = !isCovariantVoid;
const allowStructuralFallback = (targetTypeArguments && hasCovariantVoidArgument(targetTypeArguments, variances)) || isNonGeneric(source) || isNonGeneric(target);
varianceCheckFailed = !allowStructuralFallback;
// The type arguments did not relate appropriately, but it may be because we have no variance
// information (in which case typeArgumentsRelatedTo defaulted to covariance for all type
// arguments). It might also be the case that the target type has a 'void' type argument for
// a covariant type parameter that is only used in return positions within the generic type
// (in which case any type argument is permitted on the source side). In those cases we proceed
// with a structural comparison. Otherwise, we know for certain the instantiations aren't
// related and we can return here.
if (variances !== emptyArray && !isCovariantVoid) {
if (variances !== emptyArray && !allowStructuralFallback) {
// In some cases generic types that are covariant in regular type checking mode become
// invariant in --strictFunctionTypes mode because one or more type parameters are used in
// both co- and contravariant positions. In order to make it easier to diagnose *why* such
Expand Down Expand Up @@ -16988,25 +17005,27 @@ namespace ts {
captureLexicalThis(node, container);
}

const type = tryGetThisTypeAt(node, container);
if (!type && noImplicitThis) {
// With noImplicitThis, functions may not reference 'this' if it has type 'any'
const diag = error(
node,
capturedByArrowFunction && container.kind === SyntaxKind.SourceFile ?
Diagnostics.The_containing_arrow_function_captures_the_global_value_of_this_which_implicitly_has_type_any :
Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation);
if (!isSourceFile(container)) {
const outsideThis = tryGetThisTypeAt(container);
if (outsideThis) {
addRelatedInfo(diag, createDiagnosticForNode(container, Diagnostics.An_outer_value_of_this_is_shadowed_by_this_container));
const type = tryGetThisTypeAt(node, /*includeGlobalThis*/ true, container);
if (noImplicitThis) {
const globalThisType = getTypeOfSymbol(globalThisSymbol);
if (type === globalThisType && capturedByArrowFunction) {
error(node, Diagnostics.The_containing_arrow_function_captures_the_global_value_of_this);
}
else if (!type) {
// With noImplicitThis, functions may not reference 'this' if it has type 'any'
const diag = error(node, Diagnostics.this_implicitly_has_type_any_because_it_does_not_have_a_type_annotation);
if (!isSourceFile(container)) {
const outsideThis = tryGetThisTypeAt(container);
if (outsideThis && outsideThis !== globalThisType) {
addRelatedInfo(diag, createDiagnosticForNode(container, Diagnostics.An_outer_value_of_this_is_shadowed_by_this_container));
}
}
}
}
return type || anyType;
}

function tryGetThisTypeAt(node: Node, container = getThisContainer(node, /*includeArrowFunctions*/ false)): Type | undefined {
function tryGetThisTypeAt(node: Node, includeGlobalThis = true, container = getThisContainer(node, /*includeArrowFunctions*/ false)): Type | undefined {
const isInJS = isInJSFile(node);
if (isFunctionLike(container) &&
(!isInParameterInitializerBeforeContainingFunction(node) || getThisParameter(container))) {
Expand Down Expand Up @@ -17053,6 +17072,16 @@ namespace ts {
return getFlowTypeOfReference(node, type);
}
}
if (isSourceFile(container)) {
// look up in the source file's locals or exports
if (container.commonJsModuleIndicator) {
const fileSymbol = getSymbolOfNode(container);
return fileSymbol && getTypeOfSymbol(fileSymbol);
}
else if (includeGlobalThis) {
return getTypeOfSymbol(globalThisSymbol);
}
}
}

function getClassNameFromPrototypeMethod(container: Node) {
Expand Down Expand Up @@ -19393,6 +19422,12 @@ namespace ts {
if (isJSLiteralType(leftType)) {
return anyType;
}
if (leftType.symbol === globalThisSymbol) {
if (noImplicitAny) {
error(right, Diagnostics.Element_implicitly_has_an_any_type_because_type_0_has_no_index_signature, typeToString(leftType));
}
return anyType;
}
if (right.escapedText && !checkAndReportErrorForExtendingInterface(node)) {
reportNonexistentProperty(right, leftType.flags & TypeFlags.TypeParameter && (leftType as TypeParameter).isThisType ? apparentType : leftType);
}
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -4149,7 +4149,7 @@
"category": "Error",
"code": 7040
},
"The containing arrow function captures the global value of 'this' which implicitly has type 'any'.": {
"The containing arrow function captures the global value of 'this'.": {
"category": "Error",
"code": 7041
},
Expand Down
12 changes: 11 additions & 1 deletion src/compiler/sys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -615,7 +615,17 @@ namespace ts {
directoryExists,
createDirectory(directoryName: string) {
if (!nodeSystem.directoryExists(directoryName)) {
_fs.mkdirSync(directoryName);
// Wrapped in a try-catch to prevent crashing if we are in a race
// with another copy of ourselves to create the same directory
try {
_fs.mkdirSync(directoryName);
}
catch (e) {
if (e.code !== "EEXIST") {
// Failed for some other reason (access denied?); still throw
throw e;
}
}
}
},
getExecutingFilePath() {
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3223,7 +3223,7 @@ namespace ts {
*/
/* @internal */ resolveExternalModuleSymbol(symbol: Symbol): Symbol;
/** @param node A location where we might consider accessing `this`. Not necessarily a ThisExpression. */
/* @internal */ tryGetThisTypeAt(node: Node): Type | undefined;
/* @internal */ tryGetThisTypeAt(node: Node, includeGlobalThis?: boolean): Type | undefined;
/* @internal */ getTypeArgumentConstraint(node: TypeNode): Type | undefined;

/**
Expand Down
18 changes: 13 additions & 5 deletions src/harness/fourslash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,7 @@ namespace FourSlash {
if ("exact" in options) {
ts.Debug.assert(!("includes" in options) && !("excludes" in options));
if (options.exact === undefined) throw this.raiseError("Expected no completions");
this.verifyCompletionsAreExactly(actualCompletions.entries, toArray(options.exact));
this.verifyCompletionsAreExactly(actualCompletions.entries, toArray(options.exact), options.marker);
}
else {
if (options.includes) {
Expand Down Expand Up @@ -841,14 +841,14 @@ namespace FourSlash {
}
}

private verifyCompletionsAreExactly(actual: ReadonlyArray<ts.CompletionEntry>, expected: ReadonlyArray<FourSlashInterface.ExpectedCompletionEntry>) {
private verifyCompletionsAreExactly(actual: ReadonlyArray<ts.CompletionEntry>, expected: ReadonlyArray<FourSlashInterface.ExpectedCompletionEntry>, marker?: ArrayOrSingle<string | Marker>) {
// First pass: test that names are right. Then we'll test details.
assert.deepEqual(actual.map(a => a.name), expected.map(e => typeof e === "string" ? e : e.name));
assert.deepEqual(actual.map(a => a.name), expected.map(e => typeof e === "string" ? e : e.name), marker ? "At marker " + JSON.stringify(marker) : undefined);

ts.zipWith(actual, expected, (completion, expectedCompletion, index) => {
const name = typeof expectedCompletion === "string" ? expectedCompletion : expectedCompletion.name;
if (completion.name !== name) {
this.raiseError(`Expected completion at index ${index} to be ${name}, got ${completion.name}`);
this.raiseError(`${marker ? JSON.stringify(marker) : "" } Expected completion at index ${index} to be ${name}, got ${completion.name}`);
}
this.verifyCompletionEntry(completion, expectedCompletion);
});
Expand Down Expand Up @@ -4545,6 +4545,7 @@ namespace FourSlashInterface {

export function globalTypesPlus(plus: ReadonlyArray<ExpectedCompletionEntry>): ReadonlyArray<ExpectedCompletionEntry> {
return [
{ name: "globalThis", kind: "module" },
...globalTypeDecls,
...plus,
...typeKeywords,
Expand Down Expand Up @@ -4786,6 +4787,7 @@ namespace FourSlashInterface {
export const globalsInsideFunction = (plus: ReadonlyArray<ExpectedCompletionEntry>): ReadonlyArray<ExpectedCompletionEntry> => [
{ name: "arguments", kind: "local var" },
...plus,
{ name: "globalThis", kind: "module" },
...globalsVars,
{ name: "undefined", kind: "var" },
...globalKeywordsInsideFunction,
Expand Down Expand Up @@ -4921,13 +4923,19 @@ namespace FourSlashInterface {
})();

export const globals: ReadonlyArray<ExpectedCompletionEntryObject> = [
{ name: "globalThis", kind: "module" },
...globalsVars,
{ name: "undefined", kind: "var" },
...globalKeywords
];

export function globalsPlus(plus: ReadonlyArray<ExpectedCompletionEntry>): ReadonlyArray<ExpectedCompletionEntry> {
return [...globalsVars, ...plus, { name: "undefined", kind: "var" }, ...globalKeywords];
return [
{ name: "globalThis", kind: "module" },
...globalsVars,
...plus,
{ name: "undefined", kind: "var" },
...globalKeywords];
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/services/completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1030,7 +1030,7 @@ namespace ts.Completions {

// Need to insert 'this.' before properties of `this` type, so only do that if `includeInsertTextCompletions`
if (preferences.includeCompletionsWithInsertText && scopeNode.kind !== SyntaxKind.SourceFile) {
const thisType = typeChecker.tryGetThisTypeAt(scopeNode);
const thisType = typeChecker.tryGetThisTypeAt(scopeNode, /*includeGlobalThis*/ false);
if (thisType) {
for (const symbol of getPropertiesForCompletion(thisType, typeChecker)) {
symbolToOriginInfoMap[getSymbolId(symbol)] = { kind: SymbolOriginInfoKind.ThisType };
Expand Down
2 changes: 1 addition & 1 deletion src/services/symbolDisplay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ namespace ts.SymbolDisplay {
displayParts.push(spacePart());
addFullSymbolName(symbol);
}
if (symbolFlags & SymbolFlags.Module) {
if (symbolFlags & SymbolFlags.Module && !isThisExpression) {
prefixNextMeaning();
const declaration = getDeclarationOfKind<ModuleDeclaration>(symbol, SyntaxKind.ModuleDeclaration);
const isNamespace = declaration && declaration.name && declaration.name.kind === SyntaxKind.Identifier;
Expand Down
3 changes: 2 additions & 1 deletion src/testRunner/unittests/tsserver/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -708,7 +708,8 @@ namespace ts.projectSystem {
// Check identifiers defined in HTML content are available in .ts file
const project = configuredProjectAt(projectService, 0);
let completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 1, emptyOptions);
assert(completions && completions.entries[0].name === "hello", `expected entry hello to be in completion list`);
assert(completions && completions.entries[1].name === "hello", `expected entry hello to be in completion list`);
assert(completions && completions.entries[0].name === "globalThis", `first entry should be globalThis (not strictly relevant for this test).`);

// Close HTML file
projectService.applyChangesInOpenFiles(
Expand Down
2 changes: 2 additions & 0 deletions tests/baselines/reference/assignmentLHSIsValue.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ function foo() { this = value; }
>value : Symbol(value, Decl(assignmentLHSIsValue.ts, 1, 3))

this = value;
>this : Symbol(globalThis)
>value : Symbol(value, Decl(assignmentLHSIsValue.ts, 1, 3))

// identifiers: module, class, enum, function
Expand Down Expand Up @@ -116,6 +117,7 @@ foo() = value;

// parentheses, the containted expression is value
(this) = value;
>this : Symbol(globalThis)
>value : Symbol(value, Decl(assignmentLHSIsValue.ts, 1, 3))

(M) = value;
Expand Down
6 changes: 3 additions & 3 deletions tests/baselines/reference/assignmentLHSIsValue.types
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function foo() { this = value; }

this = value;
>this = value : any
>this : any
>this : typeof globalThis
>value : any

// identifiers: module, class, enum, function
Expand Down Expand Up @@ -159,8 +159,8 @@ foo() = value;
// parentheses, the containted expression is value
(this) = value;
>(this) = value : any
>(this) : any
>this : any
>(this) : typeof globalThis
>this : typeof globalThis
>value : any

(M) = value;
Expand Down
4 changes: 4 additions & 0 deletions tests/baselines/reference/castExpressionParentheses.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ declare var a;
(<any>null);
// names and dotted names
(<any>this);
>this : Symbol(globalThis)

(<any>this.x);
>this : Symbol(globalThis)

(<any>(<any>a).x);
>a : Symbol(a, Decl(castExpressionParentheses.ts, 0, 11))

Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/castExpressionParentheses.types
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,13 @@ declare var a;
(<any>this);
>(<any>this) : any
><any>this : any
>this : any
>this : typeof globalThis

(<any>this.x);
>(<any>this.x) : any
><any>this.x : any
>this.x : any
>this : any
>this : typeof globalThis
>x : any

(<any>(<any>a).x);
Expand Down
Loading

0 comments on commit 3c6dc4e

Please sign in to comment.