angular/angular · error
Unsupported property access for type reference: ${expression
Error message
Unsupported property access for type reference: ${expression.name.text} What it means
Thrown by the compiler's TypeScriptAstFactory helper getEntityTypeFromExpression while converting an expression into an EntityName (identifier or dotted qualified name) for a type reference. When the expression is a property access, its member must be a plain identifier; the only TS nodes with a non-identifier 'name' on a property access are private identifiers, so in practice this means a '#private' member was used in a type-entity position.
Source
Thrown at packages/compiler-cli/src/ngtsc/translator/src/typescript_ast_factory.ts:551
comment.toString(),
comment.trailingNewline,
);
} else {
for (const line of comment.toString().split('\n')) {
ts.addSyntheticLeadingComment(statement, commentKind, line, comment.trailingNewline);
}
}
}
}
function getEntityTypeFromExpression(expression: ts.Expression): ts.EntityName {
if (ts.isIdentifier(expression)) {
return expression;
}
if (ts.isPropertyAccessExpression(expression)) {
const left = getEntityTypeFromExpression(expression.expression);
if (!ts.isIdentifier(expression.name)) {
throw new Error(`Unsupported property access for type reference: ${expression.name.text}`);
}
return ts.factory.createQualifiedName(left, expression.name);
}
throw new Error(`Unsupported expression for type reference: ${ts.SyntaxKind[expression.kind]}`);
}
View on GitHub (pinned to 51cb07e980)
Solutions
- Remove the '#' reference: use the public counterpart or export a dedicated type/class for the thing being referenced.
- If the member must stay private, expose a public constant or type alias and reference that instead.
- If generating code, restrict emitted entity names to identifiers and dotted public property accesses.
Example fix
// before
export class Registry { static #impl = {...}; }
type Impl = typeof Registry.#impl; // '#impl' is a PrivateIdentifier, not an Identifier
// after
export class Registry { static impl = {...}; }
type Impl = typeof Registry.impl; Defensive patterns
Strategy: type-guard
Type guard
import ts from 'typescript';
/** true iff expr can become an EntityName (identifier or dotted public property access) */
function isEntityNameExpression(expr: ts.Expression): boolean {
if (ts.isIdentifier(expr)) return true;
return (
ts.isPropertyAccessExpression(expr) &&
ts.isIdentifier(expr.name) && // rejects PrivateIdentifier '#x'
isEntityNameExpression(expr.expression)
);
} Prevention
- Do not reference '#private' members in any position Angular converts to a type reference.
- Expose public constants/types for anything metadata must name.
- Run isEntityNameExpression in code generators before emitting type references.
When it happens
Trigger: Referencing a private class member like Class.#member anywhere Angular must turn an expression into a type reference — e.g. typeof SomeClass.#constant or namespace-qualified symbols built through property access chains during JIT/AST emission.
Common situations: Adopting ECMAScript private fields and then referencing them in decorator or type metadata Angular processes; code generators that emit property-access chains and accidentally include private names.
Related errors
- Unsupported expression for type reference: ${ts.SyntaxKind[e
- SYMBOL_NOT_EXPORTED
- The target of a typeof expression must be a type reference,
- Unable to statically determine the declaration file of type
- The Angular Compiler requires TypeScript >=${minVersion} and
AI-assisted analysis of angular/angular@51cb07e980 (2026-08-22).
Data as JSON: /api/errors/35e7ff6479da1946.
Report an issue: GitHub.