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

Function declaration with aliased this not treated as a constructor #39842

Closed
uniqueiniquity opened this issue Jul 31, 2020 · 7 comments · Fixed by #39908
Closed

Function declaration with aliased this not treated as a constructor #39842

uniqueiniquity opened this issue Jul 31, 2020 · 7 comments · Fixed by #39908
Labels
Domain: JavaScript The issue relates to JavaScript specifically Has Repro This issue has compiler-backed repros: https://aka.ms/ts-repros In Discussion Not yet reached consensus Suggestion An idea for TypeScript

Comments

@uniqueiniquity
Copy link
Contributor

TypeScript Version: 3.9.7

Search Terms:
this, alias

Code

// @filename: a.js
var libObjects = {}

libObjects.dataService = function (url) {
	var scope = this;

	/**
	 * updates some data
	 */
	scope.updateData = function () {
		//...
	}

	/**
	 * stop all processing
	 */
	scope.disable = function () {
		//...
	}
}

// @filename: b.js
var service = new libObjects.dataService("");
service
//   ^?

Expected behavior:
service: dataService and should offer updateData and disable in completion.

Actual behavior:
service: any

Playground Link: Workbench Repro

Related Issues:
#36618

@typescript-bot typescript-bot added the Has Repro This issue has compiler-backed repros: https://aka.ms/ts-repros label Jul 31, 2020
@uniqueiniquity
Copy link
Contributor Author

cc: @DanielRosenwasser, with whom I discussed this earlier
cc: @minestarks, who is interested in grouping JS issues
cc: @sandersn, who Daniel suggested might be interested

@DanielRosenwasser DanielRosenwasser changed the title Globally-defined object with aliased this not treated as type Functionwith aliased this not treated as a constructor Jul 31, 2020
@DanielRosenwasser DanielRosenwasser changed the title Functionwith aliased this not treated as a constructor Function declaration with aliased this not treated as a constructor Jul 31, 2020
@DanielRosenwasser DanielRosenwasser added Domain: JavaScript The issue relates to JavaScript specifically In Discussion Not yet reached consensus Suggestion An idea for TypeScript labels Jul 31, 2020
@DanielRosenwasser
Copy link
Member

DanielRosenwasser commented Jul 31, 2020

The object isn't relevant in this case - you just need the function declaration and the aliased this.

function Foo(url) {
  var scope = this;

  scope.callbackFn = function () {
    //...
  }
}

var foo = new Foo();
//  ^?

@typescript-bot
Copy link
Collaborator

typescript-bot commented Jul 31, 2020

👋 Hi, I'm the Repro bot. I can help narrow down and track compiler bugs across releases! This comment reflects the current state of the 2 repros in this issue running against the nightly TypeScript. If something changes, I will post a new comment.


Issue body code block by @uniqueiniquity

⚠️ Assertions:

  • var service: any

Comment by @uniqueiniquity

‼️ Exception: TypeError - Cannot read property 'ES2016' of undefined

TypeError: Cannot read property 'ES2016' of undefined
    at Object.twoslasher (/home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:820:29)
    at /home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:3764:29
    at Array.map (<anonymous>)
    at Object.runTwoslashRuns (/home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:3730:43)
    at run (/home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:1267:43)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)

Historical Information

Issue body code block by @uniqueiniquity

Version Reproduction Outputs
3.5.3, 3.6.4, 3.7.5, 3.8.3, 3.9.5, Nightly

⚠️ Assertions:

  • var service: any

Comment by @uniqueiniquity

Version Reproduction Outputs
Nightly

‼️ Exception: TypeError - Cannot read property 'ES2016' of undefined

TypeError: Cannot read property 'ES2016' of undefined
    at Object.twoslasher (/home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:820:29)
    at /home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:3764:29
    at Array.map (<anonymous>)
    at Object.runTwoslashRuns (/home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:3730:43)
    at run (/home/runner/work/_actions/microsoft/TypeScript-Twoslash-Repro-Action/master/dist/index.js:1267:43)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)

3.5.3, 3.6.4, 3.7.5, 3.8.3, 3.9.5

⚠️ Assertions:

  • var foo: any

@sandersn
Copy link
Member

sandersn commented Jul 31, 2020

Currently constructor functions are identified in the binder using syntactic markers like this-property assignment: this.x = 1. Making aliased this work would require shifting most of the work to the checker, or else more hacks and heuristics in the binder.

@uniqueiniquity
Copy link
Contributor Author

Makes sense.
Do we know how common a pattern this is?
@DanielRosenwasser pointed me to this style guide for Angular 1 which explicitly recommends it and has 24k stars.
Alternatively, as inspired by that guide - is there a JSDoc annotation we may want to recognize to help us here?

@sandersn
Copy link
Member

sandersn commented Aug 4, 2020

Just based on our user tests, I believe it's pretty common. It is quite an old pattern by now, so I'm not sure how many editor-hours it still takes up. That is, the code written like this may never be opened in an editor anymore.

Existing jsdoc we have that could be added:

  1. @constructor (or its alias @class) allows the construct call new Foo()
  2. @this {{ callbackFn: Function }} allows the assignment scope.callbackFn = function () { ... }
  3. Closure property declarations allow property access on instances of Foo:
function Foo() {
  /** @type {Function} */
  this.callbackFn
}
new Foo().callbackFn

However, there aren't likely to be any of those -- I've only ever seen @this annotations in webpack when they were trying to be noImplicitThis safe, and I've only seen @constructor in Closure codebases. And of course Closure property declarations are only used in Closure codebases.

@sandersn
Copy link
Member

sandersn commented Aug 4, 2020

@uniqueiniquity and I prototyped a hacky solution in the binder, which looks like it would work pretty well. Basically, when binding a variable declaration that looks like var self = this, save the identifier self as thisAlias. Then when checking whether to bind binary expressions as AssignmentDeclarationKind.ThisProperty, allow this or thisAlias.

The prototype immediately pointed out some places to fix:

  1. thisAlias needs to be scoped exactly like container in the binder, but stricter. It should only work inside a function, constructor or method. It shouldn't work at the top level.
  2. The checker also needs to have information about thisAlias in order to check correctly. I'm not yet sure how to store or reproduce this non-local information. There are a number of options, like asking for the type of self and seeing whether it's the same as the type of this.
  3. The language probably has the same requirement as the checker in a few places.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Domain: JavaScript The issue relates to JavaScript specifically Has Repro This issue has compiler-backed repros: https://aka.ms/ts-repros In Discussion Not yet reached consensus Suggestion An idea for TypeScript
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants