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

feat: support 'this' type on methods and properties #2906

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
54 changes: 46 additions & 8 deletions src/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -748,7 +748,7 @@ export class Resolver extends DiagnosticEmitter {
}

// otherwise resolve the non-generic call as usual
return this.resolveFunction(prototype, null, new Map(), reportMode);
return this.resolveFunction(prototype, null, new Map(), reportMode, ctxFlow);
}

private inferGenericTypeArguments(
Expand Down Expand Up @@ -1515,7 +1515,10 @@ export class Resolver extends DiagnosticEmitter {
let member = classLikeTarget.getMember(propertyName);
if (member) {
if (member.kind == ElementKind.PropertyPrototype) {
let propertyInstance = this.resolveProperty(<PropertyPrototype>member, reportMode);
if (!member.is(CommonFlags.Static)) {
this.currentThisExpression = targetNode;
}
let propertyInstance = this.resolveProperty(<PropertyPrototype>member, reportMode, ctxFlow);
if (!propertyInstance) return null;
member = propertyInstance;
if (propertyInstance.is(CommonFlags.Static)) {
Expand Down Expand Up @@ -2851,14 +2854,35 @@ export class Resolver extends DiagnosticEmitter {
/** Contextual types, i.e. `T`. */
ctxTypes: Map<string,Type> = new Map(),
/** How to proceed with eventual diagnostics. */
reportMode: ReportMode = ReportMode.Report
reportMode: ReportMode = ReportMode.Report,
/** Contextual flow. */
ctxFlow: Flow | null = null
): Function | null {
let classInstance: Class | null = null; // if an instance method
let instanceKey = typeArguments ? typesToString(typeArguments) : "";

// Instance method prototypes are pre-bound to their concrete class as their parent
if (prototype.is(CommonFlags.Instance)) {
classInstance = assert(prototype.getBoundClassOrInterface());

// The actual class instance may be a subclass of the bound class
if (this.currentThisExpression && ctxFlow) {
// In the case of a function or property that uses the polymorphic `this` type,
// this is important, so the return is typed as the actual class instance.
// Note: It should work without this outer type check, and does when testing, but fails the "bootstrap" build.
// TODO: Figure out why and remove the extra check.
let type = prototype.functionTypeNode.returnType;
if (type.kind == NodeKind.NamedType && (<NamedTypeNode>type).name.identifier.text == CommonNames.this_) {
let element = this.lookupExpression(this.currentThisExpression!, ctxFlow);
if (element && element.kind == ElementKind.Class) {
classInstance = <Class>element;
}
}
}

// Otherwise, the bound class is the actual class instance
if (!classInstance) {
classInstance = assert(prototype.getBoundClassOrInterface());
}

// check if this exact concrete class and function combination is known already
let resolvedInstance = prototype.getResolvedInstance(instanceKey);
Expand Down Expand Up @@ -2998,7 +3022,9 @@ export class Resolver extends DiagnosticEmitter {
prototype.setResolvedInstance(instanceKey, instance);

// check against overridden base member
if (classInstance) {
if (prototype.is(CommonFlags.Instance)) {
// always take the prototype's bound class here - which may differ from the previous classInstance
let classInstance = assert(prototype.getBoundClassOrInterface());
let methodOrPropertyName = instance.declaration.name.text;
let baseClass = classInstance.base;
if (baseClass) {
Expand Down Expand Up @@ -3492,6 +3518,14 @@ export class Resolver extends DiagnosticEmitter {
);
break;
}
if (assert(boundPrototype.typeNode).kind == NodeKind.NamedType && (<NamedTypeNode>boundPrototype.typeNode).name.identifier.text == CommonNames.this_) {
this.error(
DiagnosticCode.Not_implemented_0,
assert(boundPrototype.typeNode).range,
"Polymorphic 'this' typed fields"
);
break;
}
let needsLayout = true;
if (base) {
let existingMember = base.getMember(boundPrototype.name);
Expand Down Expand Up @@ -3749,7 +3783,9 @@ export class Resolver extends DiagnosticEmitter {
/** The prototype of the property. */
prototype: PropertyPrototype,
/** How to proceed with eventual diagnostics. */
reportMode: ReportMode = ReportMode.Report
reportMode: ReportMode = ReportMode.Report,
/** Contextual flow. */
ctxFlow: Flow | null = null
): Property | null {
let instance = prototype.instance;
if (instance) return instance;
Expand All @@ -3763,7 +3799,8 @@ export class Resolver extends DiagnosticEmitter {
getterPrototype,
null,
new Map(),
reportMode
reportMode,
ctxFlow
);
if (getterInstance) {
instance.getterInstance = getterInstance;
Expand All @@ -3776,7 +3813,8 @@ export class Resolver extends DiagnosticEmitter {
setterPrototype,
null,
new Map(),
reportMode
reportMode,
ctxFlow
);
if (setterInstance) {
instance.setterInstance = setterInstance;
Expand Down
Loading