Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add globalThis #29332

Merged
merged 27 commits into from
Feb 27, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a5484dd
Restore original code from bind-toplevel-this
sandersn Jan 7, 2019
f650492
Working in JS, but the symbol is not right.
sandersn Jan 8, 2019
4bab559
Check in TS also; update some tests
sandersn Jan 8, 2019
d62a8d8
Update baselines
sandersn Jan 8, 2019
f07c5d6
Handle type references to globalThis
sandersn Jan 9, 2019
80558a4
Restore former behaviour of implicitThis errors
sandersn Jan 9, 2019
e3fbe5c
Test values with type globalThis
sandersn Jan 9, 2019
88c6f7d
Add esnext declaration for globalThis
sandersn Jan 9, 2019
f1ebbad
Merge branch 'master' into add-globalThis
sandersn Jan 9, 2019
4215a76
Switch to symbol-based approach
sandersn Jan 9, 2019
620177f
Merge branch 'master' into add-globalThis
sandersn Jan 9, 2019
d48cc4c
Do not suggest globals for completions at toplevel
sandersn Jan 9, 2019
719cc35
Add tests of element and property access
sandersn Jan 9, 2019
6b18334
Look up globalThis using normal resolution
sandersn Jan 16, 2019
7a3d714
Update fourslash tests
sandersn Jan 16, 2019
fc10811
Merge branch 'master' into add-globalThis
sandersn Jan 16, 2019
9fe7d87
Add missed fourslash test update
sandersn Jan 16, 2019
9db574a
Remove esnext.globalthis.d.ts too
sandersn Jan 17, 2019
3b27dc4
Add chained globalThis self-lookup test
sandersn Jan 18, 2019
1bd4a0d
Merge branch 'master' into add-globalThis
sandersn Jan 18, 2019
e5216f6
Merge branch 'master' into add-globalThis
sandersn Jan 25, 2019
d4d5be4
Attempt at making globalThis readonly
sandersn Jan 29, 2019
3ac9fac
Merge branch 'master' into add-globalThis
sandersn Feb 19, 2019
00312cd
Add/update tests
sandersn Feb 19, 2019
25ad4d1
Merge branch 'master' into add-globalThis
sandersn Feb 21, 2019
6b674f2
Merge branch 'master' into add-globalThis
sandersn Feb 27, 2019
517dc15
Addres PR comments:
sandersn Feb 27, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
59 changes: 42 additions & 17 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 @@ -9703,7 +9710,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 @@ -16981,25 +16988,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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than switching on noImplicitThis out here, shouldn't we use errorOrSuggestion (switching on noImplicitThis) instead of error so we get suggestions for these issues even when noImplicitThis is off?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestions are only used for triggering codefixes, I think. And there aren't any codefixes for these errors. If both of these are true, then let's just wait until we have a codefix for them.

And I can't think of a good codefix for any of the errors except perhaps "The containing arrow function captures the global value of this", which would convert the arrow function to a function expression.

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 @@ -17046,6 +17055,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 @@ -19343,6 +19362,12 @@ namespace ts {
if (isJSLiteralType(leftType)) {
return anyType;
}
if (leftType.symbol === globalThisSymbol) {
if (noImplicitAny) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, but for 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
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`);
sandersn marked this conversation as resolved.
Show resolved Hide resolved
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module a {
}
var f = () => this;
>f : Symbol(f, Decl(collisionThisExpressionAndAliasInGlobal.ts, 3, 3))
>this : Symbol(globalThis)

import _this = a; // Error
>_this : Symbol(_this, Decl(collisionThisExpressionAndAliasInGlobal.ts, 3, 19))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ module a {
>10 : 10
}
var f = () => this;
>f : () => any
>() => this : () => any
>this : any
>f : () => typeof globalThis
>() => this : () => typeof globalThis
>this : typeof globalThis

import _this = a; // Error
>_this : typeof a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ declare class _this { // no error - as no code generation
}
var f = () => this;
>f : Symbol(f, Decl(collisionThisExpressionAndAmbientClassInGlobal.ts, 2, 3))
>this : Symbol(globalThis)

var a = new _this(); // Error
>a : Symbol(a, Decl(collisionThisExpressionAndAmbientClassInGlobal.ts, 3, 3))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ declare class _this { // no error - as no code generation
>_this : _this
}
var f = () => this;
>f : () => any
>() => this : () => any
>this : any
>f : () => typeof globalThis
>() => this : () => typeof globalThis
>this : typeof globalThis

var a = new _this(); // Error
>a : _this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ declare var _this: number; // no error as no code gen

var f = () => this;
>f : Symbol(f, Decl(collisionThisExpressionAndAmbientVarInGlobal.ts, 1, 3))
>this : Symbol(globalThis)

_this = 10; // Error
>_this : Symbol(_this, Decl(collisionThisExpressionAndAmbientVarInGlobal.ts, 0, 11))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ declare var _this: number; // no error as no code gen
>_this : number

var f = () => this;
>f : () => any
>() => this : () => any
>this : any
>f : () => typeof globalThis
>() => this : () => typeof globalThis
>this : typeof globalThis

_this = 10; // Error
>_this = 10 : 10
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ class _this {
}
var f = () => this;
>f : Symbol(f, Decl(collisionThisExpressionAndClassInGlobal.ts, 2, 3))
>this : Symbol(globalThis)

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ class _this {
>_this : _this
}
var f = () => this;
>f : () => any
>() => this : () => any
>this : any
>f : () => typeof globalThis
>() => this : () => typeof globalThis
>this : typeof globalThis

Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ enum _this { // Error
}
var f = () => this;
>f : Symbol(f, Decl(collisionThisExpressionAndEnumInGlobal.ts, 4, 3))
>this : Symbol(globalThis)

Loading