Skip to content

[Bug]: @friendlyName on enum is skipped when enum is first referenced inside a template` #12345

Closed

Description

@dalbrx-nitrobox

Describe the bug

Since TypeSpec 1.17.0, decorators on an enum can be skipped when the enum is first resolved through a property of a templated model.
In particular, @friendlyName is not applied. Consequently, the OpenAPI emitter uses the fully qualified TypeSpec name instead of the configured friendly name.
This appears to be a regression from TypeSpec 1.16.0.

Expected behavior
FriendlyName should be applied:

$ref: '#/components/schemas/AggregationType'
The generated component should be named AggregationType, as it was with TypeSpec 1.16.0.

Additional information

The issue seems to depend on the enum being resolved for the first time from within a template declaration.

As a workaround, referencing the enum through an alias causes the decorators to be evaluated correctly:

alias AggregationTypeRef = AggregationType;
model Query<T = string> {
  aggregationType: AggregationTypeRef;
  field: T;
}

This suggests that InTemplateDeclaration remains set while resolving a concrete enum reference. Enum declarations may be missing from the declarations that cause the checker to leave the template context.

This potentially affects decorators other than FriendlyName as well.

Might be related to #11019

Reproduction

import "@typespec/http";
import "@typespec/openapi3";
using TypeSpec.Http;
namespace Example.Models;

@friendlyName("AggregationType")
enum AggregationType {
  SUM,
  COUNT,
}

model Query<T = string> {
  aggregationType: AggregationType;
  field: T;
}

op statistics(...Query): void;

Compile this specification with @typespec/openapi3.

Actual behavior with TypeSpec 1.17.0
The generated reference contains the namespace:

$ref: '#/components/schemas/Example.Models.AggregationType'
The generated component is named Example.Models.AggregationType.

Checklist

Activity

  1. github-actions commented on Oct 22, 2026

    @github-actions
    Contributor

    🎯 Issue Triage

    TypeSpec 1.17.0 can skip decorators on an enum when its first resolution occurs through a templated model property. This prevents @friendlyName metadata from reaching emitters and changes the generated OpenAPI schema name.

    Category: Bug

    Area: compiler:core

    🔗 [Playground Reproduction]((typespec.io/redacted)

    📋 Reproduction code
    import "@typespec/http";
    import "@typespec/openapi3";
    using TypeSpec.Http;
    namespace Example.Models;
    
    @friendlyName("AggregationType")
    enum AggregationType {
      SUM,
      COUNT,
    }
    
    model Query<T> {
      aggregationType: AggregationType;
      field: T;
    }
    
    op statistics(...Query<string>): void;
    🔍 Analysis

    The behavior is most consistent with compiler checker state leaking template-declaration context while resolving the concrete enum. The enum then misses decorator evaluation, and @typespec/openapi3 falls back to the fully qualified name. The alias workaround changes resolution order and supports this diagnosis. The checker changes in #9588 are relevant to investigate, but duplicate searches found no issue describing this same regression.

    📝 Suggested next steps

    Generated by Agentic Triage

  2. timotheeguerin commented on Oct 22, 2026

    @timotheeguerin
    Member

    I think this is an unexpected behavior of friendlyName. That decorator was uniquely meant to be used on template to give each instance a name https://typespec.io/docs/standard-library/built-in-decorators/#@friendlyName.

    What is your goal here to have all models defined in Examples.Models to be flattened to the root of the service? A better solution might be an option in openapi3 emitter to toggle this scoping

  3. dalbrx-nitrobox commented on Oct 22, 2026

    @dalbrx-nitrobox
    Author

    Thanks, that clarification about the intended use of @friendlyName makes sense.

    Our goal is not necessarily to flatten every model in Example.Models. These are public models provided by a TypeSpec library, and we want to preserve their existing OpenAPI component names for compatibility. An OpenAPI emitter option controlling namespace qualification could indeed be a better long-term solution.

    However, I think there is a separate compiler regression behind this example. In 1.17, it appears that all decorators on a concrete enum are skipped when the enum is first resolved from within a template declaration, not only @friendlyName.

    The behavior also changes when introducing an alias:

    alias AggregationTypeRef = AggregationType;

    Using AggregationTypeRef inside the template causes the enum decorators to run again. This suggests that the concrete enum is resolved while InTemplateDeclaration is still set.

    PR #11019 changed checkEnum to call:

    finishType(enumType, {
      skipDecorators: ctx.hasFlags(CheckFlags.InTemplateDeclaration),
    });
    

    However, enums do not appear to be included among the concrete declarations that clear InTemplateDeclaration when followed through a type reference.

    Would skipping every decorator on such an enum be considered expected? If not, I can reframe the issue around decorator execution rather than OpenAPI schema naming.

  4. timotheeguerin commented on Oct 22, 2026

    @timotheeguerin
    Member

    I see yeah no that does seem like a regression, Repro without friendlyName

    model Template<T = string> {
      aggregationType: AggregationType;
    }
    
    @doc("Aggregation documentation")
    enum AggregationType {
      SUM,
      COUNT,
    }
    
    op statistics(...Template): void;

    playground

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

    bugSomething isn't workingcompiler:coreIssues for @typespec/compiler

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions