Skip to content

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

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
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
Expand Up @@ -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.
Expand All @@ -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));
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
10 changes: 8 additions & 2 deletions src/server/editorServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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();
Expand Down
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'.
Expand All @@ -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;
Expand Down Expand Up @@ -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"];
Expand Down
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']) {
}
}