Skip to content

Add ParamTag[T] and ResultTag[T] type-targeted annotations#1289

Draft
destel wants to merge 6 commits into
uber-go:masterfrom
destel:f/generic-tags
Draft

Add ParamTag[T] and ResultTag[T] type-targeted annotations#1289
destel wants to merge 6 commits into
uber-go:masterfrom
destel:f/generic-tags

Conversation

@destel
Copy link
Copy Markdown

@destel destel commented May 9, 2026

Draft/Proposal: Opening this for design feedback. Happy to iterate on the API, naming, or scope based on input.

Adding a parameter to a constructor in fx is usually a free change - the DI graph wires it from an existing provider. That stops being free when there are multiple providers of the same type: now we have to tell fx which one to pick by attaching a name to the parameter.

One way to do this is fx.Annotate. In typical fx codebases, fx.Annotate calls live in module assembly files, far from the constructors they annotate. This makes positional tagging via fx.ParamTags / fx.ResultTags error-prone:

  • Adding new parameters can silently shift tags.
  • Reordering parameters does the same.
  • Single-parameter tagging requires padding. fx.ParamTags("", "", "", name:"ro") to tag just the fourth parameter is verbose, and the reader has to count positions.

This PR is a proposal to add two new functions, fx.ParamTag[T] and fx.ResultTag[T], that let us tag constructor params/results based on their type rather than position:

// Before - positional, fragile:
fx.Annotate(NewService,
    fx.ParamTags(``, ``, `name:"ro"`, ``, `name:"primary"`),
)

// After - type-targeted:
fx.Annotate(NewService,
    fx.ParamTag[*sql.DB](`name:"ro"`),
    fx.ParamTag[*Cache](`name:"primary"`),
)

If NewService parameters change, the second form either keeps working, or fails with a clear error at apply time if the targeted type is gone.

When there are multiple parameters of the same type, it's possible to target a specific one with the second argument passed to fx.ParamTag:

fx.Annotate(NewService,
    fx.ParamTag[*sql.DB](`name:"ro"`),
    fx.ParamTag[*sql.DB](`name:"rw"`, 2), // targets the second *sql.DB parameter
)

I'm proposing 1-based indexing here - it matches how we naturally speak about function arguments (1st DB, 2nd DB, not 0th DB).

Tagging of constructor results is done in a very similar way with fx.ResultTag[T].

Implementation

The change is minimal and backwards compatible. Roughly speaking, under the hood type-targeted annotations are converted into positional ones and delegated to the stock fx.ParamTags / fx.ResultTags. All new behaviors are covered with tests.

Alternative API designs

I picked a simple API design that stays as close as possible to existing fx functions, but I'm open to other shapes. Some ideas I thought of:

  • Composable name/group helpers: fx.ParamName[T], fx.ParamGroup[T], fx.ResultName[T], fx.ResultGroup[T]and groups
  • Bulk-tagging results: for results specifically, APIs like "name them all" or "name all of type T" can be more useful in practice than "name n-th result of type T"

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented May 9, 2026

CLA assistant check
All committers have signed the CLA.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants