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

  1. Remove the '#' reference: use the public counterpart or export a dedicated type/class for the thing being referenced.
  2. If the member must stay private, expose a public constant or type alias and reference that instead.
  3. 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

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


AI-assisted analysis of angular/angular@51cb07e980 (2026-08-22). Data as JSON: /api/errors/35e7ff6479da1946. Report an issue: GitHub.