Skip to content

Add class-hierarchy assignability helpers to the model API (isAssignableTo / getAssignableConcreteTypes)Β #1281

Description

@mttrbrts

Feature Request πŸ›οΈ

Provide first-class helpers on the model API to answer "is type X, or does it extend, type Y?" and "what concrete types are assignable to Y?" β€” without each consumer re-implementing the traversal over getAssignableClassDeclarations().

Use Case

Consumers frequently need to test the runtime/nominal class hierarchy of a model:

  • "Is fqn a subtype of (or equal to) baseFqn?" β€” e.g. validating that a payload's $class is, or extends, a known base type.
  • "Which concrete (non-abstract) declarations are assignable to baseFqn?" β€” e.g. building a union/enumeration of valid subtypes (plus the base itself when it is concrete).

Today the building blocks exist (ClassDeclaration.getAssignableClassDeclarations(), getAllSuperTypeDeclarations(), isAbstract()), but there is no concise, discoverable helper for these two very common questions. The only ModelUtil.isAssignableTo(modelFile, typeName, property) is @private and answers a different question (can a type be assigned to a property), so it can't be reused for a plain subtype check.

Possible Solution

Expose two small, pure helpers (on ModelUtil, ModelManager, or a new introspection util β€” wherever fits best):

/**
 * Concrete (non-abstract) declarations assignable to baseFqn: the base itself
 * (when concrete) plus all subclasses. Empty array if baseFqn is not in the model.
 */
function getAssignableConcreteTypes(modelManager: ModelManager, baseFqn: string): ClassDeclaration[];

/**
 * True when fqn is, or extends, baseFqn (restricted to concrete types).
 */
function isAssignableTo(modelManager: ModelManager, fqn: string, baseFqn: string): boolean;

isAssignableTo can be implemented in terms of getAssignableConcreteTypes (membership test), or more directly by walking getAllSuperTypeDeclarations() from the candidate β€” implementer's choice. Naming is a suggestion; happy to align with existing conventions (note the @private ModelUtil.isAssignableTo name clash to disambiguate).

Context

In accordproject/template-engine#166 we enforce a runtime class hierarchy (State / Request / Response / Event) both at compile time (building a TypeScript union of the assignable concrete types) and at runtime (checking a payload's $class is-a the base). We implemented these two helpers locally in the template-engine's own utils.ts and had two call sites drift apart during review before centralizing them β€” a reviewer specifically flagged that the assignability rule should have a single source of truth. This felt like model-level functionality that belongs in Concerto rather than being re-derived by each downstream project.

Detailed Description

  • Add the helpers described above with unit tests covering: base type present & concrete (included), base type abstract (excluded), subclass (included/assignable), unrelated type (not assignable), and base type absent from the model (empty / false).
  • Purely additive, no breaking changes.
  • Once released, template-engine can drop its local copies and depend on the Concerto helpers. We are not blocking on this β€” template-engine keeps its local implementation for now and will migrate when this lands.

πŸ€– Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions