Skip to content

Jsdoc @constructor - in constructor properly infer this as class instance#25980

Merged
sandersn merged 7 commits into
microsoft:masterfrom
jameskeane:jsdoc-constructor-this-inference
Jul 31, 2018
Merged

Jsdoc @constructor - in constructor properly infer this as class instance#25980
sandersn merged 7 commits into
microsoft:masterfrom
jameskeane:jsdoc-constructor-this-inference

Conversation

@jameskeane
Copy link
Copy Markdown
Contributor

c.prototype.method = function() { this } was already supported.

This commit add support to two more kinds relying on the JSDoc
@constructor tag. These are:

  1. /** @constructor */ function Example() { this }
  2. /** @constructor */ var Example = function() { this }

Fixes #25979

`c.prototype.method = function() { this }` was already supported.

This commit add support to two more kinds relying on the JSDoc
`@constructor` tag. These are:
 1. `/** @constructor */ function Example() { this }`
 2. `/** @constructor */ var Example = function() { this }`
C3 and C4 `this` was set as `any`, now it is properly showing as
the class type.
@msftclas
Copy link
Copy Markdown

msftclas commented Jul 26, 2018

CLA assistant check
All CLA requirements met.

@jameskeane
Copy link
Copy Markdown
Contributor Author

jameskeane commented Jul 26, 2018

This PR doesn't fix the following case, which I need some guidance on:

/** @constructor */
var Example3 = module.exports = function() {
  this.method();
};
Example3.prototype.method = function() {};

The VariableDeclaration correctly infers the member method, but module.exports and the FunctionExpression do not, I would assume the checker would just merge the = binaryops for ref values but it doesn't seem to be doing that.

I looked everywhere for something that walks up binary ops and combines the symbols but could not find it.

EDIT: looks like a special binding for module.exports @ https://github.com/Microsoft/TypeScript/blob/master/src/compiler/binder.ts#L2087

Copy link
Copy Markdown
Member

@sandersn sandersn left a comment

Choose a reason for hiding this comment

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

I don't think we should take this change until we've discussed and agreed on a solution in the originating bug.

Comment thread src/compiler/checker.ts
if (classType) {
return getFlowTypeOfReference(node, classType);
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't think this is the correct fix. The type of this should still be any because without an explicit or contextual type for this, you can still assign to any property on it. Instead, the no-implicit-this error should just be suppressed in javascript. The check at the bottom of checkThisExpression should change to if (!type && noImplicitThis && !isInJavascriptFile(node)) { so that JS-based uses of this in constructor functions won't error.

When calling a JS function explicitly tagged with either `@class` or
`@constructor` the checker should throw a TS2348 not callable error.
@jameskeane
Copy link
Copy Markdown
Contributor Author

@sandersn I've added a test case for the circular initializer example you wanted.

Also, I've included the change that will throw error TS2348: Value of type 'typeof xx' is not callable. when trying to call a function tagged with @class or @constructor.

There was a baseline change to typeFromPropertyAssignment12 that I did not expect; I'm not really sure how to interpret the change either.

I'm running through the user contributed test cases now and will update soon.

Copy link
Copy Markdown
Member

@sandersn sandersn left a comment

Choose a reason for hiding this comment

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

  1. Probably should move the code that requires @class-marked functions to be called with new.
  2. Some odd indentation.

Otherwise looks good.

Comment thread src/compiler/checker.ts Outdated
// * /** @constructor */ function [name]() { ... }
// * /** @constructor */ var x = function() { ... }
else if ((container.kind === SyntaxKind.FunctionExpression ||
container.kind === SyntaxKind.FunctionDeclaration) &&
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

indentation looks weird on this line.

>new Outer.Pos(1, 'x') : Pos
>Outer.Pos : typeof Pos
>Outer : { (element: any, config: any): void; Pos(line: any, ch: any): void; }
>Outer : { (element: any, config: any): void; }
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You're right, this is a result of the change in resolveAnonymousTypeMembers. And it breaks intelllisense -- you get no suggestions for Outer.. I haven't figured out why though.

Comment thread src/compiler/checker.ts Outdated
type.callSignatures = getSignaturesOfSymbol(symbol);
// If the function is explicitly marked with `@class`, then it must be constructed.
if (isInJavaScriptFile(symbol.valueDeclaration) && getJSDocClassTag(symbol.valueDeclaration)) {
type.constructSignatures = getSignaturesOfSymbol(symbol);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

given the way this breaks intellisense, perhaps a better change would be to change resolveCallExpression to not resolve call signatures on functions with @constructor instead of switching them entirely to be construct signatures. Something like (just before the last return of the function):

            if (callSignatures.some(sig => isJavaScriptConstructor(sig.declaration) && !!getJSDocClassTag(sig.declaration!))) {
                error(node, Diagnostics.Value_of_type_0_is_not_callable_Did_you_mean_to_include_new, typeToString(funcType));
                return resolveErrorCall(node);
            }

Although there's probably a more elegant way to write it. This undoes the questionable baseline changes that happen with the current code.

This undoes the last commit that sought to change how js functions
tagged with `@class` were inferred. For some reason, currently
unknown, giving those functions construct signatures causes issues
in property assignment/member resolution (as seen in the
`typeFromPropertyAssignment12` test case).

Instead of changing the signature resolution, the error is explicitly
generated in `resolveCallExpression` for those functions.
@sandersn sandersn merged commit dfedb24 into microsoft:master Jul 31, 2018
@sandersn
Copy link
Copy Markdown
Member

@jameskeane Thanks for the suggestion and implementation!

@jameskeane
Copy link
Copy Markdown
Contributor Author

@sandersn thanks!

@microsoft microsoft locked as resolved and limited conversation to collaborators Oct 21, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants