Skip to content

Allow accessors to override non-class or abstract properties #41994

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

Merged
merged 15 commits into from
May 31, 2022

Conversation

sandersn
Copy link
Member

@sandersn sandersn commented Dec 16, 2020

An accessor may override a base property if all the property's declarations are (1) from an interface or (2) abstract. If the base property is synthetic — from an intersection, for example — then it is allowed if any of the property's declarations are abstract or from an interface.

fix #40733 and fix #41347

@typescript-bot typescript-bot added Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug labels Dec 16, 2020
@sandersn
Copy link
Member Author

Based on discussion in the design meeting, the change should instead be to exempt all properties that don't come from a class. Plus, we should keep this issue in mind in case other changes to Ecmascript cause TS to add language features dealing with prototypiness of properties/accessors.

@sandersn
Copy link
Member Author

sandersn commented Feb 9, 2021

This now matches what we decided in the design meeting. I'm going to fold in #42635 next since they do almost the same thing.

Originally from #42635, but this version is simpler.
@sandersn sandersn changed the title Allow accessors to override properties from mapped types Allow accessors to override non-class or abstract properties Feb 10, 2021
@typescript-bot typescript-bot added For Milestone Bug PRs that fix a bug with a specific milestone and removed For Uncommitted Bug PR for untriaged, rejected, closed or missing bug labels Feb 10, 2021
@sandersn
Copy link
Member Author

@elibarzilay do you mind taking a look at this while @rbuckton is at TC39?

@sandersn
Copy link
Member Author

Ping @elibarzilay @rbuckton

@sandersn sandersn requested review from gabritto and removed request for elibarzilay May 10, 2022 21:37
@rbuckton
Copy link
Contributor

Based on discussion in the design meeting, the change should instead be to exempt all properties that don't come from a class. [...]

I'm not 100% sure what "all properties" means here. We specifically added support to describe getters and setters in interfaces and type literals to differentiate between fields and accessors. We couldn't do that for mapped types, though, since mapped types can only describe field-like values (i.e., no getters, setters, or methods).

@sandersn
Copy link
Member Author

Re-reading it, I think I put the specifier in the wrong location on that comment -- I edited the description a couple months after the design meeting, so it's more up-to-date than the comment.

Basically, the change should exempt properties that whose declarations all come from outside a class, unless they're synthetic, in which case they are exempt if any declaration comes from outside a class.

I don't know what support we had for accessors in interfaces and type literals at the time, but it definitely sounds like they behave the same as classes now. I'll update the code to implement that.

@sandersn
Copy link
Member Author

Well, running the tests again without the accessor change in createUnionOrIntersectionProperty still has them all passing. So I reverted my change to see whether CI agrees with me.

Copy link
Contributor

@rbuckton rbuckton left a comment

Choose a reason for hiding this comment

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

This looks fine, though I have a few questions.


// Mixin base class
interface ApiItemContainerMixin extends ApiItem {
readonly members: ReadonlyArray<ApiItem>;
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm curious why this is readonly members: ReadonlyArray<ApiItem>; and not get members(): ReadonlyArray<ApiItem>;. Is it purely to support pre-TS 4.0, which wouldn't be able to parse the declaration file?

4.0 has been available for over a year and a half, and 41347 was filed only two months after 4.0 shipped. If we are weakening restrictions to improve backcompat support, I'd want to be sure the backcompat support is warranted. If api-extractor still supports consumers using < TS 4.0, then I could see this being valuable.

Copy link
Contributor

Choose a reason for hiding this comment

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

@octogonz, is pre-TS 4.0 support still critical to api-extractor package consumers?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's still an error because of the way union/intersection properties are created. If I bring back the commit with create-unions-of-accessors-as-accessors, your suggestion might work. Let me check.

Copy link
Member Author

Choose a reason for hiding this comment

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

Nope, the changed check is still required. (It's not that much looser either; it just allows any declaration of a synthetic symbol instead of only valueDeclaration).

function getNonInterhitedProperties(type: InterfaceType, baseTypes: BaseType[], properties: Symbol[]) {
function isPropertyAbstractOrInterface(declaration: Declaration, baseDeclarationFlags: ModifierFlags) {
return baseDeclarationFlags & ModifierFlags.Abstract && (!isPropertyDeclaration(declaration) || !declaration.initializer)
|| isInterfaceDeclaration(declaration.parent);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we only care that the declaration is an interface? I find it interesting that the following isn't currently an error:

declare class X1 {
    get y(): number;
}

class Y1 extends X1 {
    y = 1; // error (correct)
}


interface X2 {
    get y(): number;
}

declare var X2: {
    new (): X2;
    prototype: X2;
}

class Y2 extends X2 {
    y = 1; // no error (incorrect?)
}

Maybe we can be weaker when the property on the interface is just PropertySignature because of back-compat, but we probably should be erroring if the property on the interface is a GetAccessorDeclaration or SetAccessorDeclaration and you're trying to override it with a field.

Copy link
Member Author

Choose a reason for hiding this comment

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

That's a good idea, but without the commit with create-unions-of-accessors-as-accessors, it adds a weird wrinkle when synthetic properties are all accessors: specifically, if you modify accessorsOverrideProperty9 so that all bases use accessors (as in your other comment), then the error shows up again: the base symbol is synthetic, but all 3 bases are accessors, so the exemption no longer applies.

Instead, execution falls through to the flags check, but the flags are wrong, so the error message is wrong.

It's probably more correct to change the way synthetic accessors are created, but it's a much further-reaching change, and most of this code is still needed. I'd rather have a more pressing reason than this to change the code that creates synthetic accessors.

@sandersn sandersn merged commit a5b1f95 into main May 31, 2022
@sandersn sandersn deleted the allow-override-transient-properties branch May 31, 2022 21:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Author: Team For Milestone Bug PRs that fix a bug with a specific milestone
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

TypeScript 4.0 causes unsolvable TS2611 error for properties inherited from a mixin Cannot subclass class-like types with accessors.
3 participants