Describe the bug
Screenplay is line-based, and every block decides what a line is purely from its first word. A property line
is <name> <Type> — matched by:
// Source/DotNET/Screenplay/Parsing/PropertyLineParser.cs:54
[GeneratedRegex(@"^([a-z_]\w*)\s+([\w.]+(?:\[\])?\??)$", RegexOptions.None, 1000)]
private static partial Regex PropertyRegex();
Several enclosing blocks check a line's first word before falling back to this property regex, in order to
recognize their own directives (description, authorize, tag, validate, key, parent, ...). Because
that check happens first and there is no escape syntax, a property whose name collides with a reserved word is
never seen as a property — it is parsed as the directive instead, whether or not that's what the author meant.
This isn't theoretical: generating a .play document from a real Cratis Arc application produced 4 compile
errors purely from command properties named Description — an extremely common domain field name.
Reserved first words
| Block |
Parser |
Reserved words |
command body |
CommandParser.cs (switch at line 39) |
authorize, concurrency, description, handler, produces, validate |
event body |
EventParser.cs (line 33) |
tag |
produces mapping block |
CommandParser.ParseMappingsAndTags (line 232) |
tag |
projection from block |
ProjectionParser.ParseFrom (lines 174, 183) |
key, parent |
concept body |
ScreenplayParser.ParseConcept (line 149) |
validate |
Note events is not reserved in a command body — it only exists inside a concurrency block.
Severity varies — two cases are worse than a compile error, because they corrupt silently
description, concurrency, validate, handler as a property name in a command body → hard parse errors.
authorize / produces as a property name in a command body → the property is swallowed and a misleading
diagnostic appears, because ScreenplayValidator then checks the (bogus) reference:
authorize SomeType → parsed as an authorize declaration referencing policy SomeType → Unknown policy 'SomeType' - declare it with 'policy SomeType'.
produces SomeType → parsed as a production of event SomeType → Unknown event 'SomeType' - declare it with 'event SomeType'.
- i.e. the document ends up asserting a policy/event reference the application never made.
tag <Type> as a property name in an event body → silently becomes a static tag on the event. No diagnostic
at all, and the property is dropped entirely (TagParser.Parse accepts any PascalCase identifier as a valid
tag value).
validate as an enum value name in a concept body → silently parsed as an empty validate block (because
ValidateParser.Parse matches on line.Content == "validate" with no further check), swallowing the enum
value with no diagnostic at all.
tag = x in a produces mapping block, and key = x / parent = x in a projection from block → hard
errors (recognized as the directive before the general <property> = <source> mapping regex is tried).
Steps to reproduce
1. A description property on a command → hard parse error:
module Invoicing
feature InvoiceManagement
slice StateChange RegisterInvoice
command RegisterInvoice
invoiceId InvoiceId
description String
produces InvoiceRegistered
invoiceId = invoiceId
description = description
event InvoiceRegistered
invoiceId InvoiceId
description String
Compiling this reports:
Invalid description 'description String' - expected 'description "<text>"'
description is a completely ordinary field name for a command payload — there is no way to declare it as a
property.
2. A tag property on an event → silently absorbed, no diagnostic:
module Invoicing
feature InvoiceManagement
slice StateChange RegisterInvoice
command RegisterInvoice
invoiceId InvoiceId
tag TagType
produces InvoiceRegistered
invoiceId = invoiceId
tag = tag
event InvoiceRegistered
invoiceId InvoiceId
tag TagType
This compiles with zero diagnostics, but tag TagType under event InvoiceRegistered is never added as a
property — EventParser recognizes tag as the tag directive first, and TagParser happily accepts
TagType as a literal tag value (it matches the identifier regex ^[A-Za-z_]\w*$). The event silently gets an
extra static tag named "TagType" and loses the tag property the author intended. Nothing in the compiler
output tells you this happened.
Expected behavior
A property line should be recognized as a property whenever it has property shape, regardless of which word the
enclosing block also uses as a directive keyword — and at minimum, the two silent cases above should raise a
diagnostic rather than quietly changing the meaning of the document.
Actual behavior
The first word alone decides directive-vs-property, with no escape syntax, and two of the collisions
(tag in an event body, validate as an enum value) don't even raise a diagnostic — they just silently reinterpret
the input.
Suggested fix
Framed as a suggestion, not a demand — the maintainers know this grammar best:
- Disambiguate by shape rather than by first word alone.
description "some text" (or a fenced block) is
unambiguously the directive; description SomeType matches the property-line regex and is unambiguously a
property. The same reasoning applies to tag.
- Where shape genuinely cannot disambiguate (e.g.
validate as a bare enum value vs. the validate block
header), an explicit escape for property/value names would resolve it.
- At minimum, the two fully-silent cases (
tag swallowing an event property, validate swallowing an enum
value) should produce a diagnostic instead of quietly changing meaning — even before a full disambiguation fix
lands.
Environment
- Screenplay repo:
main branch, Source/DotNET/Screenplay/Parsing/
- Found while building
Cratis.Arc.Screenplay (Cratis/Arc PR #2384),
which generates .play documents from real Cratis Arc application source. It currently works around this by
omitting the colliding property and reporting its own SP0032 diagnostic instead — which is lossy (the
property is dropped from the generated document, not just renamed or escaped). The real fix belongs in the
grammar/parser here.
Additional context
Not a duplicate of #25 — that issue is about ScreenplayPrinter emitting non-round-trippable output (culture-
dependent numeric formatting, unescaped string literals). This issue is about the parser's reserved-word
handling and applies regardless of how the input was produced (hand-written or printed).
Describe the bug
Screenplay is line-based, and every block decides what a line is purely from its first word. A property line
is
<name> <Type>— matched by:Several enclosing blocks check a line's first word before falling back to this property regex, in order to
recognize their own directives (
description,authorize,tag,validate,key,parent, ...). Becausethat check happens first and there is no escape syntax, a property whose name collides with a reserved word is
never seen as a property — it is parsed as the directive instead, whether or not that's what the author meant.
This isn't theoretical: generating a
.playdocument from a real Cratis Arc application produced 4 compileerrors purely from command properties named
Description— an extremely common domain field name.Reserved first words
commandbodyCommandParser.cs(switch at line 39)authorize,concurrency,description,handler,produces,validateeventbodyEventParser.cs(line 33)tagproducesmapping blockCommandParser.ParseMappingsAndTags(line 232)tagfromblockProjectionParser.ParseFrom(lines 174, 183)key,parentconceptbodyScreenplayParser.ParseConcept(line 149)validateNote
eventsis not reserved in a command body — it only exists inside aconcurrencyblock.Severity varies — two cases are worse than a compile error, because they corrupt silently
description,concurrency,validate,handleras a property name in a command body → hard parse errors.authorize/producesas a property name in a command body → the property is swallowed and a misleadingdiagnostic appears, because
ScreenplayValidatorthen checks the (bogus) reference:authorize SomeType→ parsed as an authorize declaration referencing policySomeType→Unknown policy 'SomeType' - declare it with 'policy SomeType'.produces SomeType→ parsed as a production of eventSomeType→Unknown event 'SomeType' - declare it with 'event SomeType'.tag <Type>as a property name in an event body → silently becomes a static tag on the event. No diagnosticat all, and the property is dropped entirely (
TagParser.Parseaccepts any PascalCase identifier as a validtag value).
validateas an enum value name in a concept body → silently parsed as an emptyvalidateblock (becauseValidateParser.Parsematches online.Content == "validate"with no further check), swallowing the enumvalue with no diagnostic at all.
tag = xin aproducesmapping block, andkey = x/parent = xin a projectionfromblock → harderrors (recognized as the directive before the general
<property> = <source>mapping regex is tried).Steps to reproduce
1. A
descriptionproperty on a command → hard parse error:Compiling this reports:
descriptionis a completely ordinary field name for a command payload — there is no way to declare it as aproperty.
2. A
tagproperty on an event → silently absorbed, no diagnostic:This compiles with zero diagnostics, but
tag TagTypeunderevent InvoiceRegisteredis never added as aproperty —
EventParserrecognizestagas the tag directive first, andTagParserhappily acceptsTagTypeas a literal tag value (it matches the identifier regex^[A-Za-z_]\w*$). The event silently gets anextra static tag named
"TagType"and loses thetagproperty the author intended. Nothing in the compileroutput tells you this happened.
Expected behavior
A property line should be recognized as a property whenever it has property shape, regardless of which word the
enclosing block also uses as a directive keyword — and at minimum, the two silent cases above should raise a
diagnostic rather than quietly changing the meaning of the document.
Actual behavior
The first word alone decides directive-vs-property, with no escape syntax, and two of the collisions
(
tagin an event body,validateas an enum value) don't even raise a diagnostic — they just silently reinterpretthe input.
Suggested fix
Framed as a suggestion, not a demand — the maintainers know this grammar best:
description "some text"(or a fenced block) isunambiguously the directive;
description SomeTypematches the property-line regex and is unambiguously aproperty. The same reasoning applies to
tag.validateas a bare enum value vs. thevalidateblockheader), an explicit escape for property/value names would resolve it.
tagswallowing an event property,validateswallowing an enumvalue) should produce a diagnostic instead of quietly changing meaning — even before a full disambiguation fix
lands.
Environment
mainbranch,Source/DotNET/Screenplay/Parsing/Cratis.Arc.Screenplay(Cratis/Arc PR #2384),which generates
.playdocuments from real Cratis Arc application source. It currently works around this byomitting the colliding property and reporting its own
SP0032diagnostic instead — which is lossy (theproperty is dropped from the generated document, not just renamed or escaped). The real fix belongs in the
grammar/parser here.
Additional context
Not a duplicate of #25 — that issue is about
ScreenplayPrinteremitting non-round-trippable output (culture-dependent numeric formatting, unescaped string literals). This issue is about the parser's reserved-word
handling and applies regardless of how the input was produced (hand-written or printed).