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

Restrict indexed accesses on nonpublic fields of concrete types the same way we do generic ones #61291

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from all 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
26 changes: 18 additions & 8 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
@@ -42066,6 +42066,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {

function checkIndexedAccessIndexType(type: Type, accessNode: IndexedAccessTypeNode | ElementAccessExpression) {
if (!(type.flags & TypeFlags.IndexedAccess)) {
if (isIndexedAccessTypeNode(accessNode)) {
const indexType = getTypeFromTypeNode(accessNode.indexType);
const objectType = getTypeFromTypeNode(accessNode.objectType);
const propertyName = getPropertyNameFromIndex(indexType, accessNode);
if (propertyName && getEmitDeclarations(compilerOptions)) {
const propertySymbol = forEachType(getApparentType(objectType), t => getPropertyOfType(t, propertyName));
if (propertySymbol && getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.Private) {
error(accessNode, Diagnostics.Private_member_0_cannot_be_accessed_via_indexed_access, unescapeLeadingUnderscores(propertyName));
return errorType;
}
}
}
return type;
}
// Check if the index type is assignable to 'keyof T' for the object type.
@@ -42085,14 +42097,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
return type;
}
if (isGenericObjectType(objectType)) {
const propertyName = getPropertyNameFromIndex(indexType, accessNode);
if (propertyName) {
const propertySymbol = forEachType(getApparentType(objectType), t => getPropertyOfType(t, propertyName));
if (propertySymbol && getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.NonPublicAccessibilityModifier) {
error(accessNode, Diagnostics.Private_or_protected_member_0_cannot_be_accessed_on_a_type_parameter, unescapeLeadingUnderscores(propertyName));
return errorType;
}
const propertyName = getPropertyNameFromIndex(indexType, accessNode);
if (propertyName) {
const propertySymbol = forEachType(getApparentType(objectType), t => getPropertyOfType(t, propertyName));
if (propertySymbol && getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.NonPublicAccessibilityModifier) {
error(accessNode, Diagnostics.Private_or_protected_member_0_cannot_be_accessed_on_a_type_parameter, unescapeLeadingUnderscores(propertyName));
return errorType;
}
}
error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(objectType));
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
@@ -4436,6 +4436,10 @@
"category": "Error",
"code": 4128
},
"Private member '{0}' cannot be accessed via indexed access.": {
"category": "Error",
"code": 4129
},

"The current host does not support the '{0}' option.": {
"category": "Error",
10 changes: 8 additions & 2 deletions src/server/editorServices.ts
Original file line number Diff line number Diff line change
@@ -748,7 +748,13 @@ export type ConfiguredProjectToAnyReloadKind = Map<
>;

/** @internal */
export type DefaultConfiguredProjectResult = ReturnType<ProjectService["tryFindDefaultConfiguredProjectForOpenScriptInfoOrClosedFileInfo"]>;
export interface DefaultConfiguredProjectResult {
defaultProject: ConfiguredProject | undefined;
tsconfigProject: ConfiguredProject | undefined;
sentConfigDiag: Set<ConfiguredProject>;
seenProjects: ConfigureProjectToLoadKind;
seenConfigs: Set<NormalizedPath> | undefined;
}

/** @internal */
export interface FindCreateOrLoadConfiguredProjectResult {
@@ -4534,7 +4540,7 @@ export class ProjectService {
allowDeferredClosed?: boolean,
/** Used with ConfiguredProjectLoadKind.Reload to check if this project was already reloaded */
reloadedProjects?: ConfiguredProjectToAnyReloadKind,
) {
): DefaultConfiguredProjectResult {
const infoIsOpenScriptInfo = isOpenScriptInfo(info);
const optimizedKind = toConfiguredProjectLoadOptimized(kind);
const seenProjects: ConfigureProjectToLoadKind = new Map();
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
indexedAccessOfConcreteNonpublicFields.ts(4,22): error TS4129: Private member '_property' cannot be accessed via indexed access.


==== indexedAccessOfConcreteNonpublicFields.ts (1 errors) ====
export class Foo {
private _property: string = '';
protected _property2: string = '';
constructor(arg: Foo['_property'], other: Foo['_property2']) {
~~~~~~~~~~~~~~~~
!!! error TS4129: Private member '_property' cannot be accessed via indexed access.
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//// [tests/cases/compiler/indexedAccessOfConcreteNonpublicFields.ts] ////

//// [indexedAccessOfConcreteNonpublicFields.ts]
export class Foo {
private _property: string = '';
protected _property2: string = '';
constructor(arg: Foo['_property'], other: Foo['_property2']) {
}
}

//// [indexedAccessOfConcreteNonpublicFields.js]
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Foo = void 0;
var Foo = /** @class */ (function () {
function Foo(arg, other) {
this._property = '';
this._property2 = '';
}
return Foo;
}());
exports.Foo = Foo;


//// [indexedAccessOfConcreteNonpublicFields.d.ts]
export declare class Foo {
private _property;
protected _property2: string;
constructor(arg: Foo['_property'], other: Foo['_property2']);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//// [tests/cases/compiler/indexedAccessOfConcreteNonpublicFields.ts] ////

=== indexedAccessOfConcreteNonpublicFields.ts ===
export class Foo {
>Foo : Symbol(Foo, Decl(indexedAccessOfConcreteNonpublicFields.ts, 0, 0))

private _property: string = '';
>_property : Symbol(Foo._property, Decl(indexedAccessOfConcreteNonpublicFields.ts, 0, 18))

protected _property2: string = '';
>_property2 : Symbol(Foo._property2, Decl(indexedAccessOfConcreteNonpublicFields.ts, 1, 35))

constructor(arg: Foo['_property'], other: Foo['_property2']) {
>arg : Symbol(arg, Decl(indexedAccessOfConcreteNonpublicFields.ts, 3, 16))
>Foo : Symbol(Foo, Decl(indexedAccessOfConcreteNonpublicFields.ts, 0, 0))
>other : Symbol(other, Decl(indexedAccessOfConcreteNonpublicFields.ts, 3, 38))
>Foo : Symbol(Foo, Decl(indexedAccessOfConcreteNonpublicFields.ts, 0, 0))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//// [tests/cases/compiler/indexedAccessOfConcreteNonpublicFields.ts] ////

=== indexedAccessOfConcreteNonpublicFields.ts ===
export class Foo {
>Foo : Foo
> : ^^^

private _property: string = '';
>_property : string
> : ^^^^^^
>'' : ""
> : ^^

protected _property2: string = '';
>_property2 : string
> : ^^^^^^
>'' : ""
> : ^^

constructor(arg: Foo['_property'], other: Foo['_property2']) {
>arg : string
> : ^^^^^^
>other : string
> : ^^^^^^
}
}
5 changes: 4 additions & 1 deletion tests/baselines/reference/keyofAndIndexedAccess.errors.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
keyofAndIndexedAccess.ts(174,14): error TS4129: Private member 'z' cannot be accessed via indexed access.
keyofAndIndexedAccess.ts(205,24): error TS2322: Type 'T[keyof T]' is not assignable to type 'object'.
Type 'T[string] | T[number] | T[symbol]' is not assignable to type 'object'.
Type 'T[string]' is not assignable to type 'object'.
@@ -15,7 +16,7 @@ keyofAndIndexedAccess.ts(318,5): error TS2322: Type 'T[K]' is not assignable to
Type 'T[string]' is not assignable to type '{}'.


==== keyofAndIndexedAccess.ts (5 errors) ====
==== keyofAndIndexedAccess.ts (6 errors) ====
class Shape {
name: string;
width: number;
@@ -190,6 +191,8 @@ keyofAndIndexedAccess.ts(318,5): error TS2322: Type 'T[K]' is not assignable to
type X = C["x"];
type Y = C["y"];
type Z = C["z"];
~~~~~~
!!! error TS4129: Private member 'z' cannot be accessed via indexed access.
let x: X = c["x"];
let y: Y = c["y"];
let z: Z = c["z"];
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// @declaration: true
export class Foo {
private _property: string = '';
protected _property2: string = '';
constructor(arg: Foo['_property'], other: Foo['_property2']) {
}
}