Skip to content

Commit 3d64b9d

Browse files
authored
Handle intersection types when looking up base types for visibility (#25418)
* Handle intersection types when looking up base types for visibility * Extract protected constructor check to function and recur on intersections * Remove unneeded cast
1 parent 6b4a3b2 commit 3d64b9d

File tree

6 files changed

+261
-9
lines changed

6 files changed

+261
-9
lines changed

src/compiler/checker.ts

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19345,6 +19345,38 @@ namespace ts {
1934519345
return resolveErrorCall(node);
1934619346
}
1934719347

19348+
function typeHasProtectedAccessibleBase(target: Symbol, type: InterfaceType): boolean {
19349+
const baseTypes = getBaseTypes(type);
19350+
if (!length(baseTypes)) {
19351+
return false;
19352+
}
19353+
const firstBase = baseTypes[0];
19354+
if (firstBase.flags & TypeFlags.Intersection) {
19355+
const types = (firstBase as IntersectionType).types;
19356+
const mixinCount = countWhere(types, isMixinConstructorType);
19357+
let i = 0;
19358+
for (const intersectionMember of (firstBase as IntersectionType).types) {
19359+
i++;
19360+
// We want to ignore mixin ctors
19361+
if (mixinCount === 0 || mixinCount === types.length && i === 0 || !isMixinConstructorType(intersectionMember)) {
19362+
if (getObjectFlags(intersectionMember) & (ObjectFlags.Class | ObjectFlags.Interface)) {
19363+
if (intersectionMember.symbol === target) {
19364+
return true;
19365+
}
19366+
if (typeHasProtectedAccessibleBase(target, intersectionMember as InterfaceType)) {
19367+
return true;
19368+
}
19369+
}
19370+
}
19371+
}
19372+
return false;
19373+
}
19374+
if (firstBase.symbol === target) {
19375+
return true;
19376+
}
19377+
return typeHasProtectedAccessibleBase(target, firstBase as InterfaceType);
19378+
}
19379+
1934819380
function isConstructorAccessible(node: NewExpression, signature: Signature) {
1934919381
if (!signature || !signature.declaration) {
1935019382
return true;
@@ -19364,16 +19396,10 @@ namespace ts {
1936419396
// A private or protected constructor can only be instantiated within its own class (or a subclass, for protected)
1936519397
if (!isNodeWithinClass(node, declaringClassDeclaration)) {
1936619398
const containingClass = getContainingClass(node);
19367-
if (containingClass) {
19399+
if (containingClass && modifiers & ModifierFlags.Protected) {
1936819400
const containingType = getTypeOfNode(containingClass);
19369-
let baseTypes = getBaseTypes(containingType as InterfaceType);
19370-
while (baseTypes.length) {
19371-
const baseType = baseTypes[0];
19372-
if (modifiers & ModifierFlags.Protected &&
19373-
baseType.symbol === declaration.parent.symbol) {
19374-
return true;
19375-
}
19376-
baseTypes = getBaseTypes(baseType as InterfaceType);
19401+
if (typeHasProtectedAccessibleBase(declaration.parent.symbol, containingType as InterfaceType)) {
19402+
return true;
1937719403
}
1937819404
}
1937919405
if (modifiers & ModifierFlags.Private) {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
tests/cases/compiler/noCrashOnMixin.ts(21,9): error TS2674: Constructor of class 'Abstract' is protected and only accessible within the class declaration.
2+
3+
4+
==== tests/cases/compiler/noCrashOnMixin.ts (1 errors) ====
5+
class Abstract {
6+
protected constructor() {
7+
}
8+
}
9+
10+
class Concrete extends Abstract {
11+
}
12+
13+
type Constructor<T = {}> = new (...args: any[]) => T;
14+
15+
function Mixin<TBase extends Constructor>(Base: TBase) {
16+
return class extends Base {
17+
};
18+
}
19+
20+
class Empty {
21+
}
22+
23+
class CrashTrigger extends Mixin(Empty) {
24+
public trigger() {
25+
new Concrete();
26+
~~~~~~~~~~~~~~
27+
!!! error TS2674: Constructor of class 'Abstract' is protected and only accessible within the class declaration.
28+
}
29+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
//// [noCrashOnMixin.ts]
2+
class Abstract {
3+
protected constructor() {
4+
}
5+
}
6+
7+
class Concrete extends Abstract {
8+
}
9+
10+
type Constructor<T = {}> = new (...args: any[]) => T;
11+
12+
function Mixin<TBase extends Constructor>(Base: TBase) {
13+
return class extends Base {
14+
};
15+
}
16+
17+
class Empty {
18+
}
19+
20+
class CrashTrigger extends Mixin(Empty) {
21+
public trigger() {
22+
new Concrete();
23+
}
24+
}
25+
26+
//// [noCrashOnMixin.js]
27+
var __extends = (this && this.__extends) || (function () {
28+
var extendStatics = function (d, b) {
29+
extendStatics = Object.setPrototypeOf ||
30+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
31+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
32+
return extendStatics(d, b);
33+
}
34+
return function (d, b) {
35+
extendStatics(d, b);
36+
function __() { this.constructor = d; }
37+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
38+
};
39+
})();
40+
var Abstract = /** @class */ (function () {
41+
function Abstract() {
42+
}
43+
return Abstract;
44+
}());
45+
var Concrete = /** @class */ (function (_super) {
46+
__extends(Concrete, _super);
47+
function Concrete() {
48+
return _super !== null && _super.apply(this, arguments) || this;
49+
}
50+
return Concrete;
51+
}(Abstract));
52+
function Mixin(Base) {
53+
return /** @class */ (function (_super) {
54+
__extends(class_1, _super);
55+
function class_1() {
56+
return _super !== null && _super.apply(this, arguments) || this;
57+
}
58+
return class_1;
59+
}(Base));
60+
}
61+
var Empty = /** @class */ (function () {
62+
function Empty() {
63+
}
64+
return Empty;
65+
}());
66+
var CrashTrigger = /** @class */ (function (_super) {
67+
__extends(CrashTrigger, _super);
68+
function CrashTrigger() {
69+
return _super !== null && _super.apply(this, arguments) || this;
70+
}
71+
CrashTrigger.prototype.trigger = function () {
72+
new Concrete();
73+
};
74+
return CrashTrigger;
75+
}(Mixin(Empty)));
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
=== tests/cases/compiler/noCrashOnMixin.ts ===
2+
class Abstract {
3+
>Abstract : Symbol(Abstract, Decl(noCrashOnMixin.ts, 0, 0))
4+
5+
protected constructor() {
6+
}
7+
}
8+
9+
class Concrete extends Abstract {
10+
>Concrete : Symbol(Concrete, Decl(noCrashOnMixin.ts, 3, 1))
11+
>Abstract : Symbol(Abstract, Decl(noCrashOnMixin.ts, 0, 0))
12+
}
13+
14+
type Constructor<T = {}> = new (...args: any[]) => T;
15+
>Constructor : Symbol(Constructor, Decl(noCrashOnMixin.ts, 6, 1))
16+
>T : Symbol(T, Decl(noCrashOnMixin.ts, 8, 17))
17+
>args : Symbol(args, Decl(noCrashOnMixin.ts, 8, 32))
18+
>T : Symbol(T, Decl(noCrashOnMixin.ts, 8, 17))
19+
20+
function Mixin<TBase extends Constructor>(Base: TBase) {
21+
>Mixin : Symbol(Mixin, Decl(noCrashOnMixin.ts, 8, 53))
22+
>TBase : Symbol(TBase, Decl(noCrashOnMixin.ts, 10, 15))
23+
>Constructor : Symbol(Constructor, Decl(noCrashOnMixin.ts, 6, 1))
24+
>Base : Symbol(Base, Decl(noCrashOnMixin.ts, 10, 42))
25+
>TBase : Symbol(TBase, Decl(noCrashOnMixin.ts, 10, 15))
26+
27+
return class extends Base {
28+
>Base : Symbol(Base, Decl(noCrashOnMixin.ts, 10, 42))
29+
30+
};
31+
}
32+
33+
class Empty {
34+
>Empty : Symbol(Empty, Decl(noCrashOnMixin.ts, 13, 1))
35+
}
36+
37+
class CrashTrigger extends Mixin(Empty) {
38+
>CrashTrigger : Symbol(CrashTrigger, Decl(noCrashOnMixin.ts, 16, 1))
39+
>Mixin : Symbol(Mixin, Decl(noCrashOnMixin.ts, 8, 53))
40+
>Empty : Symbol(Empty, Decl(noCrashOnMixin.ts, 13, 1))
41+
42+
public trigger() {
43+
>trigger : Symbol(CrashTrigger.trigger, Decl(noCrashOnMixin.ts, 18, 41))
44+
45+
new Concrete();
46+
>Concrete : Symbol(Concrete, Decl(noCrashOnMixin.ts, 3, 1))
47+
}
48+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
=== tests/cases/compiler/noCrashOnMixin.ts ===
2+
class Abstract {
3+
>Abstract : Abstract
4+
5+
protected constructor() {
6+
}
7+
}
8+
9+
class Concrete extends Abstract {
10+
>Concrete : Concrete
11+
>Abstract : Abstract
12+
}
13+
14+
type Constructor<T = {}> = new (...args: any[]) => T;
15+
>Constructor : Constructor<T>
16+
>T : T
17+
>args : any[]
18+
>T : T
19+
20+
function Mixin<TBase extends Constructor>(Base: TBase) {
21+
>Mixin : <TBase extends Constructor<{}>>(Base: TBase) => { new (...args: any[]): (Anonymous class); prototype: Mixin<any>.(Anonymous class); } & TBase
22+
>TBase : TBase
23+
>Constructor : Constructor<T>
24+
>Base : TBase
25+
>TBase : TBase
26+
27+
return class extends Base {
28+
>class extends Base { } : { new (...args: any[]): (Anonymous class); prototype: Mixin<any>.(Anonymous class); } & TBase
29+
>Base : {}
30+
31+
};
32+
}
33+
34+
class Empty {
35+
>Empty : Empty
36+
}
37+
38+
class CrashTrigger extends Mixin(Empty) {
39+
>CrashTrigger : CrashTrigger
40+
>Mixin(Empty) : Mixin<typeof Empty>.(Anonymous class) & Empty
41+
>Mixin : <TBase extends Constructor<{}>>(Base: TBase) => { new (...args: any[]): (Anonymous class); prototype: Mixin<any>.(Anonymous class); } & TBase
42+
>Empty : typeof Empty
43+
44+
public trigger() {
45+
>trigger : () => void
46+
47+
new Concrete();
48+
>new Concrete() : any
49+
>Concrete : typeof Concrete
50+
}
51+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
class Abstract {
2+
protected constructor() {
3+
}
4+
}
5+
6+
class Concrete extends Abstract {
7+
}
8+
9+
type Constructor<T = {}> = new (...args: any[]) => T;
10+
11+
function Mixin<TBase extends Constructor>(Base: TBase) {
12+
return class extends Base {
13+
};
14+
}
15+
16+
class Empty {
17+
}
18+
19+
class CrashTrigger extends Mixin(Empty) {
20+
public trigger() {
21+
new Concrete();
22+
}
23+
}

0 commit comments

Comments
 (0)