Skip to content

Commit 674bb58

Browse files
Smaug123claude
andcommitted
Reject tagged types which claim the runtime-module prefix
The embedded runtime module is named ArgParserRuntime_<firstTaggedType> and generated parser modules are named after their tagged types, so a tagged type named ArgParserRuntime_Foo alongside a tagged type Foo generated two modules with one name, which does not compile. The prefix was already documented as reserved; enforce the reservation at generation time for the names the untyped AST can see. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 1ea032c commit 674bb58

3 files changed

Lines changed: 40 additions & 1 deletion

File tree

WoofWare.Myriad.Plugins/ArgParserGenerator.fs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1855,6 +1855,18 @@ module internal ArgParserGenerator =
18551855
)
18561856
)
18571857

1858+
// The runtime-module prefix is reserved: a tagged type named e.g. ArgParserRuntime_Foo
1859+
// would generate a parser module colliding with the runtime module emitted for a
1860+
// namespace whose first tagged type is Foo. Enforce the reservation where it is visible;
1861+
// collisions with *untagged* user declarations cannot be seen on the untyped AST, so for
1862+
// those the prefix is documented as reserved.
1863+
for _, (taggedType, _), _, _ in namespaceAndTypes do
1864+
let name = SynTypeDefn.getName taggedType |> List.last |> _.idText
1865+
1866+
if name.StartsWith ("ArgParserRuntime_", StringComparison.Ordinal) then
1867+
failwith
1868+
$"Type names beginning 'ArgParserRuntime_' are reserved: the ArgParser generator emits its runtime module under that prefix alongside the generated parsers. Rename the [<ArgParser>] type '%s{name}'."
1869+
18581870
// Each namespace containing a generated parser gets one embedded runtime module,
18591871
// named after the first [<ArgParser>] type in that namespace (see
18601872
// ArgParserRuntimeEmbed.moduleName for why that cannot collide).

WoofWare.Myriad.Plugins/ArgParserRuntimeEmbed.fs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,7 @@ module internal ArgParserRuntimeEmbed =
5454
/// parser is being generated there. That type's name is necessarily unique across the
5555
/// project's input files (two files defining the same type in one namespace would fail to
5656
/// compile anyway), unlike e.g. the input file's base name, so runtime modules emitted into
57-
/// the same namespace from different generated files cannot collide.
57+
/// the same namespace from different generated files cannot collide. Nor can the result
58+
/// collide with a generated *parser* module (which is named after its tagged type): the
59+
/// generator rejects tagged types whose names begin with this prefix.
5860
let moduleName (taggedTypeName : string) : string = "ArgParserRuntime_" + taggedTypeName

WoofWare.Myriad.Plugins/Test/TestArgParserRejection.fs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,31 @@ type Args =
238238
|> shouldRejectWith
239239
"Invalid argument name 'foo=bar' for field 'A': a --key=value token splits at its first '=', so this argument could never be addressed."
240240

241+
[<Test>]
242+
let ``Tagged type names may not claim the reserved runtime-module prefix`` () =
243+
// The generator emits one runtime module per namespace, named
244+
// ArgParserRuntime_<firstTaggedType>; a tagged type named ArgParserRuntime_Foo alongside
245+
// a tagged type Foo would therefore generate two modules with the same name, which does
246+
// not compile. The prefix is documented as reserved; enforce it where we can see it.
247+
"""namespace TestMe
248+
249+
open WoofWare.Myriad.Plugins
250+
251+
[<ArgParser>]
252+
type Foo =
253+
{
254+
A : int
255+
}
256+
257+
[<ArgParser>]
258+
type ArgParserRuntime_Foo =
259+
{
260+
B : int
261+
}
262+
"""
263+
|> shouldRejectWith
264+
"Type names beginning 'ArgParserRuntime_' are reserved: the ArgParser generator emits its runtime module under that prefix alongside the generated parsers. Rename the [<ArgParser>] type 'ArgParserRuntime_Foo'."
265+
241266
[<Test>]
242267
let ``The reserved name help cannot be claimed, in any casing`` () =
243268
"""namespace TestMe

0 commit comments

Comments
 (0)