[Pitch] Add `unprefixed` and `unsuffixed` Naming Options to Attached Macros

Attached macros using the peer, member, and accessor roles currently require a names: argument to declare the names of symbols that the macro will generate. The supported options for this argument are:

  • named(<#name#>)
  • overloaded
  • prefixed(<#prefix#>)
  • suffixed(<#suffix#>)
  • arbitrary (prohibited for peer macros)

Ignoring arbitrary since this option is prohibited in peer macros, only prefixed and suffixed offer dynamic name generation derived from the attached type. However, because they strictly prepend or append to the type name, the generated names must:

  1. Be longer than the original type name
  2. Contain the full attached type name as a substring

This limitation can be problematic in scenarios where naming conventions are semantically inverted. For example:

A Limiting Use Case

Imagine a macro @GenerateProtocol that reads the public interface of the attached type and emits a corresponding protocol. Suppose a project adopts the convention of:

  • Generalized names for protocols (e.g. Database)
  • Specialized names for concrete implementations (e.g. ProductionDatabase)

If I attach @GenerateProtocol to ProductionDatabase, there is currently no way to dynamically derive the name Database using existing names: options. The only solution is to hard-code the target name using named(Database), which:

  • Is brittle and unsustainable at scale
  • Requires the macro definition to be aware of client-specific types
  • Defeats the purpose of automatic naming

Proposed Solution: unprefixed(...) and unsuffixed(...)

To improve ergonomic, semantically meaningful naming, I propose two new options for the names: argument:

  • unprefixed(<#prefix#>): Strips the given prefix from the attached type name
  • unsuffixed(<#suffix#>): Strips the given suffix from the attached type name

These options allow for type-name derivation based on naming patterns without requiring hardcoded values.

Example:

// Macro module

@attached(peer, names: unprefixed(Production))
public macro GenerateProtocol() = #externalMacro(...)

// App module

@GenerateProtocol
class ProductionDatabase: Database { ... }

// Generated code:

protocol Database { ... }

This supports a clean and maintainable relationship between generated code and client types, especially in large codebases.

Benefits

  • Enables more expressive and flexible naming patterns
  • Preserves peer macro constraints (avoids arbitrary names)
  • Encourages scalable naming conventions
  • Requires no knowledge of client-specific naming in macro libraries

Alternative Names Considered

If unprefixed/unsuffixed feel too unconventional, other viable names include:

  • dropPrefix(...) / dropSuffix(...)
  • trimPrefix(...) / trimSuffix(...)
  • stripPrefix(...) / stripSuffix(...)

I'm looking forward to hearing your feedback.

7 Likes

Another (possibly controversial) idea for naming would be to take inspiration from Noncopyable and use:

  • prefixed(~PrefixToRemove)
  • suffixed(~SuffixToRemove)

The semantics are similar to ~Copyable—We are indicating that the specified prefix/suffix will be removed from the type name rather than added, just like the implicit Copyable conformance/constraint is removed when using ~Copyable.

1 Like

FWIW I believe there might also be some unresolved behaviors where names should be required but are not. I think it might be a good idea — and potentially block on this pitch — to first of all try and maybe clean up any of those loose ends and either update the documentation (if the implementation is correct) or update the implementation (if the documentation is correct).

1 Like