Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions Argu.slnx
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
<Solution>
<Folder Name="/.config/">
<File Path=".config/dotnet-tools.json" />
</Folder>
<Folder Name="/.github/">
<File Path=".github/ISSUE_TEMPLATE.md" />
</Folder>
Expand All @@ -10,6 +7,7 @@
<File Path=".github/workflows/release.yml" />
</Folder>
<Folder Name="/.project/">
<File Path=".config\dotnet-tools.json" />
<File Path=".dockerignore" />
<File Path=".editorconfig" />
<File Path=".gitignore" />
Expand Down Expand Up @@ -37,11 +35,11 @@
<Folder Name="/resource/">
<File Path="resource/logo.png" />
</Folder>
<Folder Name="/samples/">
<Project Path="samples/Argu.Samples.LS/Argu.Samples.LS.fsproj" />
<Project Path="samples/Argu.Samples.Introspect/Argu.Samples.Introspect.fsproj" />
</Folder>
<Project Path="samples/Argu.Samples.Introspect/Argu.Samples.Introspect.fsproj" />
<Project Path="samples/Argu.Samples.LS/Argu.Samples.LS.fsproj" />
<Project Path="samples/Argu.Samples.SourceGenerated/Argu.Samples.SourceGenerated.fsproj" />
<Project Path="src/Argu/Argu.fsproj" />
<Project Path="tests/Argu.Tests/Argu.Tests.fsproj" />
<Project Path="src/Argu.SourceGenerator/Argu.SourceGenerator.fsproj" />
<Project Path="benchmarks/Argu.Benchmarks/Argu.Benchmarks.fsproj" />
<Project Path="tests/Argu.Tests/Argu.Tests.fsproj" />
</Solution>
5 changes: 3 additions & 2 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
* Fix Report all missing args in error message, not just first level [#297](https://github.com/fsprojects/Argu/pull/297) [@dimension-zero](https://github.com/dimension-zero)
* Fix Limit min wordwrap column to 20 [#302](https://github.com/fsprojects/Argu/pull/302) [@dimension-zero](https://github.com/dimension-zero)
* Fix [misrendering of usage when help blank](https://github.com/fsprojects/Argu/issues/173) [#323](https://github.com/fsprojects/Argu/pull/323) [@DominikL1999](https://github.com/DominikL1999)
* Add `Separator("=", orSpace = true)` and `Separator "="` attribute syntax to replace `Obsoleted` `EqualsAssignment`, `ColonAssignment`, `CustomAssignment`, `EqualsAssignmentOrSpaced`, `ColonAssignmentOrSpaced` and `CustomAssignmentOrSpaced` [#315](https://github.com/fsprojects/Argu/pull/315) [@dimension-zero](https://github.com/dimension-zero)
* Add `Separator("=", orSpace = true)` and `Separator "="` attribute syntax to replace `Obsolete`d `EqualsAssignment`, `ColonAssignment`, `CustomAssignment`, `EqualsAssignmentOrSpaced`, `ColonAssignmentOrSpaced` and `CustomAssignmentOrSpaced` [#315](https://github.com/fsprojects/Argu/pull/315) [@dimension-zero](https://github.com/dimension-zero)
* Add `Argu.Samples.Introspect` sample [#298](https://github.com/fsprojects/Argu/pull/298) [@dimension-zero](https://github.com/dimension-zero)
* Add `ArgumentParser.Parse(ParseConfig)` [#307](https://github.com/fsprojects/Argu/pull/307) [@dimension-zero](https://github.com/dimension-zero)
* Add `ArgumentParser.PrintUsage(..., ?UsageStrings)` for localization support [#303](https://github.com/fsprojects/Argu/pull/303) [@dimension-zero](https://github.com/dimension-zero)
* Add AOT annotations on [#314](https://github.com/fsprojects/Argu/pull/314) [@dimension-zero](https://github.com/dimension-zero)
* Add AOT annotations [#314](https://github.com/fsprojects/Argu/pull/314) [@dimension-zero](https://github.com/dimension-zero)
* Add `SourceGenerator.ArguGenerate` marker [#318](https://github.com/fsprojects/Argu/pull/318) [@dimension-zero](https://github.com/dimension-zero)
* Obsolete `EqualsAssignmentAttribute`, `ColonAssignmentAttribute`, `CustomAssignmentAttribute`, `EqualsAssignmentOrSpacedAttribute`, `ColonAssignmentOrSpacedAttribute` and `CustomAssignmentOrSpacedAttribute` [#315](https://github.com/fsprojects/Argu/pull/315) [@dimension-zero](https://github.com/dimension-zero)
* Obsolete `PostProcessResult`, `PostProcessResults`, `TryPostProcessResult` [#296](https://github.com/fsprojects/Argu/pull/296) [@dimension-zero](https://github.com/dimension-zero)

Expand Down
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>
32 changes: 32 additions & 0 deletions samples/Argu.Samples.SourceGenerated/Program.fs
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
21 changes: 21 additions & 0 deletions src/Argu.SourceGenerator/Argu.SourceGenerator.fsproj
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>
Comment thread
bartelink marked this conversation as resolved.
<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>
Comment thread
bartelink marked this conversation as resolved.
</Project>
24 changes: 24 additions & 0 deletions src/Argu.SourceGenerator/AttributeMarkers.fs
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&lt;'T&gt;</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>
Comment thread
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()
44 changes: 44 additions & 0 deletions src/Argu.SourceGenerator/README.md
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
Loading