-
Notifications
You must be signed in to change notification settings - Fork 76
feat(SourceGenerator): Scaffold + [<ArguGenerate>] marker attribute #318
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
bartelink
merged 5 commits into
fsprojects:master
from
dimension-zero:pr/30-source-generator-companion
Jun 8, 2026
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
e827557
feat(SourceGenerator): Scaffold + [<ArguGenerate>] marker attribute
dimension-zero 5d2afc8
chore(SourceGenerator): packaging hygiene per Copilot review
dimension-zero a6194f4
docs(SourceGenerator): Rewrite ArguGenerate doc comment per review
dimension-zero 18cd08a
CL
bartelink 30c1e6b
Collapse folders
bartelink File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 17 additions & 0 deletions
17
samples/Argu.Samples.SourceGenerated/Argu.Samples.SourceGenerated.fsproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <PropertyGroup> | ||
| <OutputType>Exe</OutputType> | ||
| <TargetFramework>net10.0</TargetFramework> | ||
| <IsPackable>false</IsPackable> | ||
| </PropertyGroup> | ||
| <ItemGroup> | ||
| <Compile Include="Program.fs"/> | ||
| </ItemGroup> | ||
| <ItemGroup> | ||
| <ProjectReference Include="..\..\src\Argu\Argu.fsproj"/> | ||
| <ProjectReference Include="..\..\src\Argu.SourceGenerator\Argu.SourceGenerator.fsproj"/> | ||
| </ItemGroup> | ||
| <ItemGroup> | ||
| <PackageReference Include="FSharp.Core"/> | ||
| </ItemGroup> | ||
| </Project> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| module Argu.Samples.SourceGenerated.Program | ||
|
|
||
| open Argu | ||
| open Argu.SourceGenerator | ||
|
|
||
| /// CLI template annotated with the source-generator marker. Today | ||
| /// nothing is generated; the marker is inert and Argu's reflection | ||
| /// path still computes the schema at runtime. When the generator | ||
| /// ships, this same template will gain a compile-time-built schema | ||
| /// and stop calling Activator.CreateInstance. | ||
| [<ArguGenerate>] | ||
| type SampleArgs = | ||
| | [<Mandatory>] Port of int | ||
| | Verbose | ||
| interface IArgParserTemplate with | ||
| member this.Usage = | ||
| match this with | ||
| | Port _ -> "port to bind to" | ||
| | Verbose -> "verbose output" | ||
|
|
||
| [<EntryPoint>] | ||
| let main argv = | ||
| let parser = ArgumentParser.Create<SampleArgs>(programName = "demo") | ||
| let results = parser.Parse(argv, ignoreMissing = true, raiseOnUsage = false) | ||
|
|
||
| match results.TryGetResult(Port) with | ||
| | Some p -> printfn "port: %d" p | ||
| | None -> printfn "port: (unspecified)" | ||
|
|
||
| if results.Contains(Verbose) then printfn "verbose: true" | ||
|
|
||
| 0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <PropertyGroup> | ||
| <TargetFramework>netstandard2.0</TargetFramework> | ||
| <GenerateDocumentationFile>true</GenerateDocumentationFile> | ||
| <GenerateAssemblyInfo>true</GenerateAssemblyInfo> | ||
| <Description>Marker attributes and (planned) source generator for compile-time Argu schema emission.</Description> | ||
| <PackageTags>F#, argument, commandline, parser, source-generator, aot</PackageTags> | ||
| <PackageReadmeFile>README.md</PackageReadmeFile> | ||
| </PropertyGroup> | ||
| <ItemGroup> | ||
| <Compile Include="AttributeMarkers.fs"/> | ||
| </ItemGroup> | ||
| <ItemGroup> | ||
| <None Include="README.md" Pack="true" PackagePath=""/> | ||
| </ItemGroup> | ||
| <ItemGroup> | ||
| <!-- SourceLink etc --> | ||
| <PackageReference Include="DotNet.ReproducibleBuilds" PrivateAssets="All"/> | ||
| <PackageReference Include="FSharp.Core"/> | ||
| </ItemGroup> | ||
|
bartelink marked this conversation as resolved.
|
||
| </Project> | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| namespace Argu.SourceGenerator | ||
|
|
||
| open System | ||
|
|
||
| /// <summary> | ||
| /// Opts an Argu CLI template into compile-time schema generation: its | ||
| /// <c>ArgumentParser<'T></c> is then built without runtime reflection, | ||
| /// which is what makes publish-AOT viable and removes the reflection cost | ||
| /// from startup. | ||
| /// | ||
| /// Apply to a discriminated union that implements <c>IArgParserTemplate</c>. | ||
| /// The union's cases and their Argu attributes must be resolvable at compile | ||
| /// time — the generator reads the type statically, so a template whose shape | ||
| /// is built or varied at runtime cannot be generated for. | ||
| /// </summary> | ||
|
bartelink marked this conversation as resolved.
|
||
| /// <remarks> | ||
| /// The generator that consumes this attribute does not exist yet. This | ||
| /// package currently ships the marker alone, so applying it has no build-time | ||
| /// effect beyond recording intent. Code annotated now gains reflection-free | ||
| /// parsing once the generator lands, with no further source changes. | ||
| /// </remarks> | ||
| [<AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)>] | ||
| type ArguGenerateAttribute() = | ||
| inherit Attribute() | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # Argu.SourceGenerator | ||
|
|
||
| Companion package for compile-time Argu schema generation. Right now it ships only the `[<ArguGenerate>]` marker attribute; the generator that consumes it will follow in a later release. | ||
|
|
||
| ## Why | ||
|
|
||
| Argu's runtime schema computation uses reflection (`FSharpType.GetUnionCases`, `MakeGenericType` + `Activator.CreateInstance`). This is incompatible with publish-AOT, where `Activator.CreateInstance` over a runtime-built generic type cannot be JIT-compiled. The reflection path also slows startup for large CLI templates. | ||
|
|
||
| A source generator, given an annotated DU, can emit: | ||
|
|
||
| - A frozen `UnionArgInfo` record built from compile-time-known metadata | ||
| - A `Create`-equivalent factory that skips the reflection walk | ||
|
|
||
| Consumers opt in per template: | ||
|
|
||
| ```fsharp | ||
| open Argu.SourceGenerator | ||
|
|
||
| [<ArguGenerate>] | ||
| type Args = | ||
| | [<Mandatory>] Port of int | ||
| | Verbose | ||
| interface IArgParserTemplate with | ||
| member this.Usage = ... | ||
| ``` | ||
|
|
||
| ## Status | ||
|
|
||
| | Component | State | | ||
| |---|---| | ||
| | `[<ArguGenerate>]` marker | Shipped | | ||
| | Generator producing schema | Planned | | ||
| | Companion `ArgumentParser` factory consuming the schema | Planned | | ||
|
|
||
| ## Why a separate package | ||
|
|
||
| Keeping the marker, the generator, and the runtime-bypass factory in a separate NuGet package preserves the core `Argu` package's zero-dependency footprint. Users that don't need AOT see no churn. | ||
|
|
||
| ## Roadmap | ||
|
|
||
| 1. Marker + scaffold (this PR) | ||
| 2. F# source generator (Roslyn `FSharp.Compiler.Service` or external tool like Myriad) emitting a `precomputeSchemaFor<'T>()` function for the simplest schemas (no subcommands, no custom attributes) | ||
| 3. Companion `ArgumentParser.CreateFromPrecomputed<'T>` overload that bypasses the reflection path | ||
| 4. Expand generator coverage to subcommands, custom assignment attributes, inheritance and aliasing |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.