Skip to content

[Bug]: Can not reuse declaration expressions? #12413

Open

Description

Describe the bug

Wonder if declaration expressions are reusable i.e. one can refer them elsewhere in the code?
Below are examples of probable reuse, both failing.
TIA

Reproduction

model Foo {
  id: string,
  type: enum Type { bebebe, bububu },
  aux_types: Type[] = #[], // ERR: Unknown identifier Type
}

model Bar {
  id: string,
  type: Type, // ERR: Unknown identifier Type
}

Checklist

Activity

  1. github-actions commented on Oct 28, 2026

    @github-actions
    Contributor

    🎯 Issue Triage

    Inline named declaration expressions such as enum Type { ... } do not currently introduce a reusable binding in the surrounding model or namespace. Supporting references to those names is already tracked as a language/compiler design request.

    Category: Feature Request
    Area: compiler:core

    🔁 Possible duplicate of #189 — that issue tracks expression forms for declarations, including named inline enums/models and the question of whether and how those names can be referenced.

    🔍 Analysis

    The diagnostic is produced by compiler name resolution: the inline enum is a type expression, and its displayed name does not create a symbol named Type that sibling properties or Bar can resolve. This is the same underlying language-design gap described in #189 rather than an emitter-specific failure.

    📝 Suggested next steps
    • Confirm this use case is covered by Expression forms for declarations #189 and close this issue as a duplicate if so.
    • Until reusable expression bindings are designed, declare enum Type at namespace scope and reference it from both models.

    Generated by Agentic Triage

  2. timotheeguerin commented on Oct 28, 2026

    @timotheeguerin
    Member

    yeah we did discuss the scope of named expression and think it would create a much larger problem we they were hoisted to the namespace scope.
    I guess we could consider introducing those direct parent scope but that is a whole new independent feature. (Like you can't reference another property without using the model name right now)

  3. dvv commented on Oct 28, 2026

    @dvv
    Author

    Aha, I see. Thank you for clarification.
    Is that new feature planned short/middle-time?

  4. timotheeguerin commented on Oct 28, 2026

    @timotheeguerin
    Member

    it isn't this would be the first mention of it, I was more explaining from a compiler behavior point of view.
    I don't expect this to be added soon though, feel free to share more use case

  5. dvv commented on Oct 28, 2026

    @dvv
    Author

    First of all I would like to control what hoisted (i call it "materialized") at what name, to control the form it gets in openapi.
    Example:

    // burden library portion
    alias GuessCreateModel<T extends Model> = someFunctionOrStandardType(T);
    alias GuessCreatedModel<T extends Model> = someFunctionOrStandardType(T);
    interface CRUD<
      T extends Model,
      Create extends Model = HOIST(GuessCreateModel<T>, "{name}Create"), // FIXME: this would become a hoisted model, truly named component in openapi
      Created = HOIST(GuessCreatedModel<T>, "{name}Created"), // FIXME: this would become a hoisted model, truly named component in openapi
    > {
      op create(
        @HOIST() @bodyRoot input: Create // FIXME: operation parameters would become a hoisted model `FooCreateInput`, truly named component in openapi, ref-ing FooCreate
      ): Created | ...; // FIXME: operation return would become a hoisted model `FooCreateOutput`, truly named component in openapi, ref-ing FooCreated
    }
    
    // clean main
    model Foo { id: string, name: string }
    interface Foos extends CRUD<Foo> {}

    In other words, I would like to offload the routine portion of defining a lengthy CRUD-like API to helper functions.

    If it can not be done I would like to have kinda what other languages call macros -- again, to replace the repeatable parts of code with their expanded form -- the result of expansion should be both semantically and syntactically the same as one written by hands.
    Example:

    // burden library portion
    macro DefineCRUD<T, CreateError = never> {
      model ${T::name}Create { ...someFunctionOrStandardType(T) }
      model ${T::name}Created { ...someFunctionOrStandardType(T) }
      model ${T::name}CreateInput {
        @bodyRoot input: ${T::name}Create,
      }
      union ${T::name}Output { model ${T::name}Created, CreateError }
      interface IFoos {
        op create(...${T::name}CreateInput): ${T::name}Output;
      }
    }
    
    // clean main
    model Foo { id: string, name: string }
    expand(DefineCRUD<Foo>);
    interface Foos extends IFoos {}
  6. added
    design:neededA design request has been raised that needs a proposal
    and removed
    bugSomething isn't working
    on Oct 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    compiler:coreIssues for @typespec/compilerdesign:neededA design request has been raised that needs a proposal

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions