Skip to content

Commit

Permalink
Propagate accessor flags in intersected properties
Browse files Browse the repository at this point in the history
  • Loading branch information
Andarist committed Jan 1, 2025
1 parent 3fe512a commit 1baa828
Show file tree
Hide file tree
Showing 18 changed files with 828 additions and 1 deletion.
10 changes: 9 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14938,6 +14938,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined {
let propFlags = SymbolFlags.None;
let singleProp: Symbol | undefined;
let propSet: Map<SymbolId, Symbol> | undefined;
let indexTypes: Type[] | undefined;
Expand All @@ -14964,6 +14965,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
if (!singleProp) {
singleProp = prop;
propFlags = (prop.flags & SymbolFlags.Accessor) || SymbolFlags.Property;
}
else if (prop !== singleProp) {
const isInstantiation = (getTargetSymbol(prop) || prop) === (getTargetSymbol(singleProp) || singleProp);
Expand All @@ -14986,6 +14988,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
propSet.set(id, prop);
}
}
// classes created by mixins are represented as intersections and overriding a property in a derived class redefines it completely at runtime
// so a get accessor can't be merged with a set accessor in a base class, for that reason the accessor flags are only used when they are the same in all consistuents
if (propFlags & SymbolFlags.Accessor && (prop.flags & SymbolFlags.Accessor) !== (propFlags & SymbolFlags.Accessor)) {
propFlags = (propFlags & ~SymbolFlags.Accessor) | SymbolFlags.Property;
}
}
if (isUnion && isReadonlySymbol(prop)) {
checkFlags |= CheckFlags.Readonly;
Expand All @@ -15004,6 +15011,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
else if (isUnion) {
const indexInfo = !isLateBoundName(name) && getApplicableIndexInfoForName(type, name);
if (indexInfo) {
propFlags = (propFlags & ~SymbolFlags.Accessor) | SymbolFlags.Property;
checkFlags |= CheckFlags.WritePartial | (indexInfo.isReadonly ? CheckFlags.Readonly : 0);
indexTypes = append(indexTypes, isTupleType(type) ? getRestTypeOfTupleType(type) || undefinedType : indexInfo.type);
}
Expand Down Expand Up @@ -15082,7 +15090,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
propTypes.push(type);
}
addRange(propTypes, indexTypes);
const result = createSymbol(SymbolFlags.Property | (optionalFlag ?? 0), name, syntheticFlag | checkFlags);
const result = createSymbol(propFlags | (optionalFlag ?? 0), name, syntheticFlag | checkFlags);
result.links.containingType = containingType;
if (!hasNonUniformValueDeclaration && firstValueDeclaration) {
result.valueDeclaration = firstValueDeclaration;
Expand Down
66 changes: 66 additions & 0 deletions tests/baselines/reference/mixinAccessors1.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//// [tests/cases/conformance/classes/mixinAccessors1.ts] ////

//// [mixinAccessors1.ts]
// https://github.com/microsoft/TypeScript/issues/58790

function mixin<T extends { new (...args: any[]): {} }>(superclass: T) {
return class extends superclass {
get validationTarget(): HTMLElement {
return document.createElement("input");
}
};
}

class BaseClass {
get validationTarget(): HTMLElement {
return document.createElement("div");
}
}

class MyClass extends mixin(BaseClass) {
get validationTarget(): HTMLElement {
return document.createElement("select");
}
}

//// [mixinAccessors1.js]
"use strict";
// https://github.com/microsoft/TypeScript/issues/58790
function mixin(superclass) {
return class extends superclass {
get validationTarget() {
return document.createElement("input");
}
};
}
class BaseClass {
get validationTarget() {
return document.createElement("div");
}
}
class MyClass extends mixin(BaseClass) {
get validationTarget() {
return document.createElement("select");
}
}


//// [mixinAccessors1.d.ts]
declare function mixin<T extends {
new (...args: any[]): {};
}>(superclass: T): {
new (...args: any[]): {
get validationTarget(): HTMLElement;
};
} & T;
declare class BaseClass {
get validationTarget(): HTMLElement;
}
declare const MyClass_base: {
new (...args: any[]): {
get validationTarget(): HTMLElement;
};
} & typeof BaseClass;
declare class MyClass extends MyClass_base {
get validationTarget(): HTMLElement;
}
56 changes: 56 additions & 0 deletions tests/baselines/reference/mixinAccessors1.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//// [tests/cases/conformance/classes/mixinAccessors1.ts] ////

=== mixinAccessors1.ts ===
// https://github.com/microsoft/TypeScript/issues/58790

function mixin<T extends { new (...args: any[]): {} }>(superclass: T) {
>mixin : Symbol(mixin, Decl(mixinAccessors1.ts, 0, 0))
>T : Symbol(T, Decl(mixinAccessors1.ts, 2, 15))
>args : Symbol(args, Decl(mixinAccessors1.ts, 2, 32))
>superclass : Symbol(superclass, Decl(mixinAccessors1.ts, 2, 55))
>T : Symbol(T, Decl(mixinAccessors1.ts, 2, 15))

return class extends superclass {
>superclass : Symbol(superclass, Decl(mixinAccessors1.ts, 2, 55))

get validationTarget(): HTMLElement {
>validationTarget : Symbol((Anonymous class).validationTarget, Decl(mixinAccessors1.ts, 3, 35))
>HTMLElement : Symbol(HTMLElement, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --))

return document.createElement("input");
>document.createElement : Symbol(Document.createElement, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --))
>document : Symbol(document, Decl(lib.dom.d.ts, --, --))
>createElement : Symbol(Document.createElement, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --))
}
};
}

class BaseClass {
>BaseClass : Symbol(BaseClass, Decl(mixinAccessors1.ts, 8, 1))

get validationTarget(): HTMLElement {
>validationTarget : Symbol(BaseClass.validationTarget, Decl(mixinAccessors1.ts, 10, 17))
>HTMLElement : Symbol(HTMLElement, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --))

return document.createElement("div");
>document.createElement : Symbol(Document.createElement, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --))
>document : Symbol(document, Decl(lib.dom.d.ts, --, --))
>createElement : Symbol(Document.createElement, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --))
}
}

class MyClass extends mixin(BaseClass) {
>MyClass : Symbol(MyClass, Decl(mixinAccessors1.ts, 14, 1))
>mixin : Symbol(mixin, Decl(mixinAccessors1.ts, 0, 0))
>BaseClass : Symbol(BaseClass, Decl(mixinAccessors1.ts, 8, 1))

get validationTarget(): HTMLElement {
>validationTarget : Symbol(MyClass.validationTarget, Decl(mixinAccessors1.ts, 16, 40))
>HTMLElement : Symbol(HTMLElement, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --))

return document.createElement("select");
>document.createElement : Symbol(Document.createElement, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --))
>document : Symbol(document, Decl(lib.dom.d.ts, --, --))
>createElement : Symbol(Document.createElement, Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --), Decl(lib.dom.d.ts, --, --))
}
}
87 changes: 87 additions & 0 deletions tests/baselines/reference/mixinAccessors1.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//// [tests/cases/conformance/classes/mixinAccessors1.ts] ////

=== mixinAccessors1.ts ===
// https://github.com/microsoft/TypeScript/issues/58790

function mixin<T extends { new (...args: any[]): {} }>(superclass: T) {
>mixin : <T extends { new (...args: any[]): {}; }>(superclass: T) => { new (...args: any[]): (Anonymous class); prototype: mixin<any>.(Anonymous class); } & T
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>args : any[]
> : ^^^^^
>superclass : T
> : ^

return class extends superclass {
>class extends superclass { get validationTarget(): HTMLElement { return document.createElement("input"); } } : { new (...args: any[]): (Anonymous class); prototype: mixin<any>.(Anonymous class); } & T
> : ^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>superclass : {}
> : ^^

get validationTarget(): HTMLElement {
>validationTarget : HTMLElement
> : ^^^^^^^^^^^

return document.createElement("input");
>document.createElement("input") : HTMLInputElement
> : ^^^^^^^^^^^^^^^^
>document.createElement : { <K extends keyof HTMLElementTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementTagNameMap[K]; <K extends keyof HTMLElementDeprecatedTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementDeprecatedTagNameMap[K]; (tagName: string, options?: ElementCreationOptions): HTMLElement; }
> : ^^^ ^^^^^^^^^ ^^ ^^ ^^ ^^^ ^^^ ^^^ ^^^^^^^^^ ^^ ^^ ^^ ^^^ ^^^ ^^^ ^^ ^^ ^^^ ^^^ ^^^
>document : Document
> : ^^^^^^^^
>createElement : { <K extends keyof HTMLElementTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementTagNameMap[K]; <K extends keyof HTMLElementDeprecatedTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementDeprecatedTagNameMap[K]; (tagName: string, options?: ElementCreationOptions): HTMLElement; }
> : ^^^ ^^^^^^^^^ ^^ ^^ ^^ ^^^ ^^^ ^^^ ^^^^^^^^^ ^^ ^^ ^^ ^^^ ^^^ ^^^ ^^ ^^ ^^^ ^^^ ^^^
>"input" : "input"
> : ^^^^^^^
}
};
}

class BaseClass {
>BaseClass : BaseClass
> : ^^^^^^^^^

get validationTarget(): HTMLElement {
>validationTarget : HTMLElement
> : ^^^^^^^^^^^

return document.createElement("div");
>document.createElement("div") : HTMLDivElement
> : ^^^^^^^^^^^^^^
>document.createElement : { <K extends keyof HTMLElementTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementTagNameMap[K]; <K extends keyof HTMLElementDeprecatedTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementDeprecatedTagNameMap[K]; (tagName: string, options?: ElementCreationOptions): HTMLElement; }
> : ^^^ ^^^^^^^^^ ^^ ^^ ^^ ^^^ ^^^ ^^^ ^^^^^^^^^ ^^ ^^ ^^ ^^^ ^^^ ^^^ ^^ ^^ ^^^ ^^^ ^^^
>document : Document
> : ^^^^^^^^
>createElement : { <K extends keyof HTMLElementTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementTagNameMap[K]; <K extends keyof HTMLElementDeprecatedTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementDeprecatedTagNameMap[K]; (tagName: string, options?: ElementCreationOptions): HTMLElement; }
> : ^^^ ^^^^^^^^^ ^^ ^^ ^^ ^^^ ^^^ ^^^ ^^^^^^^^^ ^^ ^^ ^^ ^^^ ^^^ ^^^ ^^ ^^ ^^^ ^^^ ^^^
>"div" : "div"
> : ^^^^^
}
}

class MyClass extends mixin(BaseClass) {
>MyClass : MyClass
> : ^^^^^^^
>mixin(BaseClass) : mixin<typeof BaseClass>.(Anonymous class) & BaseClass
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>mixin : <T extends { new (...args: any[]): {}; }>(superclass: T) => { new (...args: any[]): (Anonymous class); prototype: mixin<any>.(Anonymous class); } & T
> : ^ ^^^^^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>BaseClass : typeof BaseClass
> : ^^^^^^^^^^^^^^^^

get validationTarget(): HTMLElement {
>validationTarget : HTMLElement
> : ^^^^^^^^^^^

return document.createElement("select");
>document.createElement("select") : HTMLSelectElement
> : ^^^^^^^^^^^^^^^^^
>document.createElement : { <K extends keyof HTMLElementTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementTagNameMap[K]; <K extends keyof HTMLElementDeprecatedTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementDeprecatedTagNameMap[K]; (tagName: string, options?: ElementCreationOptions): HTMLElement; }
> : ^^^ ^^^^^^^^^ ^^ ^^ ^^ ^^^ ^^^ ^^^ ^^^^^^^^^ ^^ ^^ ^^ ^^^ ^^^ ^^^ ^^ ^^ ^^^ ^^^ ^^^
>document : Document
> : ^^^^^^^^
>createElement : { <K extends keyof HTMLElementTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementTagNameMap[K]; <K extends keyof HTMLElementDeprecatedTagNameMap>(tagName: K, options?: ElementCreationOptions): HTMLElementDeprecatedTagNameMap[K]; (tagName: string, options?: ElementCreationOptions): HTMLElement; }
> : ^^^ ^^^^^^^^^ ^^ ^^ ^^ ^^^ ^^^ ^^^ ^^^^^^^^^ ^^ ^^ ^^ ^^^ ^^^ ^^^ ^^ ^^ ^^^ ^^^ ^^^
>"select" : "select"
> : ^^^^^^^^
}
}
54 changes: 54 additions & 0 deletions tests/baselines/reference/mixinAccessors2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//// [tests/cases/conformance/classes/mixinAccessors2.ts] ////

//// [mixinAccessors2.ts]
function mixin<T extends { new (...args: any[]): {} }>(superclass: T) {
return class extends superclass {
accessor name = "";
};
}

class BaseClass {
accessor name = "";
}

class MyClass extends mixin(BaseClass) {
accessor name = "";
}


//// [mixinAccessors2.js]
"use strict";
function mixin(superclass) {
return class extends superclass {
accessor name = "";
};
}
class BaseClass {
accessor name = "";
}
class MyClass extends mixin(BaseClass) {
accessor name = "";
}


//// [mixinAccessors2.d.ts]
declare function mixin<T extends {
new (...args: any[]): {};
}>(superclass: T): {
new (...args: any[]): {
get name(): string;
set name(arg: string);
};
} & T;
declare class BaseClass {
accessor name: string;
}
declare const MyClass_base: {
new (...args: any[]): {
get name(): string;
set name(arg: string);
};
} & typeof BaseClass;
declare class MyClass extends MyClass_base {
accessor name: string;
}
35 changes: 35 additions & 0 deletions tests/baselines/reference/mixinAccessors2.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//// [tests/cases/conformance/classes/mixinAccessors2.ts] ////

=== mixinAccessors2.ts ===
function mixin<T extends { new (...args: any[]): {} }>(superclass: T) {
>mixin : Symbol(mixin, Decl(mixinAccessors2.ts, 0, 0))
>T : Symbol(T, Decl(mixinAccessors2.ts, 0, 15))
>args : Symbol(args, Decl(mixinAccessors2.ts, 0, 32))
>superclass : Symbol(superclass, Decl(mixinAccessors2.ts, 0, 55))
>T : Symbol(T, Decl(mixinAccessors2.ts, 0, 15))

return class extends superclass {
>superclass : Symbol(superclass, Decl(mixinAccessors2.ts, 0, 55))

accessor name = "";
>name : Symbol((Anonymous class).name, Decl(mixinAccessors2.ts, 1, 35))

};
}

class BaseClass {
>BaseClass : Symbol(BaseClass, Decl(mixinAccessors2.ts, 4, 1))

accessor name = "";
>name : Symbol(BaseClass.name, Decl(mixinAccessors2.ts, 6, 17))
}

class MyClass extends mixin(BaseClass) {
>MyClass : Symbol(MyClass, Decl(mixinAccessors2.ts, 8, 1))
>mixin : Symbol(mixin, Decl(mixinAccessors2.ts, 0, 0))
>BaseClass : Symbol(BaseClass, Decl(mixinAccessors2.ts, 4, 1))

accessor name = "";
>name : Symbol(MyClass.name, Decl(mixinAccessors2.ts, 10, 40))
}

Loading

0 comments on commit 1baa828

Please sign in to comment.