Skip to content

Commit 0c3dc7f

Browse files
Smaug123claude
andauthored
ArgParser: backtick a record field name when constructing the parsed record (#604)
The assemble callback in toParseSpec rebuilds each field's name as a fresh Ident (via SynLongIdent.create [ Ident.create ident ]) when it constructs the record-construction expression. Fantomas prints an Ident exactly as its idText reads, with no backticking of its own -- it can only reproduce backticks for a name by slicing the original source text at that node's range, and a freshly-constructed Ident has no such range. So a field declared with backticks because its name is not a plain identifier (a space, a keyword, ...) reached the generated file unbackticked and did not compile. Fixed by routing the reconstructed name through Fantomas.FCS.Syntax.PrettyNaming.NormalizeIdentifierBackticks, which already exists in the Fantomas.FCS dependency for exactly this purpose. Audited every other record-construction site in this file and in JsonParseGenerator, RemoveOptionsGenerator, and the two mock generators: all of them reuse the field's original Ident node (SynLongIdent.createI) rather than rebuilding one from a raw string, so none share this bug. Discriminated union case names are likewise reused directly and are unaffected. I did spot two similarly-shaped latent bugs (also reconstructing a name via string concatenation, in ArgParserGenerator's and RemoveOptionsGenerator's Default<FieldName> member-reference convention) -- filing those separately rather than folding them into this fix, since they're a different feature each and orthogonal to this record-construction bug. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 8fd0f61 commit 0c3dc7f

4 files changed

Lines changed: 488 additions & 1 deletion

File tree

ConsumePlugin/Args.fs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,3 +390,26 @@ type NonPositionalBoolList =
390390
{
391391
Flags : bool list
392392
}
393+
394+
/// A record field's name must be a plain identifier at its declaration, or carry backticks; the
395+
/// generator reconstructs the same name as an `Ident` when it builds the expression that
396+
/// constructs this type at runtime, and that reconstruction needs the backticks re-added for
397+
/// exactly the same reason the declaration did, or the generated file does not parse.
398+
///
399+
/// Every field beyond `` ``back\tab`` `` here is a shape F#'s lexer treats as a meaningful bare
400+
/// token in some *other* grammar position, so a naive "does this need backticks" check keeps being
401+
/// wrong about it: `` ``_`` `` is the wildcard pattern, `` ``|A|_|`` `` is an active-pattern name,
402+
/// `` ``mod`` `` is a word-form operator keyword, `` ``__LINE__`` `` is a context-sensitive
403+
/// constant, and `` ``break`` `` is a word reserved "for future use" that parses bare but warns
404+
/// (FS0046) -- an error under `--warnaserror`, which this repo enables. None of them is a valid
405+
/// bare record label.
406+
[<ArgParser true>]
407+
type AwkwardFieldName =
408+
{
409+
``back\tab`` : ChildRecord
410+
``_`` : int
411+
``|A|_|`` : int
412+
``mod`` : int
413+
``__LINE__`` : int
414+
``break`` : int
415+
}

ConsumePlugin/GeneratedArgs.fs

Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7681,3 +7681,357 @@ module NonPositionalBoolList =
76817681

76827682
let parse (args : string list) : NonPositionalBoolList =
76837683
parse' (System.Environment.GetEnvironmentVariable >> Option.ofObj) args
7684+
namespace ConsumePlugin
7685+
7686+
open System
7687+
open System.IO
7688+
open WoofWare.Myriad.Plugins
7689+
7690+
/// Methods to parse arguments for the type AwkwardFieldName
7691+
[<AutoOpen>]
7692+
module AwkwardFieldNameArgParse =
7693+
/// Extension methods for argument parsing
7694+
type AwkwardFieldName with
7695+
7696+
static member parse'
7697+
(getEnvironmentVariable : string -> string option)
7698+
(args : string list)
7699+
: AwkwardFieldName
7700+
=
7701+
let helpText () =
7702+
[
7703+
(sprintf "%s:" "back\\tab")
7704+
(sprintf " %s %s%s%s" (sprintf "--%s" "thing1") "int32" "" "")
7705+
(sprintf " %s %s%s%s" (sprintf "--%s" "thing2") "string" "" "")
7706+
(sprintf "%s %s%s%s" (sprintf "--%s" "_") "int32" "" "")
7707+
(sprintf "%s %s%s%s" (sprintf "--%s" "|-a|_|") "int32" "" "")
7708+
(sprintf "%s %s%s%s" (sprintf "--%s" "mod") "int32" "" "")
7709+
(sprintf "%s %s%s%s" (sprintf "--%s" "__-l-i-n-e__") "int32" "" "")
7710+
(sprintf "%s %s%s%s" (sprintf "--%s" "break") "int32" "" "")
7711+
]
7712+
|> String.concat "\n"
7713+
7714+
let parser_LeftoverArgs : string ResizeArray = ResizeArray ()
7715+
let mutable arg_0 : int option = None
7716+
let mutable arg_1 : string option = None
7717+
let mutable arg_2 : int option = None
7718+
let mutable arg_3 : int option = None
7719+
let mutable arg_4 : int option = None
7720+
let mutable arg_5 : int option = None
7721+
let mutable arg_6 : int option = None
7722+
7723+
let parser_schema : ArgParserRuntime_BasicNoPositionals.ErasedSchema =
7724+
{
7725+
Leaves =
7726+
[
7727+
{
7728+
Id = 0
7729+
Forms = [ "thing1" ]
7730+
AcceptsNegation = false
7731+
Arity = ArgParserRuntime_BasicNoPositionals.ErasedArity.One
7732+
Repeatable = false
7733+
Requirement = ArgParserRuntime_BasicNoPositionals.ErasedRequirement.Required
7734+
TypeDescription = ""
7735+
Help = None
7736+
}
7737+
7738+
{
7739+
Id = 1
7740+
Forms = [ "thing2" ]
7741+
AcceptsNegation = false
7742+
Arity = ArgParserRuntime_BasicNoPositionals.ErasedArity.One
7743+
Repeatable = false
7744+
Requirement = ArgParserRuntime_BasicNoPositionals.ErasedRequirement.Required
7745+
TypeDescription = ""
7746+
Help = None
7747+
}
7748+
7749+
{
7750+
Id = 2
7751+
Forms = [ "_" ]
7752+
AcceptsNegation = false
7753+
Arity = ArgParserRuntime_BasicNoPositionals.ErasedArity.One
7754+
Repeatable = false
7755+
Requirement = ArgParserRuntime_BasicNoPositionals.ErasedRequirement.Required
7756+
TypeDescription = ""
7757+
Help = None
7758+
}
7759+
7760+
{
7761+
Id = 3
7762+
Forms = [ "|-a|_|" ]
7763+
AcceptsNegation = false
7764+
Arity = ArgParserRuntime_BasicNoPositionals.ErasedArity.One
7765+
Repeatable = false
7766+
Requirement = ArgParserRuntime_BasicNoPositionals.ErasedRequirement.Required
7767+
TypeDescription = ""
7768+
Help = None
7769+
}
7770+
7771+
{
7772+
Id = 4
7773+
Forms = [ "mod" ]
7774+
AcceptsNegation = false
7775+
Arity = ArgParserRuntime_BasicNoPositionals.ErasedArity.One
7776+
Repeatable = false
7777+
Requirement = ArgParserRuntime_BasicNoPositionals.ErasedRequirement.Required
7778+
TypeDescription = ""
7779+
Help = None
7780+
}
7781+
7782+
{
7783+
Id = 5
7784+
Forms = [ "__-l-i-n-e__" ]
7785+
AcceptsNegation = false
7786+
Arity = ArgParserRuntime_BasicNoPositionals.ErasedArity.One
7787+
Repeatable = false
7788+
Requirement = ArgParserRuntime_BasicNoPositionals.ErasedRequirement.Required
7789+
TypeDescription = ""
7790+
Help = None
7791+
}
7792+
{
7793+
Id = 6
7794+
Forms = [ "break" ]
7795+
AcceptsNegation = false
7796+
Arity = ArgParserRuntime_BasicNoPositionals.ErasedArity.One
7797+
Repeatable = false
7798+
Requirement = ArgParserRuntime_BasicNoPositionals.ErasedRequirement.Required
7799+
TypeDescription = ""
7800+
Help = None
7801+
}
7802+
]
7803+
Tree =
7804+
(ArgParserRuntime_BasicNoPositionals.ErasedTree.Product (
7805+
[
7806+
ArgParserRuntime_BasicNoPositionals.ErasedTree.Product (
7807+
[
7808+
ArgParserRuntime_BasicNoPositionals.ErasedTree.Leaf 0
7809+
ArgParserRuntime_BasicNoPositionals.ErasedTree.Leaf 1
7810+
]
7811+
)
7812+
7813+
ArgParserRuntime_BasicNoPositionals.ErasedTree.Leaf 2
7814+
ArgParserRuntime_BasicNoPositionals.ErasedTree.Leaf 3
7815+
ArgParserRuntime_BasicNoPositionals.ErasedTree.Leaf 4
7816+
ArgParserRuntime_BasicNoPositionals.ErasedTree.Leaf 5
7817+
ArgParserRuntime_BasicNoPositionals.ErasedTree.Leaf 6
7818+
]
7819+
))
7820+
Positionals = List.empty
7821+
}
7822+
7823+
let parser_storeOccurrence
7824+
(occurrence : ArgParserRuntime_BasicNoPositionals.ErasedOccurrence)
7825+
: string option
7826+
=
7827+
match occurrence.LeafId with
7828+
| 0 ->
7829+
match arg_0 with
7830+
| Some _ -> None
7831+
| None ->
7832+
match occurrence.Value with
7833+
| Some value ->
7834+
try
7835+
arg_0 <- Some (value |> (fun x -> System.Int32.Parse x))
7836+
None
7837+
with _ as exc ->
7838+
(sprintf "%s (at arg %s)" exc.Message occurrence.Source) |> Some
7839+
| None ->
7840+
failwith
7841+
"WoofWare.Myriad internal error in generated parser: arity-one occurrence with no value"
7842+
| 1 ->
7843+
match arg_1 with
7844+
| Some _ -> None
7845+
| None ->
7846+
match occurrence.Value with
7847+
| Some value ->
7848+
try
7849+
arg_1 <- Some (value |> (fun x -> x))
7850+
None
7851+
with _ as exc ->
7852+
(sprintf "%s (at arg %s)" exc.Message occurrence.Source) |> Some
7853+
| None ->
7854+
failwith
7855+
"WoofWare.Myriad internal error in generated parser: arity-one occurrence with no value"
7856+
| 2 ->
7857+
match arg_2 with
7858+
| Some _ -> None
7859+
| None ->
7860+
match occurrence.Value with
7861+
| Some value ->
7862+
try
7863+
arg_2 <- Some (value |> (fun x -> System.Int32.Parse x))
7864+
None
7865+
with _ as exc ->
7866+
(sprintf "%s (at arg %s)" exc.Message occurrence.Source) |> Some
7867+
| None ->
7868+
failwith
7869+
"WoofWare.Myriad internal error in generated parser: arity-one occurrence with no value"
7870+
| 3 ->
7871+
match arg_3 with
7872+
| Some _ -> None
7873+
| None ->
7874+
match occurrence.Value with
7875+
| Some value ->
7876+
try
7877+
arg_3 <- Some (value |> (fun x -> System.Int32.Parse x))
7878+
None
7879+
with _ as exc ->
7880+
(sprintf "%s (at arg %s)" exc.Message occurrence.Source) |> Some
7881+
| None ->
7882+
failwith
7883+
"WoofWare.Myriad internal error in generated parser: arity-one occurrence with no value"
7884+
| 4 ->
7885+
match arg_4 with
7886+
| Some _ -> None
7887+
| None ->
7888+
match occurrence.Value with
7889+
| Some value ->
7890+
try
7891+
arg_4 <- Some (value |> (fun x -> System.Int32.Parse x))
7892+
None
7893+
with _ as exc ->
7894+
(sprintf "%s (at arg %s)" exc.Message occurrence.Source) |> Some
7895+
| None ->
7896+
failwith
7897+
"WoofWare.Myriad internal error in generated parser: arity-one occurrence with no value"
7898+
| 5 ->
7899+
match arg_5 with
7900+
| Some _ -> None
7901+
| None ->
7902+
match occurrence.Value with
7903+
| Some value ->
7904+
try
7905+
arg_5 <- Some (value |> (fun x -> System.Int32.Parse x))
7906+
None
7907+
with _ as exc ->
7908+
(sprintf "%s (at arg %s)" exc.Message occurrence.Source) |> Some
7909+
| None ->
7910+
failwith
7911+
"WoofWare.Myriad internal error in generated parser: arity-one occurrence with no value"
7912+
| 6 ->
7913+
match arg_6 with
7914+
| Some _ -> None
7915+
| None ->
7916+
match occurrence.Value with
7917+
| Some value ->
7918+
try
7919+
arg_6 <- Some (value |> (fun x -> System.Int32.Parse x))
7920+
None
7921+
with _ as exc ->
7922+
(sprintf "%s (at arg %s)" exc.Message occurrence.Source) |> Some
7923+
| None ->
7924+
failwith
7925+
"WoofWare.Myriad internal error in generated parser: arity-one occurrence with no value"
7926+
| _ -> failwith "WoofWare.Myriad internal error in generated parser: unknown argument id"
7927+
7928+
let parser_storePositional (positionalId : int) (value : string) (afterSeparator : bool) : string option =
7929+
failwith "WoofWare.Myriad internal error in generated parser: no positional sink exists"
7930+
7931+
let parser_renderStored (leafId : int) : string =
7932+
match leafId with
7933+
| 0 ->
7934+
match arg_0 with
7935+
| Some x -> x.ToString ()
7936+
| None -> "<no value>"
7937+
| 1 ->
7938+
match arg_1 with
7939+
| Some x -> x.ToString ()
7940+
| None -> "<no value>"
7941+
| 2 ->
7942+
match arg_2 with
7943+
| Some x -> x.ToString ()
7944+
| None -> "<no value>"
7945+
| 3 ->
7946+
match arg_3 with
7947+
| Some x -> x.ToString ()
7948+
| None -> "<no value>"
7949+
| 4 ->
7950+
match arg_4 with
7951+
| Some x -> x.ToString ()
7952+
| None -> "<no value>"
7953+
| 5 ->
7954+
match arg_5 with
7955+
| Some x -> x.ToString ()
7956+
| None -> "<no value>"
7957+
| 6 ->
7958+
match arg_6 with
7959+
| Some x -> x.ToString ()
7960+
| None -> "<no value>"
7961+
| _ -> "<no value>"
7962+
7963+
let parser_applyDefault (leafId : int) : string option =
7964+
match leafId with
7965+
| _ -> failwith "WoofWare.Myriad internal error in generated parser: unknown defaulted argument id"
7966+
7967+
let parser_callbacks : ArgParserRuntime_BasicNoPositionals.TypedCallbacks =
7968+
{
7969+
StoreOccurrence = parser_storeOccurrence
7970+
StorePositional = parser_storePositional
7971+
HelpText = helpText
7972+
RenderStored = parser_renderStored
7973+
ApplyDefault = parser_applyDefault
7974+
}
7975+
7976+
match
7977+
ArgParserRuntime_BasicNoPositionals.runParse
7978+
(ArgParserRuntime_BasicNoPositionals.WellFormedSchema.checkOrFail parser_schema)
7979+
parser_callbacks
7980+
args
7981+
with
7982+
| ArgParserRuntime_BasicNoPositionals.ParseOutcome.Success parser_selection ->
7983+
{
7984+
``_`` =
7985+
(match arg_2 with
7986+
| Some x -> x
7987+
| None ->
7988+
failwith
7989+
"WoofWare.Myriad internal error in generated parser: required argument missing after successful parse")
7990+
``__LINE__`` =
7991+
(match arg_5 with
7992+
| Some x -> x
7993+
| None ->
7994+
failwith
7995+
"WoofWare.Myriad internal error in generated parser: required argument missing after successful parse")
7996+
``back\tab`` =
7997+
{
7998+
Thing1 =
7999+
(match arg_0 with
8000+
| Some x -> x
8001+
| None ->
8002+
failwith
8003+
"WoofWare.Myriad internal error in generated parser: required argument missing after successful parse")
8004+
Thing2 =
8005+
(match arg_1 with
8006+
| Some x -> x
8007+
| None ->
8008+
failwith
8009+
"WoofWare.Myriad internal error in generated parser: required argument missing after successful parse")
8010+
}
8011+
``break`` =
8012+
(match arg_6 with
8013+
| Some x -> x
8014+
| None ->
8015+
failwith
8016+
"WoofWare.Myriad internal error in generated parser: required argument missing after successful parse")
8017+
``mod`` =
8018+
(match arg_4 with
8019+
| Some x -> x
8020+
| None ->
8021+
failwith
8022+
"WoofWare.Myriad internal error in generated parser: required argument missing after successful parse")
8023+
``|A|_|`` =
8024+
(match arg_3 with
8025+
| Some x -> x
8026+
| None ->
8027+
failwith
8028+
"WoofWare.Myriad internal error in generated parser: required argument missing after successful parse")
8029+
}
8030+
| ArgParserRuntime_BasicNoPositionals.ParseOutcome.HelpRequested ->
8031+
helpText () |> failwithf "Help text requested.\n%s"
8032+
| ArgParserRuntime_BasicNoPositionals.ParseOutcome.Fatal message -> failwith message
8033+
| ArgParserRuntime_BasicNoPositionals.ParseOutcome.Errors errors ->
8034+
errors |> String.concat "\n" |> failwithf "Errors during parse!\n%s"
8035+
8036+
static member parse (args : string list) : AwkwardFieldName =
8037+
AwkwardFieldName.parse' (System.Environment.GetEnvironmentVariable >> Option.ofObj) args

0 commit comments

Comments
 (0)