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
12 changes: 9 additions & 3 deletions docs/design/api-mark-cpp.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,17 @@ through the ApiMarkCore interfaces. The system contains the following units:
- **CppAstModel** — immutable AST record types exchanged between parser and
emitters.
- **ClangAstParser** — invokes clang, walks the JSON AST, and returns
`CppCompilationResult`.
`CppCompilationResult`. Also extracts a literal display value for `constexpr`
fields whose initializer is a single simple-literal expression (integer,
floating-point, character, boolean, or string), leaving the value unset for
computed expressions so compound initializers are never folded.
- **CppEmitter** — format dispatcher and shared-helper hub.
- **CppEmitterGradualDisclosure** — writes namespace, type, member, operator,
enum, alias, nested-type, and class-scoped alias pages.
- **CppEmitterSingleFile** — writes all documentation into one `api.md` file.
enum, alias, nested-type, and class-scoped alias pages. Appends `= <value>`
to a field's signature line when `CppField.Value` is populated.
- **CppEmitterSingleFile** — writes all documentation into one `api.md` file,
appending `= <value>` to a field's signature line when `CppField.Value` is
populated, mirroring the gradual-disclosure rendering.
- **CppTypeLinkResolver** — resolves intra-library type links and tracks external
types.
- **DocumentationCoverageChecker** — scans parsed namespace declarations for
Expand Down
39 changes: 39 additions & 0 deletions docs/design/api-mark-cpp/clang-ast-parser.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,45 @@ while walking the JSON AST.
- **ParseMethod** — builds `CppFunction` records for constructors and methods,
including `IsDeleted` and default parameters.
- **ParseField** — builds `CppField` records for instance and static members.
`VarDecl` nodes (static class members) and `FieldDecl` nodes are both inspected
for a `constexpr` flag (`node.constexpr`); when the field is `constexpr`, the
sole non-comment/non-attribute child of `inner` (the initializer expression, if
any) is passed to `ExtractConstexprFieldValue` and the resulting display string
(or `null`) is stored as `CppField.Value`. Non-`constexpr` initializers are never
inspected, since they are not guaranteed to be compile-time constant.
- **ExtractConstexprFieldValue** — recursively unwraps pure pass-through wrapper
nodes clang inserts around literals (`ImplicitCastExpr`, `ConstantExpr`,
`MaterializeTemporaryExpr`, `ExprWithCleanups`), then renders a display string
for the following literal kinds: `IntegerLiteral`/`FloatingLiteral`/`StringLiteral`
(clang's own pre-formatted `value` JSON string), `CXXBoolLiteralExpr` (via
`ExtractBoolLiteralDefaultValue`), `CharacterLiteral` (via
`ExtractCharacterLiteralValue`), and `UnaryOperator` (via
`ExtractConstexprUnaryLiteralValue`). Any other node kind — e.g. `BinaryOperator`
(a computed expression such as `2 * 21`) or `DeclRefExpr` (a reference to another
named constant) — yields `null`, so the rendering feature never displays a
folded or reconstructed value for anything other than the literal the user wrote
directly.
- **ExtractConstexprUnaryLiteralValue** — reconstructs a leading `+`/`-` operator
applied directly to a literal operand (e.g. `-5`) as the display string
`"{op}{operandValue}"`, but only when the operand — after resolving its kind
through `UnwrapPassThroughKind` — is not itself a `UnaryOperator`; this rejects
a nested unary chain such as `- -5` (which would otherwise reconstruct as the
misleading `--5`, readable as a C++ decrement expression rather than a
double-negation) before delegating the actual value extraction to a recursive
call to `ExtractConstexprFieldValue`. Any other unary opcode, or one wrapping a
non-literal/computed operand (e.g. `-(2 * 21)`), yields `null` so compound
expressions are never rendered as if they were literals.
- **UnwrapPassThroughKind** — resolves the `kind` of a node after unwrapping the
same pure pass-through wrapper kinds that `ExtractConstexprFieldValue` unwraps,
without extracting a value; used solely by `ExtractConstexprUnaryLiteralValue`
to detect a nested `UnaryOperator` operand.
- **ExtractCharacterLiteralValue** — converts the clang-reported numeric code
point of a `CharacterLiteral` node into a single-quoted C++-style display
string: `0` renders as `'\0'`, and `\n`/`\r`/`\t`/`\\`/`'` render as their named
escape forms; printable ASCII (32–126) renders as the bare character (e.g.
`'A'`); anything else renders as the bare decimal code point as text (to avoid
misrepresenting non-ASCII code points). Returns `null` when the `value` property
is absent or not a JSON number.
- **ParseParameter / ExtractDefaultValue** — extract parameter shapes and simple
default-argument display strings.
- **ParseAccessSpec** — converts clang access specifiers to `CppAccessibility`.
Expand Down
10 changes: 9 additions & 1 deletion docs/design/api-mark-cpp/cpp-ast-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Cpp emitters.
| `CppTemplateParam` | Template parameter name. |
| `CppEnumValue` | Enumerator name and optional doc comment. |
| `CppParameter` | Function parameter: name, type name, optional default value. |
| `CppField` | Class field: name, type, accessibility, static/deprecated flags, location, doc. |
| `CppField` | Class field: name, type, accessibility, static/deprecated flags, location, doc, optional compile-time literal value. |
| `CppFunction` | Function or method: name, return type, parameters, accessibility, static/virtual/constructor/variadic/deprecated/deleted flags, location, doc. |
| `CppClass` | Class or struct: name, base types, template params, members, fields, nested classes, type aliases, deprecated/final flags, location, doc. |
| `CppEnum` | Scoped or unscoped enum: name, values, deprecated flag, location, doc. |
Expand All @@ -35,6 +35,14 @@ Cpp emitters.
`= delete`; emitters use the flag to append the `= delete` suffix so deleted
operations remain visible in generated documentation.

`CppField.Value` is a nullable `string?` populated only when `ClangAstParser`
determines the field is `constexpr` and literal-initialized with a single simple
literal (integer, floating-point, character, boolean, or string), including a
leading unary `+`/`-` directly over a literal (e.g. `-5`); it is `null` for
computed expressions (e.g. `2 * 21`), references to other named constants,
non-`constexpr` fields, and fields with no initializer. Both C++ emitters append
`= {Value}` to the field signature line when it is non-null.

### Key Methods

N/A - the unit contains immutable record and enum definitions only.
Expand Down
4 changes: 3 additions & 1 deletion docs/design/api-mark-cpp/cpp-emitter-gradual-disclosure.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,9 @@ in the constructor; the caller `CppEmitter.Emit` always supplies non-null values
string className, CppField field)` — write the H1 heading and body for a class field page.
- *Parameters*: writer, namespace display name, class name, field.
- *Algorithm*: emits a qualified-name comment + field-type signature, summary, details, note,
and example.
and example. The field-type signature line is `{Type} {Name} = {Value};` when
`CppField.Value` is non-null (i.e. the field is a `constexpr`/literal-initialized
constant), otherwise `{Type} {Name};`.

- **WriteFreeFunctionPage / WriteFreeFunctionContent**: `private void WriteFreeFunctionPage(
IMarkdownWriterFactory factory, string nsKey, string nsDisplayName, CppFunction fn,
Expand Down
6 changes: 5 additions & 1 deletion docs/design/api-mark-cpp/cpp-emitter-single-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,11 @@ for a namespace-level C++ type alias.
**WriteSingleFileMemberSection** (private static): Emits an H{depth+3} section
for a single class member (constructor, method, or field), including a fenced-code
signature block, summary, parameters table (when applicable), Returns line (for
non-void non-constructor methods), and example block (when present).
non-void non-constructor methods), and example block (when present). For a field
member, the signature line is `{Type} {Name} = {Value};` when `CppField.Value` is
non-null (i.e. the field is a `constexpr`/literal-initialized constant), otherwise
`{Type} {Name};` — this is implemented by the private `WriteSingleFileFieldMemberBody`
helper and mirrors `WriteFieldContent`'s gradual-disclosure rendering exactly.

**WriteSingleFileParametersTable** (private static): Writes a Parameters table
with columns (Parameter, Type, Description) for a function when it has at least
Expand Down
5 changes: 4 additions & 1 deletion docs/design/api-mark-dot-net.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ defined by the Core interfaces. The system contains ten units:
(namespaces, types, XML docs, resolver, options) produced by DotNetGenerator.Parse.
- **DotNetEmitter** — IApiEmitter dispatcher; reads EmitConfig.Format and forwards
the call to DotNetEmitterGradualDisclosure or DotNetEmitterSingleFile. Also provides
shared static helper methods used by both sub-emitters.
shared static helper methods used by both sub-emitters, including
`BuildFieldSignature`, which appends the compiler-folded constant value (read from
`FieldDefinition.Constant` when `HasConstant` is set) as C#-like literal source,
covering both plain `const` fields and enum member values.
- **DotNetEmitterGradualDisclosure** — writes the multi-file gradual-disclosure tree
(one file per namespace and type, and one file per member except where the model
combines case-insensitive filename collisions onto a shared page). Types declaring
Expand Down
25 changes: 25 additions & 0 deletions docs/design/api-mark-dot-net/dot-net-emitter.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,31 @@ These helpers are grouped by concern:
`public static implicit operator TargetType(SourceType)` or
`public static explicit operator TargetType(SourceType)`, with the return type
appearing after the `operator` keyword rather than before the method name.
`BuildFieldSignature` appends `" = " + FormatConstantValue(field.Constant)` to the
signature when `field.HasConstant` is `true`; this also covers enum member fields,
because Mono.Cecil stores enum member constants using the enum's underlying
primitive type, so the same formatter renders both plain `const` fields and enum
members correctly.
- *Constant-value formatting* — `FormatConstantValue`, `EscapeStringLiteral`,
`EscapeCharLiteral`, `EscapeControlCharacter`: `FormatConstantValue`
pattern-matches the boxed constant by runtime type — `null` renders as
`"null"`; `string` renders double-quoted and escaped char-by-char via
`EscapeStringLiteral` (handling `\\` and `"` itself, delegating every other
character to `EscapeControlCharacter`); `char` renders single-quoted and
escaped via `EscapeCharLiteral` (handling `\\` and `'` itself, delegating
every other character to `EscapeControlCharacter`); `bool` renders as
lowercase `true`/`false`; and every other numeric primitive renders via
`Convert.ToString` with `CultureInfo.InvariantCulture` and no numeric type
suffixes (no `L`/`f`/`m`), to keep the signature readable as plain C#-like
literal text. `EscapeControlCharacter` is the single shared escaping
table used by both the string and char paths: it renders the named short
escapes (`\0`, `\a`, `\b`, `\f`, `\n`, `\r`, `\t`, `\v`) for their
corresponding control characters, falls back to a `\uXXXX` escape for any
other character classified as a control character by `char.IsControl`
(covering the full Unicode control range — ASCII C0 controls, `DEL`
(`0x7f`), and the C1 controls `U+0080`-`U+009F`) so that no raw control
byte is ever embedded in generated documentation text, and otherwise
returns the character unchanged.
- *Accessibility helpers* — `GetAccessibilityKeyword(MethodDefinition)`,
`GetAccessibilityKeyword(FieldDefinition)`, `GetAccessibilityKeyword(EventDefinition)`,
`GetOperatorCSharpName`, `GetOperatorSymbol`: map Mono.Cecil access flags to C#
Expand Down
26 changes: 25 additions & 1 deletion docs/reqstream/api-mark-cpp/clang-ast-parser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ sections:
resolved C++ AST. Delegating parse to ClangAstParser keeps the I/O and
JSON-deserialization logic isolated from the higher-level generation
logic, making each unit independently testable.
children: [ApiMarkCpp-ClangAstParser-ParseHeaders, ApiMarkCpp-ClangAstParser-NormalizeIncludeRootsRegardlessOfCaller]
children: [ApiMarkCpp-ClangAstParser-ParseHeaders, ApiMarkCpp-ClangAstParser-NormalizeIncludeRootsRegardlessOfCaller, ApiMarkCpp-ClangAstParser-ParseConstexprLiteralFieldValue]
tests: [ClangAstParser_Parse_FixtureHeaders_ReturnsNonEmptyNamespaces]
- id: ApiMarkCpp-ClangAstParser-ParseHeaders
title: >-
Expand Down Expand Up @@ -50,3 +50,27 @@ sections:
locate every header reachable only through the -I search path.
tests:
- ClangAstParser_Parse_CalledDirectlyWithDifferentlyCasedIncludeRoot_ResolvesAngleBracketInclude
- id: ApiMarkCpp-ClangAstParser-ParseConstexprLiteralFieldValue
title: >-
ClangAstParser shall extract a literal display value for constexpr fields
whose initializer is a single simple-literal expression, or a leading unary
+/- directly over such a literal (e.g. -5), and shall leave the value unset
for computed expressions.
justification: |
Readers and AI agents need to see the actual value of a compile-time constant
field to use the API correctly without opening the header. Folding or
reconstructing computed expressions (e.g. 2 * 21) would misrepresent the
source, so only direct literal initializers (integer, floating-point,
character, boolean, or string) are extracted - including when prefixed by a
single leading unary + or - directly over the literal, since that is still the
literal the user wrote directly, not a computed value; anything else is left
unset.
tests:
- ClangAstParser_Parse_FixtureHeaders_ConstexprStringField_HasQuotedValue
- ClangAstParser_Parse_FixtureHeaders_ConstexprIntField_HasNumericValue
- ClangAstParser_Parse_FixtureHeaders_ConstexprComputedField_HasNullValue
- ClangAstParser_Parse_FixtureHeaders_ConstexprBoolField_HasBooleanValue
- ClangAstParser_Parse_FixtureHeaders_ConstexprCharField_EscapesNulCharacter
- ClangAstParser_Parse_FixtureHeaders_ConstexprNegativeIntField_HasNumericValue
- ClangAstParser_Parse_FixtureHeaders_ConstexprFloatingField_HasDecimalValue
- ClangAstParser_Parse_FixtureHeaders_ConstexprDoubleNegatedField_HasNullValue
12 changes: 12 additions & 0 deletions docs/reqstream/api-mark-cpp/cpp-emitter-gradual-disclosure.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,21 @@ sections:
justification: |
Dedicated member pages make navigation deterministic while still handling
case-collision edge cases safely.
children:
- ApiMarkCpp-CppEmitterGradualDisclosure-RenderFieldConstantValue
tests:
- CppEmitterGradualDisclosure_Emit_MethodMember_CreatesMemberDetailPage
- CppEmitterGradualDisclosure_Emit_CaseInsensitiveCollision_CreatesCombinedPage
- id: ApiMarkCpp-CppEmitterGradualDisclosure-RenderFieldConstantValue
title: >-
CppEmitterGradualDisclosure shall append "= value" to a field's signature
line when CppField.Value is populated.
justification: |
Readers need to see a field's compile-time literal value directly in its
signature block so they can use the constant without opening the header.
tests:
- CppEmitterGradualDisclosure_Emit_FieldWithLiteralValue_SignatureContainsValue
- CppEmitterGradualDisclosure_Emit_FieldWithoutValue_SignatureContainsNoEquals
- id: ApiMarkCpp-CppEmitterGradualDisclosure-EmitFreeFunctionPage
title: CppEmitterGradualDisclosure shall produce a detail page for each
regular free function.
Expand Down
14 changes: 14 additions & 0 deletions docs/reqstream/api-mark-cpp/cpp-emitter-single-file.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,21 @@ sections:
class section using the configured heading-depth offset.
justification: |
Member sections preserve drill-down detail while keeping the document linear.
children:
- ApiMarkCpp-CppEmitterSingleFile-RenderFieldConstantValue
tests: [CppEmitterSingleFile_Emit_NonDefaultHeadingDepth_OffsetsHeadings]
- id: ApiMarkCpp-CppEmitterSingleFile-RenderFieldConstantValue
title: >-
CppEmitterSingleFile shall append "= value" to a field's signature line
when CppField.Value is populated, mirroring the gradual-disclosure
rendering.
justification: |
Readers need to see a field's compile-time literal value directly in its
signature block in the single-file layout, consistent with the
gradual-disclosure output.
tests:
- CppEmitterSingleFile_Emit_FieldWithLiteralValue_SignatureContainsValue
- CppEmitterSingleFile_Emit_FieldWithoutValue_SignatureContainsNoEquals
- id: ApiMarkCpp-CppEmitterSingleFile-EmitsTypeAliasSection
title: CppEmitterSingleFile shall emit a section for each documented namespace-level
type alias.
Expand Down
18 changes: 18 additions & 0 deletions docs/reqstream/api-mark-dot-net/dot-net-emitter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,21 @@ sections:
only be assigned during object initialization. Rendering them as set; would
mislead readers into believing the property is mutable after construction.
tests: [DotNetEmitter_BuildPropertyAccessors_InitOnlySetter_EmitsInit]
- id: ApiMarkDotNet-DotNetEmitter-RenderConstantFieldValue
title: DotNetEmitter shall append the compiler-folded constant value to a
field signature when the field has a compile-time constant (HasConstant),
including enum member fields.
justification: |
Readers and AI agents need to see the actual value of a const field or enum
member to use the API correctly without opening the implementation. Mono.Cecil
stores enum member constants using the enum's underlying primitive type, so a
single formatter renders both plain const fields and enum members correctly.
tests:
- DotNetEmitter_BuildFieldSignature_ConstStringField_AppendsQuotedValue
- DotNetEmitter_BuildFieldSignature_ConstIntField_AppendsNumericValue
- DotNetEmitter_BuildFieldSignature_ConstBoolField_AppendsLowercaseBooleanValue
- DotNetEmitter_BuildFieldSignature_EnumMemberWithExplicitValue_AppendsUnderlyingNumericValue
Comment thread
Malcolmnixon marked this conversation as resolved.
- DotNetEmitter_BuildFieldSignature_ConstCharField_EscapesNulCharacter
- DotNetEmitter_FormatConstantValue_StringWithControlCharacters_EscapesAllOfThem
- DotNetEmitter_FormatConstantValue_UnnamedControlCharacter_FallsBackToUnicodeEscape
- DotNetEmitter_FormatConstantValue_C1ControlCharacter_FallsBackToUnicodeEscape
6 changes: 5 additions & 1 deletion docs/user_guide/cpp.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,11 @@ constructs.
- **Enumerations** — scoped (`enum class`) and unscoped (`enum`) enumerations

Constructs are filtered by the `--api-headers` patterns — only declarations from
matched headers appear in the output.
matched headers appear in the output. `static constexpr` fields with a simple literal
initializer (e.g. `static constexpr int MaxCount = 42;`) render their value in the
signature, including when the literal has a leading unary `+`/`-` directly over it
(e.g. `static constexpr int Offset = -5;` still renders as a literal); computed
initializers are not folded and render without a value.

## Doc Comments

Expand Down
3 changes: 2 additions & 1 deletion docs/user_guide/dotnet.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ visible types is controlled by `--visibility`:

Methods, properties, fields, events, and constructors are documented on the type page.
Operator overloads are grouped together on a dedicated `operators.md` page within the
type folder.
type folder. `const` fields and enum members render their compile-time value in the
signature (e.g. `public const string DefaultName = "default"`, `Active = 0`).

### Nested Types

Expand Down
Loading
Loading