Skip to content

Commit b348866

Browse files
committed
Ad hoc unions: type of the "single backing field" can be configured
1 parent 68b8615 commit b348866

59 files changed

Lines changed: 7488 additions & 140 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/CLAUDE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ All types must be declared as `partial`. Source generators produce: factory meth
9898
- Factory methods: when any member triggers factory method generation (type parameter, interface, `System.Object`, or duplicate type), factory methods (`Create{MemberName}`) are generated for **all** members — not just the triggering ones. Constructors retain their configured access modifier; conversion operators are still generated for eligible members. Use `FactoryMethodGeneration` to override: `None` suppresses all factory methods (even for type parameters/duplicates), `Always` generates for all members unconditionally.
9999
- `allows ref struct` is **not supported** on ad-hoc union type parameters (TTRESG073). Ref structs cannot be boxed, which conflicts with equality, `Value` property, and Switch/Map delegate patterns.
100100
- Stateless types (`TXIsStateless = true`): store only discriminator, not instance. Prefer structs.
101-
- Backing fields: with 2+ distinct non-stateless reference types, reference types auto-share a single `object? _obj` field (value types keep typed fields). `UseSingleBackingField = true` forces all types (including value types, with boxing) into `_obj`.
101+
- Backing fields: with 2+ distinct non-stateless reference types, reference types auto-share a single `object? _obj` field (value types keep typed fields). `UseSingleBackingField = true` forces all types (including value types, with boxing) into `_obj`. `SingleBackingFieldType = typeof(TBase)` additionally types `_obj` and `Value` as `TBase` (implies `UseSingleBackingField = true`); stateless struct members use a `private static readonly` cached boxed default to avoid per-instance allocation. Supports `TypeParamRef1``TypeParamRef5` placeholders for generic unions.
102102

103103
**Regular Unions** (`[Union]`):
104104

@@ -116,6 +116,7 @@ Several attribute settings cascade to other settings. The AI assistant must acco
116116
- **`EmptyStringInFactoryMethodsYieldsNull = true`**: Forces `NullInFactoryMethodsYieldsNull = true`
117117
- **`TXIsStateless = true`** (Unions): Automatically sets `TXIsNullableReferenceType = true` for reference types
118118
- **`UseSingleBackingField = true`** (Ad-hoc Unions): Forces all member types into a single `object? _obj` backing field. Without this setting, the generator already auto-merges reference types into `_obj` when there are 2+ distinct non-stateless reference types; this setting additionally forces value types into `_obj` (causing boxing)
119+
- **`SingleBackingFieldType` set** (Ad-hoc Unions): Forces `UseSingleBackingField = true`. `typeof(object)` is normalized to "not set" but still triggers the cascade. Backing field and `Value` are typed as the specified base type instead of `object`. Stateless struct members are stored via a `private static readonly` cached boxed default; stateless reference types leave `_obj` null. For struct unions, `Value` throws `InvalidOperationException` on `default(TUnion)` (discriminator check on every access), matching `AsTx`/`Switch`/`Map`. Conflict with explicit `UseSingleBackingField = false` produces TTRESG075
119120
- **`ConstructorAccessModifier`** (Unions): Also controls accessibility of implicit conversion operators and factory method accessibility
120121
- **`FactoryMethodGeneration`** (Ad-hoc Unions): `None` suppresses all factory methods — including for type parameters and duplicates (user must provide custom creation methods). `Always` generates factory methods for all members even without triggers. `Default` auto-detects based on trigger conditions (type parameter, interface, `System.Object`, duplicate type) and generates for all members when any trigger is present
121122

.serena/project.yml

Lines changed: 81 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,20 @@
1-
# whether to use the project's gitignore file to ignore files
2-
# Added on 2025-04-07
1+
2+
# whether to use project's .gitignore files to ignore files
33
ignore_all_files_in_gitignore: true
4-
# list of additional paths to ignore
5-
# same syntax as gitignore, so you can use * and **
6-
# Was previously called `ignored_dirs`, please update your config if you are using that.
7-
# Added (renamed)on 2025-04-07
4+
5+
# list of additional paths to ignore in this project.
6+
# Same syntax as gitignore, so you can use * and **.
7+
# Note: global ignored_paths from serena_config.yml are also applied additively.
88
ignored_paths: []
99

1010
# whether the project is in read-only mode
1111
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
1212
# Added on 2025-04-18
1313
read_only: false
1414

15-
16-
# list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
17-
# Below is the complete list of tools for convenience.
18-
# To make sure you have the latest list of tools, and to view their descriptions,
19-
# execute `uv run scripts/print_tool_overview.py`.
20-
#
21-
# * `activate_project`: Activates a project by name.
22-
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
23-
# * `create_text_file`: Creates/overwrites a file in the project directory.
24-
# * `delete_lines`: Deletes a range of lines within a file.
25-
# * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
26-
# * `execute_shell_command`: Executes a shell command.
27-
# * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
28-
# * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
29-
# * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
30-
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
31-
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
32-
# * `initial_instructions`: Gets the initial instructions for the current project.
33-
# Should only be used in settings where the system prompt cannot be set,
34-
# e.g. in clients you have no control over, like Claude Desktop.
35-
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
36-
# * `insert_at_line`: Inserts content at a given line in a file.
37-
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
38-
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
39-
# * `list_memories`: Lists memories in Serena's project-specific memory store.
40-
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
41-
# * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
42-
# * `read_file`: Reads a file within the project directory.
43-
# * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
44-
# * `remove_project`: Removes a project from the Serena configuration.
45-
# * `replace_lines`: Replaces a range of lines within a file with new content.
46-
# * `replace_symbol_body`: Replaces the full definition of a symbol.
47-
# * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
48-
# * `search_for_pattern`: Performs a search for a pattern in the project.
49-
# * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
50-
# * `switch_modes`: Activates modes by providing a list of their names
51-
# * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
52-
# * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
53-
# * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
54-
# * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
15+
# list of tool names to exclude.
16+
# This extends the existing exclusions (e.g. from the global configuration)
17+
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
5518
excluded_tools: []
5619

5720
# initial prompt for the project. It will always be given to the LLM upon activating the project
@@ -68,18 +31,24 @@ project_name: "Thinktecture.Runtime.Extensions"
6831
# Set this to a list of mode names to always include the respective modes for this project.
6932
base_modes:
7033

71-
# list of mode names that are to be activated by default.
72-
# The full set of modes to be activated is base_modes + default_modes.
73-
# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply.
34+
# list of mode names that are to be activated by default, overriding the setting in the global configuration.
35+
# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
36+
# If the setting is undefined/empty, the default_modes from the global configuration (serena_config.yml) apply.
7437
# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
38+
# Therefore, you can set this to [] if you do not want the default modes defined in the global config to apply
39+
# for this project.
7540
# This setting can, in turn, be overridden by CLI parameters (--mode).
41+
# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
7642
default_modes:
7743

78-
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default)
44+
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default).
45+
# This extends the existing inclusions (e.g. from the global configuration).
46+
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
7947
included_optional_tools: []
8048

8149
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
8250
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
51+
# Find the list of tools here: https://oraios.github.io/serena/01-about/035_tools.html
8352
fixed_tools: []
8453

8554
# the encoding used by text files in the project
@@ -88,21 +57,26 @@ encoding: utf-8
8857

8958

9059
# list of languages for which language servers are started; choose from:
91-
# al bash clojure cpp csharp
92-
# csharp_omnisharp dart elixir elm erlang
93-
# fortran fsharp go groovy haskell
94-
# java julia kotlin lua markdown
95-
# matlab nix pascal perl php
96-
# powershell python python_jedi r rego
97-
# ruby ruby_solargraph rust scala swift
98-
# terraform toml typescript typescript_vts vue
99-
# yaml zig
60+
# al angular ansible bash clojure
61+
# cpp cpp_ccls crystal csharp csharp_omnisharp
62+
# dart elixir elm erlang fortran
63+
# fsharp go groovy haskell haxe
64+
# hlsl html java json julia
65+
# kotlin lean4 lua luau markdown
66+
# matlab msl nix ocaml pascal
67+
# perl php php_phpactor powershell python
68+
# python_jedi python_ty r rego ruby
69+
# ruby_solargraph rust scala scss solidity
70+
# swift systemverilog terraform toml typescript
71+
# typescript_vts vue yaml zig
10072
# (This list may be outdated. For the current list, see values of Language enum here:
10173
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
10274
# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
10375
# Note:
10476
# - For C, use cpp
10577
# - For JavaScript, use typescript
78+
# - For Angular projects, use angular (subsumes typescript+html; requires `npm install` in the project root)
79+
# - For SCSS / Sass / plain CSS, use scss (some-sass-language-server handles all three)
10680
# - For Free Pascal/Lazarus, use pascal
10781
# Special requirements:
10882
# Some languages require additional setup/installations.
@@ -148,3 +122,51 @@ ignored_memory_patterns: []
148122
# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available.
149123
# No documentation on options means no options are available.
150124
ls_specific_settings: {}
125+
126+
# list of mode names to be activated additionally for this project, e.g. ["query-projects"]
127+
# The full set of modes to be activated is base_modes (from global config) + default_modes + added_modes.
128+
# See https://oraios.github.io/serena/02-usage/050_configuration.html#modes
129+
added_modes:
130+
131+
# list of additional workspace folder paths for cross-package reference support (e.g. in monorepos).
132+
# Paths can be absolute or relative to the project root.
133+
# Each folder is registered as an LSP workspace folder, enabling language servers to discover
134+
# symbols and references across package boundaries.
135+
# Currently supported for: TypeScript.
136+
# Example:
137+
# additional_workspace_folders:
138+
# - ../sibling-package
139+
# - ../shared-lib
140+
additional_workspace_folders: []
141+
from the global configuration.
142+
symbol_info_budget:
143+
144+
# The language backend to use for this project.
145+
# If not set, the global setting from serena_config.yml is used.
146+
# Valid values: LSP, JetBrains
147+
# Note: the backend is fixed at startup. If a project with a different backend
148+
# is activated post-init, an error will be returned.
149+
language_backend:
150+
151+
# list of regex patterns which, when matched, mark a memory entry as read‑only.
152+
# Extends the list from the global configuration, merging the two lists.
153+
read_only_memory_patterns: []
154+
155+
# line ending convention to use when writing source files.
156+
# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default)
157+
# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings.
158+
line_ending:
159+
160+
# list of regex patterns for memories to completely ignore.
161+
# Matching memories will not appear in list_memories or activate_project output
162+
# and cannot be accessed via read_memory or write_memory.
163+
# To access ignored memory files, use the read_file tool on the raw file path.
164+
# Extends the list from the global configuration, merging the two lists.
165+
# Example: ["_archive/.*", "_episodes/.*"]
166+
ignored_memory_patterns: []
167+
168+
# advanced configuration option allowing to configure language server-specific options.
169+
# Maps the language key to the options.
170+
# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available.
171+
# No documentation on options means no options are available.
172+
ls_specific_settings: {}

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<Copyright>(c) $([System.DateTime]::Now.Year), Pawel Gerr. All rights reserved.</Copyright>
5-
<VersionPrefix>10.2.0</VersionPrefix>
5+
<VersionPrefix>10.3.0</VersionPrefix>
66
<Authors>Pawel Gerr</Authors>
77
<GenerateDocumentationFile>true</GenerateDocumentationFile>
88
<PackageProjectUrl>https://github.com/PawelGerr/Thinktecture.Runtime.Extensions</PackageProjectUrl>

docs

Submodule docs updated from d8dcbc5 to e745709

src/Thinktecture.Runtime.Extensions.Analyzers/CodeAnalysis/Diagnostics/ThinktectureRuntimeExtensionsAnalyzer.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ public sealed class ThinktectureRuntimeExtensionsAnalyzer : DiagnosticAnalyzer
6464
DiagnosticsDescriptors.ComparisonAndEqualityOperatorsMismatch,
6565
DiagnosticsDescriptors.UseSwitchMapWithStaticLambda,
6666
DiagnosticsDescriptors.TypeParamRefRequiresNotnullConstraint,
67+
DiagnosticsDescriptors.SingleBackingFieldTypeConflictsWithUseSingleBackingField,
6768
];
6869

6970
/// <inheritdoc />
@@ -165,7 +166,7 @@ private void AnalyzeNamedTypes(SymbolAnalysisContext context)
165166

166167
if (adHocUnionAttribute is not null)
167168
{
168-
ValidateAdHocUnion(context, type);
169+
ValidateAdHocUnion(context, type, adHocUnionAttribute);
169170
ValidateObjectFactories(context, type, objectFactoryAttributes, false);
170171

171172
needsObjectFactoryHandling = false;
@@ -544,7 +545,8 @@ private static void AnalyzeSwitchMapLambdas(
544545

545546
private static void ValidateAdHocUnion(
546547
SymbolAnalysisContext context,
547-
INamedTypeSymbol type)
548+
INamedTypeSymbol type,
549+
AttributeData adHocUnionAttribute)
548550
{
549551
if (type.IsRecord || type.TypeKind is not (TypeKind.Class or TypeKind.Struct))
550552
{
@@ -558,6 +560,22 @@ private static void ValidateAdHocUnion(
558560

559561
CheckConstructors(context, type, mustBePrivate: false, canHavePrimaryConstructor: false);
560562
TypeMustBePartial(context, type);
563+
564+
// TTRESG075: explicit 'UseSingleBackingField = false' conflicts with 'SingleBackingFieldType'.
565+
var hasSingleBackingFieldType = adHocUnionAttribute.FindSingleBackingFieldType() is not null;
566+
var useSingleBackingField = adHocUnionAttribute.FindUseSingleBackingField();
567+
568+
if (hasSingleBackingFieldType && useSingleBackingField == false)
569+
{
570+
var location = adHocUnionAttribute.ApplicationSyntaxReference?.GetSyntax(context.CancellationToken).GetLocation()
571+
?? type.GetTypeIdentifierLocation(context.CancellationToken);
572+
573+
ReportDiagnostic(
574+
context,
575+
DiagnosticsDescriptors.SingleBackingFieldTypeConflictsWithUseSingleBackingField,
576+
location,
577+
BuildTypeName(type));
578+
}
561579
}
562580

563581
private static void ValidateRegularUnion(

0 commit comments

Comments
 (0)