From 34c38059eaa25e4c6e7b84b76f7a699bb963e6d4 Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Sat, 25 Oct 2025 12:28:05 +0200 Subject: [PATCH 01/36] add requirements.md --- UnionsGenerator/requirements.md | 135 ++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 UnionsGenerator/requirements.md diff --git a/UnionsGenerator/requirements.md b/UnionsGenerator/requirements.md new file mode 100644 index 0000000..44bcbc0 --- /dev/null +++ b/UnionsGenerator/requirements.md @@ -0,0 +1,135 @@ +# New UnionsGenerator Design + +## Terminology + +The term `union` refers to a tagged union or sum type, or an instance thereof. +It is able to represent an instance of a type present in its set of representable types. + +The term `variant` refers to a type representable by a union. +Unless improperly initialized, a union is always representing an instance of one of its variants. + +The term `union value` or `value` refers to the variant instance being wrapped by a union. + +## Requirements + +| id | description | issues | met (×/✓) | +|----|-------------|--------|-----------| +| 1 | | | | +| 2 | | | | +| 3 | | | | +| 4 | | | | +| 5 | | | | +| 6 | | | | +| 7 | | | | +| 8 | | | | +| 9 | | | | +| 10 | | | | +| 11 | | | | +| 12 | | | | +| 13 | | | | +| 14 | | | | +| 15 | | | | +| 16 | | | | +| 17 | | | | +| 18 | | | | +| 19 | | | | +| 20 | | | | +| 21 | | | | +| 22 | | | | +| 23 | | | | +| 24 | | | | +| 25 | | | | +| 26 | | | | +| 27 | | | | +| 28 | | | | +| 29 | | | | +| 30 | | | | +| 31 | | | | +| 32 | | | | +| 33 | | | | +| 34 | | | | +| 35 | | | | +| 36 | | | | +| 37 | | | | +| 38 | | | | +| 39 | | | | +| 40 | | | | + +## APIs + +### `Union` + +| id | description | issues | met (×/✓) | +|----|-------------|--------|-----------| +| 1 | | | | +| 2 | | | | +| 3 | | | | +| 4 | | | | +| 5 | | | | +| 6 | | | | +| 7 | | | | +| 8 | | | | +| 9 | | | | +| 10 | | | | +| 11 | | | | +| 12 | | | | +| 13 | | | | +| 14 | | | | +| 15 | | | | +| 16 | | | | +| 17 | | | | +| 18 | | | | +| 19 | | | | +| 20 | | | | + +### `Union.Variant` + +| id | description | issues | met (×/✓) | +|----|-------------|--------|-----------| +| 1 | | | | +| 2 | | | | +| 3 | | | | +| 4 | | | | +| 5 | | | | +| 6 | | | | +| 7 | | | | +| 8 | | | | +| 9 | | | | +| 10 | | | | + +### `Union.VariantGroup` + +| id | description | issues | met (×/✓) | +|----|-------------|--------|-----------| +| 1 | | | | +| 2 | | | | +| 3 | | | | +| 4 | | | | +| 5 | | | | +| 6 | | | | +| 7 | | | | +| 8 | | | | +| 9 | | | | +| 10 | | | | + +## Notes + +### TODO + +- Relations + +### Table Template + +| id | description | issues | met (×/✓) | +|----|-------------|--------|-----------| +| 1 | | | | +| 2 | | | | +| 3 | | | | +| 4 | | | | +| 5 | | | | +| 6 | | | | +| 7 | | | | +| 8 | | | | +| 9 | | | | +| 10 | | | | + From c14b9d79791e4300610968e431d8f65d59b12360 Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Sat, 25 Oct 2025 18:16:55 +0200 Subject: [PATCH 02/36] add requirements --- UnionsGenerator/requirements.md | 180 ++++++++++++++------------------ 1 file changed, 79 insertions(+), 101 deletions(-) diff --git a/UnionsGenerator/requirements.md b/UnionsGenerator/requirements.md index 44bcbc0..7ce93fa 100644 --- a/UnionsGenerator/requirements.md +++ b/UnionsGenerator/requirements.md @@ -5,112 +5,90 @@ The term `union` refers to a tagged union or sum type, or an instance thereof. It is able to represent an instance of a type present in its set of representable types. +The term `Union` refers to a nonspecific generated type implementing the defined union. + The term `variant` refers to a type representable by a union. Unless improperly initialized, a union is always representing an instance of one of its variants. -The term `union value` or `value` refers to the variant instance being wrapped by a union. +The term `value` refers to the variant instance being represented by a union. + +The term `adapter` refers to the type adapting the union onto interfaces implemented by all its variants. ## Requirements -| id | description | issues | met (×/✓) | -|----|-------------|--------|-----------| -| 1 | | | | -| 2 | | | | -| 3 | | | | -| 4 | | | | -| 5 | | | | -| 6 | | | | -| 7 | | | | -| 8 | | | | -| 9 | | | | -| 10 | | | | -| 11 | | | | -| 12 | | | | -| 13 | | | | -| 14 | | | | -| 15 | | | | -| 16 | | | | -| 17 | | | | -| 18 | | | | -| 19 | | | | -| 20 | | | | -| 21 | | | | -| 22 | | | | -| 23 | | | | -| 24 | | | | -| 25 | | | | -| 26 | | | | -| 27 | | | | -| 28 | | | | -| 29 | | | | -| 30 | | | | -| 31 | | | | -| 32 | | | | -| 33 | | | | -| 34 | | | | -| 35 | | | | -| 36 | | | | -| 37 | | | | -| 38 | | | | -| 39 | | | | -| 40 | | | | +| id | description | issues | met (×/✓) | +|----|---------------------------------------------------------------------------------------------|--------|-----------| +| 5 | An error is issued if `Union` is not partial. | #151 | × | +| 6 | The value may be accessed as `object`. | #146 | × | +| 7 | Interfaces implemented by all variants are implemented by an adapter property on the union. | #40 | × | +| 8 | Conflicting interface member implementations on the adapter are implemented explicitly. | #40 | × | ## APIs ### `Union` -| id | description | issues | met (×/✓) | -|----|-------------|--------|-----------| -| 1 | | | | -| 2 | | | | -| 3 | | | | -| 4 | | | | -| 5 | | | | -| 6 | | | | -| 7 | | | | -| 8 | | | | -| 9 | | | | -| 10 | | | | -| 11 | | | | -| 12 | | | | -| 13 | | | | -| 14 | | | | -| 15 | | | | -| 16 | | | | -| 17 | | | | -| 18 | | | | -| 19 | | | | -| 20 | | | | +| id | signature | description | conditions | issues | met (×/✓) | +|----|-------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|--------------|--------|-----------| +| 1 | `T Switch(Func, Func)` | projects the value onto `T`, cases are obligatory | none | | × | +| 2 | `T Switch(Func, Func? = null, Func? = null)` | projects the value onto `T`, cases are optional, default case is obligatory | > 1 variants | | × | +| 3 | `T Switch(T, Func? = null, Func? = null)` | projects the value onto `T`, cases are optional, default projection is obligatory | > 1 variants | | × | +| 4 | `T Switch(TState, Func, Func)` | projects the value onto `T`, cases are obligatory, state is obligatory | none | | × | +| 5 | `T Switch(TState, Func, Func? = null, Func? = null)` | projects the value onto `T`, cases are optional, default case is obligatory, state is obligatory | > 1 variants | | × | +| 6 | `T Switch(TState, T, Func? = null, Func? = null)` | projects the value onto `T`, cases are optional, default projection is obligatory, state is obligatory | > 1 variants | | × | +| 7 | `void Switch(Action, Action)` | handles the value, cases are obligatory | none | | × | +| 8 | `void Switch(Action, Action? = null, Action? = null)` | handles the value, cases are optional, default case is obligatory | > 1 variants | | × | +| 9 | `void Switch(TState, Action, Action)` | handles the value, cases are obligatory, state is obligatory | none | | × | +| 10 | `void Switch(TState, Action , Action?, Action?)` | handles the value, cases are optional, default case is obligatory, state is obligatory | > 1 variants | | × | +| 11 | `Union Create(TValue)` | wraps a value in a union, is polymorphically sensitive (allows for variant instances typed to their superclass), accepts `Union` instances | none | #153 | × | +| 12 | `bool TryCreate(TValue, out Union union)` | wraps a value in a union, is polymorphically sensitive (allows for variant instances typed to their superclass), accepts `Union` instances | none | #153 | × | +| 13 | `ToObject` | | | | × | +| 14 | | | | | × | +| 15 | | | | | × | +| 16 | | | | | × | +| 17 | | | | | × | +| 18 | | | | | × | +| 19 | | | | | × | +| 20 | | | | | × | +| 21 | | | | | × | +| 22 | | | | | × | +| 23 | | | | | × | +| 24 | | | | | × | +| 25 | | | | | × | +| 26 | | | | | × | +| 27 | | | | | × | +| 28 | | | | | × | +| 29 | | | | | × | +| 30 | | | | | × | ### `Union.Variant` -| id | description | issues | met (×/✓) | -|----|-------------|--------|-----------| -| 1 | | | | -| 2 | | | | -| 3 | | | | -| 4 | | | | -| 5 | | | | -| 6 | | | | -| 7 | | | | -| 8 | | | | -| 9 | | | | -| 10 | | | | +| id | signature | description | conditions | issues | met (×/✓) | +|----|-----------|-------------|------------|--------|-----------| +| 1 | | | | | × | +| 2 | | | | | × | +| 3 | | | | | × | +| 4 | | | | | × | +| 5 | | | | | × | +| 6 | | | | | × | +| 7 | | | | | × | +| 8 | | | | | × | +| 9 | | | | | × | +| 10 | | | | | × | ### `Union.VariantGroup` -| id | description | issues | met (×/✓) | -|----|-------------|--------|-----------| -| 1 | | | | -| 2 | | | | -| 3 | | | | -| 4 | | | | -| 5 | | | | -| 6 | | | | -| 7 | | | | -| 8 | | | | -| 9 | | | | -| 10 | | | | +| id | signature | description | conditions | issues | met (×/✓) | +|----|-----------|-------------|------------|--------|-----------| +| 1 | | | | | × | +| 2 | | | | | × | +| 3 | | | | | × | +| 4 | | | | | × | +| 5 | | | | | × | +| 6 | | | | | × | +| 7 | | | | | × | +| 8 | | | | | × | +| 9 | | | | | × | +| 10 | | | | | × | ## Notes @@ -120,16 +98,16 @@ The term `union value` or `value` refers to the variant instance being wrapped b ### Table Template -| id | description | issues | met (×/✓) | -|----|-------------|--------|-----------| -| 1 | | | | -| 2 | | | | -| 3 | | | | -| 4 | | | | -| 5 | | | | -| 6 | | | | -| 7 | | | | -| 8 | | | | -| 9 | | | | -| 10 | | | | +| id | signature | description | conditions | issues | met (×/✓) | +|----|-----------|-------------|------------|--------|-----------| +| 1 | | | | | × | +| 2 | | | | | × | +| 3 | | | | | × | +| 4 | | | | | × | +| 5 | | | | | × | +| 6 | | | | | × | +| 7 | | | | | × | +| 8 | | | | | × | +| 9 | | | | | × | +| 10 | | | | | × | From b3e61b2de495654ee4d4074042636c11f91d7b6b Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Sat, 25 Oct 2025 22:18:23 +0200 Subject: [PATCH 03/36] update requirements.md --- UnionsGenerator/requirements.md | 163 ++++++++++++++++++++------------ 1 file changed, 100 insertions(+), 63 deletions(-) diff --git a/UnionsGenerator/requirements.md b/UnionsGenerator/requirements.md index 7ce93fa..f05a463 100644 --- a/UnionsGenerator/requirements.md +++ b/UnionsGenerator/requirements.md @@ -18,77 +18,114 @@ The term `adapter` refers to the type adapting the union onto interfaces impleme | id | description | issues | met (×/✓) | |----|---------------------------------------------------------------------------------------------|--------|-----------| -| 5 | An error is issued if `Union` is not partial. | #151 | × | -| 6 | The value may be accessed as `object`. | #146 | × | -| 7 | Interfaces implemented by all variants are implemented by an adapter property on the union. | #40 | × | -| 8 | Conflicting interface member implementations on the adapter are implemented explicitly. | #40 | × | +| 1 | An error is issued if `Union` is not partial. | #151 | × | +| 2 | Interfaces implemented by all variants are implemented by an adapter property on the union. | #40 | × | +| 3 | Conflicting interface member implementations on the adapter are implemented explicitly. | #40 | × | +| 4 | An error is issued for `ref` like variants. | | × | +| 4 | An error is issued for `record` unions. | | × | +| 5 | A warning is issued for non-nullable reference variant on struct union. | | × | +| 6 | An error is issued if more than 31 variant groups are defined. | | × | +| 7 | | | × | +| 8 | | | × | +| 9 | | | × | +| 10 | | | × | ## APIs ### `Union` -| id | signature | description | conditions | issues | met (×/✓) | -|----|-------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|--------------|--------|-----------| -| 1 | `T Switch(Func, Func)` | projects the value onto `T`, cases are obligatory | none | | × | -| 2 | `T Switch(Func, Func? = null, Func? = null)` | projects the value onto `T`, cases are optional, default case is obligatory | > 1 variants | | × | -| 3 | `T Switch(T, Func? = null, Func? = null)` | projects the value onto `T`, cases are optional, default projection is obligatory | > 1 variants | | × | -| 4 | `T Switch(TState, Func, Func)` | projects the value onto `T`, cases are obligatory, state is obligatory | none | | × | -| 5 | `T Switch(TState, Func, Func? = null, Func? = null)` | projects the value onto `T`, cases are optional, default case is obligatory, state is obligatory | > 1 variants | | × | -| 6 | `T Switch(TState, T, Func? = null, Func? = null)` | projects the value onto `T`, cases are optional, default projection is obligatory, state is obligatory | > 1 variants | | × | -| 7 | `void Switch(Action, Action)` | handles the value, cases are obligatory | none | | × | -| 8 | `void Switch(Action, Action? = null, Action? = null)` | handles the value, cases are optional, default case is obligatory | > 1 variants | | × | -| 9 | `void Switch(TState, Action, Action)` | handles the value, cases are obligatory, state is obligatory | none | | × | -| 10 | `void Switch(TState, Action , Action?, Action?)` | handles the value, cases are optional, default case is obligatory, state is obligatory | > 1 variants | | × | -| 11 | `Union Create(TValue)` | wraps a value in a union, is polymorphically sensitive (allows for variant instances typed to their superclass), accepts `Union` instances | none | #153 | × | -| 12 | `bool TryCreate(TValue, out Union union)` | wraps a value in a union, is polymorphically sensitive (allows for variant instances typed to their superclass), accepts `Union` instances | none | #153 | × | -| 13 | `ToObject` | | | | × | -| 14 | | | | | × | -| 15 | | | | | × | -| 16 | | | | | × | -| 17 | | | | | × | -| 18 | | | | | × | -| 19 | | | | | × | -| 20 | | | | | × | -| 21 | | | | | × | -| 22 | | | | | × | -| 23 | | | | | × | -| 24 | | | | | × | -| 25 | | | | | × | -| 26 | | | | | × | -| 27 | | | | | × | -| 28 | | | | | × | -| 29 | | | | | × | -| 30 | | | | | × | +| id | signature | description | conditions | issues | met (×/✓) | +|----|--------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|---------------|--------|-----------| +| 1 | `public T Switch(Func, Func)` | projects the value onto `T`, cases are obligatory | | | × | +| 2 | `public T Switch(Func, Func? = null, Func? = null)` | projects the value onto `T`, cases are optional, default case is obligatory | \> 1 variants | | × | +| 3 | `public T Switch(T, Func? = null, Func? = null)` | projects the value onto `T`, cases are optional, default projection is obligatory | \> 1 variants | | × | +| 4 | `public T Switch(TState, Func, Func)` | projects the value onto `T`, cases are obligatory, state is obligatory | | | × | +| 5 | `public T Switch(TState, Func, Func? = null, Func? = null)` | projects the value onto `T`, cases are optional, default case is obligatory, state is obligatory | \> 1 variants | | × | +| 6 | `public T Switch(TState, T, Func? = null, Func? = null)` | projects the value onto `T`, cases are optional, default projection is obligatory, state is obligatory | \> 1 variants | | × | +| 7 | `public void Switch(Action, Action)` | handles the value, cases are obligatory | | | × | +| 8 | `public void Switch(Action, Action? = null, Action? = null)` | handles the value, cases are optional, default case is obligatory | \> 1 variants | | × | +| 9 | `public void Switch(TState, Action, Action)` | handles the value, cases are obligatory, state is obligatory | | | × | +| 10 | `public void Switch(TState, Action , Action?, Action?)` | handles the value, cases are optional, default case is obligatory, state is obligatory | \> 1 variants | | × | +| 11 | `public Union Create(TValue)` | wraps a value in a union, is polymorphically sensitive (allows for variant instances typed to their superclass), accepts `Union` instances | | #153 | × | +| 12 | `public bool TryCreate(TValue, out Union union)` | wraps a value in a union, is polymorphically sensitive (allows for variant instances typed to their superclass), accepts `Union` instances | | #153 | × | +| 13 | `public object Value { get; }` | gets the underlying value, boxing it if it is a value type | | #146 | × | +| 14 | `public Union.Variant Variant { get; }` | gets the variant | | | × | +| 15 | | | | | × | +| 16 | | | | | × | +| 17 | | | | | × | +| 18 | | | | | × | +| 19 | | | | | × | +| 20 | | | | | × | +| 21 | | | | | × | +| 22 | | | | | × | +| 23 | | | | | × | +| 24 | | | | | × | +| 25 | | | | | × | +| 26 | | | | | × | +| 27 | | | | | × | +| 28 | | | | | × | +| 29 | | | | | × | +| 30 | | | | | × | ### `Union.Variant` -| id | signature | description | conditions | issues | met (×/✓) | -|----|-----------|-------------|------------|--------|-----------| -| 1 | | | | | × | -| 2 | | | | | × | -| 3 | | | | | × | -| 4 | | | | | × | -| 5 | | | | | × | -| 6 | | | | | × | -| 7 | | | | | × | -| 8 | | | | | × | -| 9 | | | | | × | -| 10 | | | | | × | - -### `Union.VariantGroup` - -| id | signature | description | conditions | issues | met (×/✓) | -|----|-----------|-------------|------------|--------|-----------| -| 1 | | | | | × | -| 2 | | | | | × | -| 3 | | | | | × | -| 4 | | | | | × | -| 5 | | | | | × | -| 6 | | | | | × | -| 7 | | | | | × | -| 8 | | | | | × | -| 9 | | | | | × | -| 10 | | | | | × | +| id | signature | description | conditions | issues | met (×/✓) | +|----|------------------------------|--------------------------------------------------------------------------------------|------------|--------|-----------| +| 1 | `public enum Variant : byte` | enumerates all variant names, names are mapped onto variant type references or aliae | | | × | +| 2 | `None = 0` | default value, used for detecting uninitialized unions | | | × | +| 3 | | | | | × | +| 4 | | | | | × | +| 5 | | | | | × | +| 6 | | | | | × | +| 7 | | | | | × | +| 8 | | | | | × | +| 9 | | | | | × | +| 10 | | | | | × | + +### `Union.VariantOperations` + +| id | signature | description | conditions | issues | met (×/✓) | +|----|-----------------------------------------|----------------------------------------------------------------|------------|--------|-----------| +| 1 | `public static class VariantOperations` | defines operations and extensions on `Union.Variant` instances | | | × | +| 2 | `public string Name { get; }` | gets the name of the variant | | | × | +| 3 | `public bool IsDefault { get; }` | indicates if the variant is `Variant.None` | | | × | +| 4 | `public VariantGroups Groups { get; }` | gets the groups containing the variant | | | × | +| 5 | | | | | × | +| 6 | | | | | × | +| 7 | | | | | × | +| 8 | | | | | × | +| 9 | | | | | × | +| 10 | | | | | × | + +### `Union.VariantGroups` + +| id | signature | description | conditions | issues | met (×/✓) | +|----|-------------------------------------------|----------------------------------------------|------------|--------|-----------| +| 1 | `[Flags] public enum VariantGroups : int` | enumerates all variant group names | | | × | +| 2 | `None = 0` | default group used for unassociated variants | | | × | +| 3 | | | | | × | +| 4 | | | | | × | +| 5 | | | | | × | +| 6 | | | | | × | +| 7 | | | | | × | +| 8 | | | | | × | +| 9 | | | | | × | +| 10 | | | | | × | + +### `Union.VariantGroupsOperations` + +| id | signature | description | conditions | issues | met (×/✓) | +|----|-----------------------------------------------|----------------------------------------------------------------------|------------|--------|-----------| +| 1 | `public static class VariantGroupsOperations` | defines operations and extensions on `Union.VariantGroups` instances | | | × | +| 2 | `public bool HasFlag(VariantGroups flag)` | defines operations and extensions on `Union.VariantGroups` instances | | | × | +| 3 | `public string Name { get; }` | gets the name of the variant group | | | × | +| 4 | `public bool IsDefault` | indicates if the variant group is `VariantGroups.None` | | | × | +| 5 | | | | | × | +| 6 | | | | | × | +| 7 | | | | | × | +| 8 | | | | | × | +| 9 | | | | | × | +| 10 | | | | | × | ## Notes From 3fcc9a5f89660758a0f053eee39f78e7ed4def56 Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Mon, 27 Oct 2025 00:11:26 +0100 Subject: [PATCH 04/36] update requirements.md --- UnionsGenerator/requirements.md | 134 ++++++++++++++++---------------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/UnionsGenerator/requirements.md b/UnionsGenerator/requirements.md index f05a463..e57cf26 100644 --- a/UnionsGenerator/requirements.md +++ b/UnionsGenerator/requirements.md @@ -16,19 +16,39 @@ The term `adapter` refers to the type adapting the union onto interfaces impleme ## Requirements -| id | description | issues | met (×/✓) | -|----|---------------------------------------------------------------------------------------------|--------|-----------| -| 1 | An error is issued if `Union` is not partial. | #151 | × | -| 2 | Interfaces implemented by all variants are implemented by an adapter property on the union. | #40 | × | -| 3 | Conflicting interface member implementations on the adapter are implemented explicitly. | #40 | × | -| 4 | An error is issued for `ref` like variants. | | × | -| 4 | An error is issued for `record` unions. | | × | -| 5 | A warning is issued for non-nullable reference variant on struct union. | | × | -| 6 | An error is issued if more than 31 variant groups are defined. | | × | -| 7 | | | × | -| 8 | | | × | -| 9 | | | × | -| 10 | | | × | +| id | description | issues | met (×/✓) | +|----|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|-----------| +| 1 | An error is issued if `Union` is not partial. | #151 | × | +| 2 | Interfaces implemented by all variants are implemented by an adapter property on the union. | #40 | × | +| 3 | Conflicting interface member implementations on the adapter are implemented explicitly. | #40 | × | +| 4 | An error is issued for `ref` like variants. | | × | +| 4 | An error is issued for `record` unions. | | × | +| 5 | A warning is issued for non-nullable reference variant on struct union. | | × | +| 6 | An error is issued if more than 31 variant groups are defined. This is to allow groups to be modelled using an int backed [Flags] enum. | | × | +| 7 | Variant group names have diagnostics mapped onto their definition in the attribute usage. | | × | +| 8 | Variant names have diagnostics mapped onto their definition in the attribute usage. | | × | +| 9 | An error is issued if more than 255 variants are defined. This is to allow variants to be modelled using a byte backed enum. | | × | +| 10 | An option is provided to box managed struct variants. | | × | +| 11 | Managed struct variants are stored in dedicated fields by default. | | × | +| 12 | An error is issued for static variants. | | × | +| 13 | An error is issued for conflicting variant names. | | × | +| 14 | An error is issued if no unmanaged, managed struct or nullable reference variant is defined for a struct union. This is to prevent struct unions to be left in an invalid state when uninitialized. | | × | +| 15 | Variants are ordered by unmanaged, managed struct, nullable reference, reference, fully qualified type name. This is to force the default variant to be valid when the struct union is uninitialized. | | × | +| 16 | If exactly one variant is defined, an implicit conversion to that variant is defined. | | × | +| 17 | If multiple variants are defined, an explicit conversion to each variant is defined. | | × | +| 18 | If a validation method is implemented, then an explicit conversion from the validated variant is defined. | | × | +| 19 | If a validation method is not implemented, then an implicit conversion from the variant is defined. | | × | +| 20 | | | × | +| 21 | | | × | +| 22 | | | × | +| 23 | | | × | +| 24 | | | × | +| 25 | | | × | +| 26 | | | × | +| 27 | | | × | +| 28 | | | × | +| 29 | | | × | +| 30 | | | × | ## APIs @@ -69,63 +89,43 @@ The term `adapter` refers to the type adapting the union onto interfaces impleme ### `Union.Variant` -| id | signature | description | conditions | issues | met (×/✓) | -|----|------------------------------|--------------------------------------------------------------------------------------|------------|--------|-----------| -| 1 | `public enum Variant : byte` | enumerates all variant names, names are mapped onto variant type references or aliae | | | × | -| 2 | `None = 0` | default value, used for detecting uninitialized unions | | | × | -| 3 | | | | | × | -| 4 | | | | | × | -| 5 | | | | | × | -| 6 | | | | | × | -| 7 | | | | | × | -| 8 | | | | | × | -| 9 | | | | | × | -| 10 | | | | | × | - -### `Union.VariantOperations` - -| id | signature | description | conditions | issues | met (×/✓) | -|----|-----------------------------------------|----------------------------------------------------------------|------------|--------|-----------| -| 1 | `public static class VariantOperations` | defines operations and extensions on `Union.Variant` instances | | | × | -| 2 | `public string Name { get; }` | gets the name of the variant | | | × | -| 3 | `public bool IsDefault { get; }` | indicates if the variant is `Variant.None` | | | × | -| 4 | `public VariantGroups Groups { get; }` | gets the groups containing the variant | | | × | -| 5 | | | | | × | -| 6 | | | | | × | -| 7 | | | | | × | -| 8 | | | | | × | -| 9 | | | | | × | -| 10 | | | | | × | +| id | signature | description | conditions | issues | met (×/✓) | +|----|--------------------------------------------------------|--------------------------------------------------------|------------|--------|-----------| +| 1 | `public readonly struct Variant` | represents a variant and enumerates all valid variants | | | × | +| 2 | `public static Variant None { get; }` | default value, used for detecting uninitialized unions | | | × | +| 3 | `public static Variant Variant0 { get; }` | enumeration of variants | | | × | +| 4 | `public static ImmutableArray GetAllValues()` | gets an array containing all available variant groups | | | × | +| 5 | `public string Name { get; }` | gets the name of the variant | | | × | +| 6 | `public Type Type { get; }` | gets the `System.Type` of the variant | | | × | +| 7 | `public VariantGroups Groups { get; }` | gets the variant groups containing this variant | | | × | +| 8 | `public bool IsDefault { get; }` | indicates whether the variant is equal to `None` | | | × | +| 9 | | | | | × | +| 10 | | | | | × | ### `Union.VariantGroups` -| id | signature | description | conditions | issues | met (×/✓) | -|----|-------------------------------------------|----------------------------------------------|------------|--------|-----------| -| 1 | `[Flags] public enum VariantGroups : int` | enumerates all variant group names | | | × | -| 2 | `None = 0` | default group used for unassociated variants | | | × | -| 3 | | | | | × | -| 4 | | | | | × | -| 5 | | | | | × | -| 6 | | | | | × | -| 7 | | | | | × | -| 8 | | | | | × | -| 9 | | | | | × | -| 10 | | | | | × | - -### `Union.VariantGroupsOperations` - -| id | signature | description | conditions | issues | met (×/✓) | -|----|-----------------------------------------------|----------------------------------------------------------------------|------------|--------|-----------| -| 1 | `public static class VariantGroupsOperations` | defines operations and extensions on `Union.VariantGroups` instances | | | × | -| 2 | `public bool HasFlag(VariantGroups flag)` | defines operations and extensions on `Union.VariantGroups` instances | | | × | -| 3 | `public string Name { get; }` | gets the name of the variant group | | | × | -| 4 | `public bool IsDefault` | indicates if the variant group is `VariantGroups.None` | | | × | -| 5 | | | | | × | -| 6 | | | | | × | -| 7 | | | | | × | -| 8 | | | | | × | -| 9 | | | | | × | -| 10 | | | | | × | +| id | signature | description | conditions | issues | met (×/✓) | +|----|-------------------------------------------------------------------------|-------------------------------------------------------------------------|------------|--------|-----------| +| 1 | `public readonly struct VariantGroups` | represents a variant group and enumerates all valid variant groups | | | × | +| 2 | `public static VariantGroups None { get; }` | default group used for unassociated variants | | | × | +| 3 | `public static VariantGroups VariantGroup0 { get; }` | enumeration of variant groups | | | × | +| 4 | `public static ImmutableArray GetAllValues()` | gets an array containing all available variant groups | | | × | +| 5 | `public bool Contains(VariantGroups groups)` | indicates whether groups is contained in the current group | | | × | +| 6 | `public bool IsDefault { get; }` | indicates whether the variant group is equal to `None` | | | × | +| 7 | `public static VariantGroups operator \|(VariantGroups, VariantGroups)` | bitwise or operator for combining variant groups | | | × | +| 8 | `public int IndividualGroupsCount { get; }` | gets the amount of individual groups combined in this group | | | × | +| 9 | `public void GetIndividualGroups(Span buffer)` | populates a buffer with the individual groups combined in this group | | | × | +| 10 | `public ImmutableArray GetIndividualGroups()` | gets an array containing the individual groups combined in this group | | | × | +| 11 | `public string Name { get; }` | gets the name of or combined names of the groups combined in this group | | | × | +| 12 | | | | | × | +| 13 | | | | | × | +| 14 | | | | | × | +| 15 | | | | | × | +| 16 | | | | | × | +| 17 | | | | | × | +| 18 | | | | | × | +| 19 | | | | | × | +| 20 | | | | | × | ## Notes From eecb8c69fda45dbd27b1f76f7ad52841c54a4386 Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Mon, 27 Oct 2025 00:12:34 +0100 Subject: [PATCH 05/36] update test lib, add test app --- RhoMicro.CodeAnalysis.sln | 14 + UnionsGenerator.TestApplication/Program.cs | 14 + .../UnionsGenerator.TestApplication.csproj | 17 + UnionsGenerator.TestLibrary/IUnion.cs | 12 + UnionsGenerator.TestLibrary/IUnionFactory.cs | 9 + UnionsGenerator.TestLibrary/IntDouble.cs | 693 ++++++++++++++++ .../IntDoubleString.cs | 781 ++++++++++++++++++ .../UnionsGenerator.TestLibrary.csproj | 21 + 8 files changed, 1561 insertions(+) create mode 100644 UnionsGenerator.TestApplication/Program.cs create mode 100644 UnionsGenerator.TestApplication/UnionsGenerator.TestApplication.csproj create mode 100644 UnionsGenerator.TestLibrary/IUnion.cs create mode 100644 UnionsGenerator.TestLibrary/IUnionFactory.cs create mode 100644 UnionsGenerator.TestLibrary/IntDouble.cs create mode 100644 UnionsGenerator.TestLibrary/IntDoubleString.cs create mode 100644 UnionsGenerator.TestLibrary/UnionsGenerator.TestLibrary.csproj diff --git a/RhoMicro.CodeAnalysis.sln b/RhoMicro.CodeAnalysis.sln index 2a0228f..5c5c884 100644 --- a/RhoMicro.CodeAnalysis.sln +++ b/RhoMicro.CodeAnalysis.sln @@ -71,6 +71,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OptionsGenerator.Tests", "O EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisitorGenerator", "VisitorGenerator\VisitorGenerator.csproj", "{D3BF809F-4878-470C-9897-D6C58D7068A9}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnionsGenerator.TestLibrary", "UnionsGenerator.TestLibrary\UnionsGenerator.TestLibrary.csproj", "{A88CF6DB-68BC-4CDF-8151-7C5981A25FB1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnionsGenerator.TestApplication", "UnionsGenerator.TestApplication\UnionsGenerator.TestApplication.csproj", "{23B13825-06BE-462A-A41D-CD5E26189BE0}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -185,6 +189,14 @@ Global {D3BF809F-4878-470C-9897-D6C58D7068A9}.Debug|Any CPU.Build.0 = Debug|Any CPU {D3BF809F-4878-470C-9897-D6C58D7068A9}.Release|Any CPU.ActiveCfg = Release|Any CPU {D3BF809F-4878-470C-9897-D6C58D7068A9}.Release|Any CPU.Build.0 = Release|Any CPU + {A88CF6DB-68BC-4CDF-8151-7C5981A25FB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A88CF6DB-68BC-4CDF-8151-7C5981A25FB1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A88CF6DB-68BC-4CDF-8151-7C5981A25FB1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A88CF6DB-68BC-4CDF-8151-7C5981A25FB1}.Release|Any CPU.Build.0 = Release|Any CPU + {23B13825-06BE-462A-A41D-CD5E26189BE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23B13825-06BE-462A-A41D-CD5E26189BE0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23B13825-06BE-462A-A41D-CD5E26189BE0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23B13825-06BE-462A-A41D-CD5E26189BE0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -217,6 +229,8 @@ Global {876F9DD3-D211-4867-B69E-60BF76946838} = {D2B8AC38-3B81-453F-A253-6D410D860D6C} {412EC8AA-7F9B-4A54-A478-0524B791A208} = {96BBB21A-F476-4234-97AF-D79F88CE0644} {D3BF809F-4878-470C-9897-D6C58D7068A9} = {96BBB21A-F476-4234-97AF-D79F88CE0644} + {A88CF6DB-68BC-4CDF-8151-7C5981A25FB1} = {D2B8AC38-3B81-453F-A253-6D410D860D6C} + {23B13825-06BE-462A-A41D-CD5E26189BE0} = {D2B8AC38-3B81-453F-A253-6D410D860D6C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8A219ED5-5120-4C5F-8EB4-FFF44FFCE2CF} diff --git a/UnionsGenerator.TestApplication/Program.cs b/UnionsGenerator.TestApplication/Program.cs new file mode 100644 index 0000000..3247f00 --- /dev/null +++ b/UnionsGenerator.TestApplication/Program.cs @@ -0,0 +1,14 @@ +// See https://aka.ms/new-console-template for more information + +var ids = IntDoubleString.Create(32); +Console.WriteLine(ids.Variant); +Console.WriteLine(ids.Value); +var id = IntDouble.Create(ids); +Console.WriteLine(id.Variant); +Console.WriteLine(id.Value); +id = 42d; +Console.WriteLine(id.Variant); +Console.WriteLine(id.Value); +ids = "Foo"; +Console.WriteLine(ids.Variant); +Console.WriteLine(ids.Value); diff --git a/UnionsGenerator.TestApplication/UnionsGenerator.TestApplication.csproj b/UnionsGenerator.TestApplication/UnionsGenerator.TestApplication.csproj new file mode 100644 index 0000000..ef1de10 --- /dev/null +++ b/UnionsGenerator.TestApplication/UnionsGenerator.TestApplication.csproj @@ -0,0 +1,17 @@ + + + + Exe + net9.0 + preview + enable + enable + + false + + + + + + + diff --git a/UnionsGenerator.TestLibrary/IUnion.cs b/UnionsGenerator.TestLibrary/IUnion.cs new file mode 100644 index 0000000..b45c69e --- /dev/null +++ b/UnionsGenerator.TestLibrary/IUnion.cs @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MPL-2.0 + +using System.Diagnostics.CodeAnalysis; + +public interface IUnion +{ + TUnion MapTo(TFactory factory) + where TFactory : IUnionFactory; + + bool TryMapTo(TFactory factory, [NotNullWhen(true)] out TUnion? union) + where TFactory : IUnionFactory; +} diff --git a/UnionsGenerator.TestLibrary/IUnionFactory.cs b/UnionsGenerator.TestLibrary/IUnionFactory.cs new file mode 100644 index 0000000..94391ae --- /dev/null +++ b/UnionsGenerator.TestLibrary/IUnionFactory.cs @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MPL-2.0 + +using System.Diagnostics.CodeAnalysis; + +public interface IUnionFactory +{ + TUnion Create(TVariant value); + bool TryCreate(TVariant value, [NotNullWhen(true)] out TUnion? union); +} diff --git a/UnionsGenerator.TestLibrary/IntDouble.cs b/UnionsGenerator.TestLibrary/IntDouble.cs new file mode 100644 index 0000000..98a7986 --- /dev/null +++ b/UnionsGenerator.TestLibrary/IntDouble.cs @@ -0,0 +1,693 @@ +// SPDX-License-Identifier: MPL-2.0 + +using System.Collections.Immutable; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +public partial class IntDouble : IUnion, IEquatable +{ +#region VariantGroupKinds + + [Flags] + public enum VariantGroupKinds : int + { + None = 0, + Number = 1 << 0, + Integer = 1 << 1, + ValueType = 1 << 3 + } + +#endregion + +#region VariantKind + + public enum VariantKind : byte + { + Int32, + Double + } + +#endregion + +#region Factory + + readonly struct Factory : IUnionFactory + { + public Boolean TryCreate(TVariant value, [NotNullWhen(true)] out IntDouble? union) + { + switch (value) + { + // switch against all variants + case Int32 v: return IntDouble.TryCreate(v, out union); + case Double v: return IntDouble.TryCreate(v, out union); + // By supporting the union itself as a variant, we avoid SOE when matching IUnion, + // which would otherwise recurse into this method. + case IntDouble v: + union = new IntDouble(v); + return true; + // TODO: detect/register related unions, match here (before IUnion box) + + // attempt to convert to this union through double dispatch, this will + // effectively convert between related unions, albeit while incurring boxing + case IUnion v: return v.TryMapTo(this, out union); + // switch against nullable variants here + /* + * case null: + * in this placeholder code, variants for string? and SomeVirtualClass? are assumed + * first, we check exact matches: + * + * if(typeof(TVariant) == typeof(string)) + * { + * return Union.TryCreate((string?)null, out union); + * } + * if(typeof(TVariant) == typeof(SomeVirtualClass)) + * { + * return Union.TryCreate((SomeVirtualClass?)null, out union); + * } + * + * next, we check polymorphic relationships on non-sealed variants: + * + * if(typeof(SomeVirtualClass).IsAssignableFrom(typeof(TVariant))) + * { + * return Union.TryCreate((SomeVirtualClass?)null, out union); + * } + */ + default: + union = default; + return false; + } + } + + public IntDouble Create(TVariant value) + { + // factory is responsible for exact variant match conversion + switch (value) + { + // switch against all variants + case Int32 v: return new IntDouble(v); + case Double v: return new IntDouble(v); + // By supporting the union itself as a variant, we avoid SOE when matching IUnion, + // which would otherwise recurse into this method. + case IntDouble v: return new IntDouble(v); + // TODO: detect/register related unions, match here (before IUnion box) + + // attempt to convert to this union through double dispatch, this will + // effectively convert between related unions, albeit while incurring boxing + case IUnion v: return v.MapTo(this); + case null: + // switch against nullable variants here + /* + * in this placeholder code, variants for string? and SomeVirtualClass? are assumed + * first, we check exact matches: + * + * if(typeof(TVariant) == typeof(string)) + * { + * return Union.Create((string?)null); + * } + * if(typeof(TVariant) == typeof(SomeVirtualClass)) + * { + * return Union.Create((SomeVirtualClass?)null); + * } + * + * next, we check polymorphic relationships on non-sealed variants: + * + * if(typeof(SomeVirtualClass).IsAssignableFrom(typeof(TVariant))) + * { + * return Union.Create((SomeVirtualClass?)null); + * } + */ + default: + throw new ArgumentOutOfRangeException( + nameof(value), + value, + $"Unable to create an instance of '{typeof(IntDouble)}' from a value of type '{value?.GetType() ?? typeof(TVariant)}'"); + } + } + } + +#endregion + +#region UnmanagedVariantsContainer + + [StructLayout(LayoutKind.Explicit)] + private readonly struct UnmanagedVariantsContainer + { + public UnmanagedVariantsContainer(Int32 value) + { + Int32 = value; + } + + public UnmanagedVariantsContainer(Double value) + { + Double = value; + } + + [FieldOffset(0)] public readonly Int32 Int32; + [FieldOffset(0)] public readonly Double Double; + } + +#endregion + +#region Constructors + + public IntDouble(IntDouble prototype) + { + } + + public IntDouble(Int32 value) : this(value, validate: true) + { + } + + public IntDouble(Double value) : this(value, validate: true) + { + } + + private IntDouble(Int32 value, Boolean validate) + { + if (validate) + { + var isValid = true; + Validate(value, throwIfInvalid: true, ref isValid); + } + + // assign reference container with null + // omit if no reference variants + // __referenceVariantsContainer = null!; + // assign unmanaged container if unmanaged variant + __unmanagedVariantsContainer = new UnmanagedVariantsContainer(value); + // enumerate unused managed variant field assignments with default + + Variant = VariantKind.Int32; + } + + private IntDouble(Double value, Boolean validate) + { + if (validate) + { + var isValid = true; + Validate(value, throwIfInvalid: true, ref isValid); + } + + // assign reference container with null + // omit if no reference variants + // __referenceVariantsContainer = null!; + // assign unmanaged container if unmanaged variant + __unmanagedVariantsContainer = new UnmanagedVariantsContainer(value); + // enumerate unused managed variant field assignments with default + + Variant = VariantKind.Double; + } + +#endregion + +#region Fields + + private readonly UnmanagedVariantsContainer __unmanagedVariantsContainer; + // private readonly Object __referenceVariantsContainer; + // enumerate fields for managed structs, make boxing optional + +#endregion + +#region Properties + + public VariantKind Variant { get; } + + public Object Value + { + get + { + Object result = Variant switch + { + VariantKind.Int32 => __unmanagedVariantsContainer.Int32, + VariantKind.Double => __unmanagedVariantsContainer.Double, + _ => throw new InvalidOperationException( + $"Unable to determine the variant of this union, as '{nameof(Variant)}' is not representing a valid variant of this union: '{Variant}'. This could be either because the union itself was not initialized correctly, or due to a bug in the 'UnionsGenerator' that generated this union type. Please report an issue to the maintainer.") + }; + + return result; + } + } + + public Boolean IsInt32 => Variant is VariantKind.Int32; + public Int32 AsInt32 => __unmanagedVariantsContainer.Int32; + + public Int32 ToInt32 => IsInt32 + ? AsInt32 + : throw new InvalidOperationException( + $"Unable to convert union to 'Int32', as it is currently representing the '{Variant}' variant."); + + public Boolean IsDouble => Variant is VariantKind.Double; + public Double AsDouble => __unmanagedVariantsContainer.Double; + + public Double ToDouble => IsDouble + ? AsDouble + : throw new InvalidOperationException( + $"Unable to convert union to 'Double', as it is currently representing the '{Variant}' variant."); + +#endregion + +#region Is/As Methods + + // warn that value is guaranteed to be null for class TVariant and false return + // attach [NNW(true)] and TVariant? when not representing any nullable reference variants + public Boolean TryAs(out TVariant value) + { + // TODO: fix variant comparisons not actually checking the currently represented variant + var variant = typeof(TVariant); + value = default!; + + if (variant == typeof(Int32)) + { + if (IsInt32) + { + var fromValue = AsInt32; + value = Unsafe.As(ref fromValue); + return true; + } + } + + if (variant == typeof(Double)) + { + var fromValue = AsDouble; + value = Unsafe.As(ref fromValue); + return IsDouble; + } + + if (variant == typeof(IntDouble)) + { + return true; + } + + if (variant == typeof(Object)) + { + return true; + } + + // include this check if we have value type variants + if (variant == typeof(Enum)) + { + // disjunct all value type variants + return Variant is VariantKind.Int32 or VariantKind.Double; + } + + return false; + } + + public Boolean Is() + { + var variant = typeof(TVariant); + + if (variant == Variant.Type) + { + return true; + } + + if (variant == typeof(IntDouble)) + { + return true; + } + + if (variant == typeof(Object)) + { + return true; + } + + // include this check if we have value type variants, replicate for enum types + if (variant == typeof(ValueType)) + { + // disjunct all value type variants + return Variant is VariantKind.Int32 or VariantKind.Double; + } + + return false; + } + +#endregion + +#region Validation + + static partial void Validate(Int32 value, Boolean throwIfInvalid, ref Boolean isValid); + static partial void Validate(Double value, Boolean throwIfInvalid, ref Boolean isValid); + +#endregion + +#region Factories + + public static IntDouble Create(IntDouble value) => new(value); + + public static Boolean TryCreate(IntDouble value, [NotNullWhen(true)] out IntDouble? union) + { + union = Create(value); + + return true; + } + + public static IntDouble Create(Int32 value) => new IntDouble(value, validate: true); + + public static Boolean TryCreate(Int32 value, [NotNullWhen(true)] out IntDouble? union) + { + var isValid = true; + Validate(value, throwIfInvalid: false, ref isValid); + union = isValid + ? new IntDouble(value, validate: false) + : default; + + return isValid; + } + + public static IntDouble Create(Double value) => new IntDouble(value, validate: true); + + public static Boolean TryCreate(Double value, [NotNullWhen(true)] out IntDouble? union) + { + var isValid = true; + Validate(value, throwIfInvalid: false, ref isValid); + union = isValid + ? new IntDouble(value, validate: false) + : default; + + return isValid; + } + + public static IntDouble Create(T value) + => new Factory().Create(value); + + public static Boolean TryCreate(T value, [NotNullWhen(true)] out IntDouble? union) + => new Factory().TryCreate(value, out union); + +#endregion + +#region Mapping + + // this method is intended for inter-union conversion only + public TUnion MapTo( + TFactory factory) + where TFactory : IUnionFactory + { + var result = Variant switch + { + VariantKind.Int32 => factory.Create(__unmanagedVariantsContainer.Int32), + VariantKind.Double => factory.Create(__unmanagedVariantsContainer.Double), + _ => throw new InvalidOperationException( + $"Unable to determine the variant of this union, as '{nameof(Variant)}' is not representing a valid variant of this union: '{Variant}'. This could be either because the union itself was not initialized correctly, or due to a bug in the 'UnionsGenerator' that generated this union type. Please report an issue to the maintainer.") + }; + + return result; + } + + // this method is intended for inter-union conversion only + public Boolean TryMapTo( + TFactory factory, + [NotNullWhen(true)] out TUnion? union) + where TFactory : IUnionFactory + { + var result = Variant switch + { + VariantKind.Int32 => factory.TryCreate(__unmanagedVariantsContainer.Int32, out union), + VariantKind.Double => factory.TryCreate(__unmanagedVariantsContainer.Double, out union), + _ => throw new InvalidOperationException( + $"Unable to determine the variant of this union, as '{nameof(Variant)}' is not representing a valid variant of this union: '{Variant}'. This could be either because the union itself was not initialized correctly, or due to a bug in the 'UnionsGenerator' that generated this union type. Please report an issue to the maintainer.") + }; + + return result; + } + +#endregion + +#region Equality + + public override Boolean Equals(Object? obj) => obj is IntDouble union && Equals(union); + + public Boolean Equals(IntDouble other) + { + // only emit for reference unions + if (ReferenceEquals(this, other)) + { + return true; + } + + if (Variant != other.Variant) + { + return false; + } + + var result = Variant switch + { + VariantKind.Int32 => + EqualityComparer.Default.Equals( + __unmanagedVariantsContainer.Int32, + other.__unmanagedVariantsContainer.Int32), + VariantKind.Double => + EqualityComparer.Default.Equals( + __unmanagedVariantsContainer.Double, + other.__unmanagedVariantsContainer.Double), + _ => throw new InvalidOperationException( + $"Unable to determine the variant of this union, as '{nameof(Variant)}' is not representing a valid variant of this union: '{Variant}'. This could be either because the union itself was not initialized correctly, or due to a bug in the 'UnionsGenerator' that generated this union type. Please report an issue to the maintainer.") + }; + + return result; + } + + public override Int32 GetHashCode() + { + var result = Variant switch + { + VariantKind.Int32 => HashCode.Combine(Variant, __unmanagedVariantsContainer.Int32), + VariantKind.Double => HashCode.Combine(__unmanagedVariantsContainer.Double), + _ => throw new InvalidOperationException( + $"Unable to determine the variant of this union, as '{nameof(Variant)}' is not representing a valid variant of this union: '{Variant}'. This could be either because the union itself was not initialized correctly, or due to a bug in the 'UnionsGenerator' that generated this union type. Please report an issue to the maintainer.") + }; + + return result; + } + +#endregion + +#region Conversion + + public static implicit operator IntDouble(Int32 value) => Create(value); + public static explicit operator Int32(IntDouble union) => union.ToInt32; + + public static implicit operator IntDouble(Double value) => Create(value); + public static explicit operator Double(IntDouble union) => union.ToDouble; + +#endregion +} + +public static partial class IntDoubleVariantGroupKindsOperations +{ + extension(IntDouble.VariantGroupKinds @this) + { + public Boolean HasFlag(IntDouble.VariantGroupKinds flag) => (@this & flag) == flag; + + public static ImmutableArray GetAllValues() => + [ + IntDouble.VariantGroupKinds.Number, + IntDouble.VariantGroupKinds.Integer, + IntDouble.VariantGroupKinds.ValueType + ]; + + public Int32 IndividualGroupCount => PopCount((UInt32)@this); + + private void GetIndividualGroups(Span buffer) + { + var count = @this.IndividualGroupCount; + + if (count is 0) + { + return; + } + + if (buffer.Length < count) + { + throw new ArgumentOutOfRangeException( + nameof(buffer), + $"{nameof(buffer)} did not have the required length of IndividualGroupCount. The required length was {count}, but the span provided had a length of {buffer.Length}."); + } + + if (count is 1) + { + buffer[0] = @this; + return; + } + + var groupIndex = 0; + for (var i = LeadingZeroCount((UInt32)@this) + 1; i < 33 && groupIndex < count; i++) + { + var flagPosition = 32 - i; + var flag = 1 << flagPosition; + if (((Int32)@this & flag) == flag) + { + buffer[groupIndex++] = (IntDouble.VariantGroupKinds)flag; + } + } + } + + public ImmutableArray GetIndividualGroups() + { + var groups = new IntDouble.VariantGroupKinds[@this.IndividualGroupCount]; + @this.GetIndividualGroups(groups); + var result = ImmutableCollectionsMarshal.AsImmutableArray(groups); + + return result; + } + + public String Name + { + get + { + var count = @this.IndividualGroupCount; + + if (count is 0 or 1) + { + return @this.DegenerateName; + } + + Span + groups = stackalloc IntDouble.VariantGroupKinds[count]; // Count never exceeds 32 + @this.GetIndividualGroups(groups); + var builder = new StringBuilder(); + for (var i = 0; i < count; i++) + { + var group = groups[i]; + var name = group.DegenerateName; + + if (i is not 0) + { + builder.Append(" | "); + } + + builder.Append(name); + } + + var result = builder.ToString(); + + return result; + } + } + + private String DegenerateName + { + get + { + return @this switch + { + IntDouble.VariantGroupKinds.None => nameof(IntDouble.VariantGroupKinds.None), + IntDouble.VariantGroupKinds.Number => nameof(IntDouble.VariantGroupKinds.Number), + IntDouble.VariantGroupKinds.Integer => nameof(IntDouble.VariantGroupKinds.Integer), + IntDouble.VariantGroupKinds.ValueType => nameof(IntDouble.VariantGroupKinds + .ValueType), + _ => throw new InvalidOperationException( + $"The VariantGroups instance was not initialized correctly and is holding an invalid value: {@this}") + }; + } + } + } + + private static ReadOnlySpan Log2DeBruijn => + [ + 00, 09, 01, 10, 13, 21, 02, 29, + 11, 14, 16, 18, 22, 25, 03, 30, + 08, 12, 20, 28, 15, 17, 24, 07, + 19, 27, 23, 06, 26, 05, 04, 31 + ]; + + private static Int32 LeadingZeroCount(UInt32 value) + { + if (value == 0) + { + return 32; + } + + value |= value >> 01; + value |= value >> 02; + value |= value >> 04; + value |= value >> 08; + value |= value >> 16; + + var result = 31 ^ Unsafe.AddByteOffset( + ref MemoryMarshal.GetReference(Log2DeBruijn), + (IntPtr)(Int32)((value * 0x07C4ACDDu) >> 27)); + + return result; + } + + private static Int32 PopCount(UInt32 value) + { + const UInt32 c1 = 0x_55555555u; + const UInt32 c2 = 0x_33333333u; + const UInt32 c3 = 0x_0F0F0F0Fu; + const UInt32 c4 = 0x_01010101u; + + value -= (value >> 1) & c1; + value = (value & c2) + ((value >> 2) & c2); + value = (((value + (value >> 4)) & c3) * c4) >> 24; + + return (Int32)value; + } +} + +public static partial class IntDoubleVariantKindsOperations +{ + extension(IntDouble.VariantKind @this) + { + public static ImmutableArray GetAllValues() => + [ + IntDouble.VariantKind.Int32, + IntDouble.VariantKind.Double, + ]; + + public String Name + { + get + { + var name = @this switch + { + IntDouble.VariantKind.Int32 => nameof(Int32), + IntDouble.VariantKind.Double => nameof(Double), + _ => throw new InvalidOperationException( + "Unable to determine name, as the variant was not initialized correctly.") + }; + + return name; + } + } + + public Type Type + { + get + { + var type = @this switch + { + IntDouble.VariantKind.Int32 => typeof(Int32), + IntDouble.VariantKind.Double => typeof(Double), + _ => throw new InvalidOperationException( + "Unable to determine type, as the variant was not initialized correctly.") + }; + + return type; + } + } + + public IntDouble.VariantGroupKinds Groups + { + get + { + var groups = @this switch + { + IntDouble.VariantKind.Int32 => IntDouble.VariantGroupKinds.Number | + IntDouble.VariantGroupKinds.Integer | + IntDouble.VariantGroupKinds.ValueType, + IntDouble.VariantKind.Double => IntDouble.VariantGroupKinds.Number | + IntDouble.VariantGroupKinds.ValueType, + _ => throw new InvalidOperationException( + "Unable to determine groups, as the variant was not initialized correctly.") + }; + + return groups; + } + } + } +} diff --git a/UnionsGenerator.TestLibrary/IntDoubleString.cs b/UnionsGenerator.TestLibrary/IntDoubleString.cs new file mode 100644 index 0000000..e19ea55 --- /dev/null +++ b/UnionsGenerator.TestLibrary/IntDoubleString.cs @@ -0,0 +1,781 @@ +// SPDX-License-Identifier: MPL-2.0 + +using System.Collections.Immutable; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Text; + +public partial class IntDoubleString : IUnion, IEquatable +{ +#region VariantGroupKinds + + [Flags] + public enum VariantGroupKinds : byte + { + None = 0, + Number = 1 << 0, + Integer = 1 << 1, + ReferenceType = 1 << 2, + ValueType = 1 << 3 + } + +#endregion + +#region VariantKind + + public enum VariantKind : byte + { + Int32, + Double, + String + } + +#endregion + +#region Factory + + readonly struct Factory : IUnionFactory + { + public Boolean TryCreate(TVariant value, [NotNullWhen(true)] out IntDoubleString? union) + { + switch (value) + { + // switch against all variants + case Int32 v: return IntDoubleString.TryCreate(v, out union); + case Double v: return IntDoubleString.TryCreate(v, out union); + case String v: return IntDoubleString.TryCreate(v, out union); + // By supporting the union itself as a variant, we avoid SOE when matching IUnion, + // which would otherwise recurse into this method. + case IntDoubleString v: + union = new IntDoubleString(v); + return true; + // TODO: detect/register related unions, match here (before IUnion box) + + // attempt to convert to this union through double dispatch, this will + // effectively convert between related unions, albeit while incurring boxing + case IUnion v: return v.TryMapTo(this, out union); + // switch against nullable variants here + /* + * case null: + * in this placeholder code, variants for string? and SomeVirtualClass? are assumed + * first, we check exact matches: + * + * if(typeof(TVariant) == typeof(string)) + * { + * return Union.TryCreate((string?)null, out union); + * } + * if(typeof(TVariant) == typeof(SomeVirtualClass)) + * { + * return Union.TryCreate((SomeVirtualClass?)null, out union); + * } + * + * next, we check polymorphic relationships on non-sealed variants: + * + * if(typeof(SomeVirtualClass).IsAssignableFrom(typeof(TVariant))) + * { + * return Union.TryCreate((SomeVirtualClass?)null, out union); + * } + */ + default: + union = default; + return false; + } + } + + public IntDoubleString Create(TVariant value) + { + // factory is responsible for exact variant match conversion + switch (value) + { + // switch against all variants + case Int32 v: return new IntDoubleString(v); + case Double v: return new IntDoubleString(v); + case String v: return new IntDoubleString(v); + // By supporting the union itself as a variant, we avoid SOE when matching IUnion, + // which would otherwise recurse into this method. + case IntDoubleString v: return new IntDoubleString(v); + // TODO: detect/register related unions, match here (before IUnion box) + + // attempt to convert to this union through double dispatch, this will + // effectively convert between related unions, albeit while incurring boxing + case IUnion v: return v.MapTo(this); + case null: + // switch against nullable variants here + /* + * in this placeholder code, variants for string? and SomeVirtualClass? are assumed + * first, we check exact matches: + * + * if(typeof(TVariant) == typeof(string)) + * { + * return Union.Create((string?)null); + * } + * if(typeof(TVariant) == typeof(SomeVirtualClass)) + * { + * return Union.Create((SomeVirtualClass?)null); + * } + * + * next, we check polymorphic relationships on non-sealed variants: + * + * if(typeof(SomeVirtualClass).IsAssignableFrom(typeof(TVariant))) + * { + * return Union.Create((SomeVirtualClass?)null); + * } + */ + default: + throw new ArgumentOutOfRangeException( + nameof(value), + value, + $"Unable to create an instance of '{typeof(IntDoubleString)}' from a value of type '{value?.GetType() ?? typeof(TVariant)}'"); + } + } + } + +#endregion + +#region UnmanagedVariantsContainer + + [StructLayout(LayoutKind.Explicit)] + private readonly struct UnmanagedVariantsContainer + { + public UnmanagedVariantsContainer(Int32 value) + { + Int32 = value; + } + + public UnmanagedVariantsContainer(Double value) + { + Double = value; + } + + [FieldOffset(0)] public readonly Int32 Int32; + [FieldOffset(0)] public readonly Double Double; + } + +#endregion + +#region Constructors + + public IntDoubleString(IntDoubleString prototype) + { + } + + public IntDoubleString(Int32 value) : this(value, validate: true) + { + } + + public IntDoubleString(Double value) : this(value, validate: true) + { + } + + public IntDoubleString(String value) : this(value, validate: true) + { + } + + private IntDoubleString(Int32 value, Boolean validate) + { + if (validate) + { + var isValid = true; + Validate(value, throwIfInvalid: true, ref isValid); + } + + // assign reference container with null + // omit if no reference variants + __referenceVariantsContainer = null!; + // assign unmanaged container if unmanaged variant + __unmanagedVariantsContainer = new UnmanagedVariantsContainer(value); + // enumerate unused managed variant field assignments with default + + Variant = VariantKind.Int32; + } + + private IntDoubleString(Double value, Boolean validate) + { + if (validate) + { + var isValid = true; + Validate(value, throwIfInvalid: true, ref isValid); + } + + // assign reference container with null + // omit if no reference variants + __referenceVariantsContainer = null!; + // assign unmanaged container if unmanaged variant + __unmanagedVariantsContainer = new UnmanagedVariantsContainer(value); + // enumerate unused managed variant field assignments with default + + Variant = VariantKind.Double; + } + + private IntDoubleString(String value, Boolean validate) + { + if (validate) + { + var isValid = true; + Validate(value, throwIfInvalid: true, ref isValid); + } + + // assign reference container with null + // omit if no reference variants + __referenceVariantsContainer = value; + // assign unmanaged container if unmanaged variants exist + __unmanagedVariantsContainer = default; + // enumerate unused managed variant field assignments with default + + Variant = VariantKind.String; + } + +#endregion + +#region Fields + + private readonly UnmanagedVariantsContainer __unmanagedVariantsContainer; + private readonly Object __referenceVariantsContainer; + // enumerate fields for managed structs, make boxing optional + +#endregion + +#region Properties + + public VariantKind Variant { get; } + + public Object Value + { + get + { + Object result = Variant switch + { + VariantKind.Int32 => __unmanagedVariantsContainer.Int32, + VariantKind.Double => __unmanagedVariantsContainer.Double, + VariantKind.String => __referenceVariantsContainer, + _ => throw new InvalidOperationException( + $"Unable to determine the variant of this union, as '{nameof(Variant)}' is not representing a valid variant of this union: '{Variant}'. This could be either because the union itself was not initialized correctly, or due to a bug in the 'UnionsGenerator' that generated this union type. Please report an issue to the maintainer.") + }; + + return result; + } + } + + public Boolean IsInt32 => Variant is VariantKind.Int32; + public Int32 AsInt32 => __unmanagedVariantsContainer.Int32; + + public Int32 ToInt32 => IsInt32 + ? AsInt32 + : throw new InvalidOperationException( + $"Unable to convert union to 'Int32', as it is currently representing the '{Variant}' variant."); + + public Boolean IsDouble => Variant is VariantKind.Double; + public Double AsDouble => __unmanagedVariantsContainer.Double; + + public Double ToDouble => IsDouble + ? AsDouble + : throw new InvalidOperationException( + $"Unable to convert union to 'Double', as it is currently representing the '{Variant}' variant."); + + // MNNW for non-nullable reference variants + [MemberNotNullWhen(true, nameof(AsString))] + public Boolean IsString => Variant is VariantKind.String; + + public String? AsString => (String?)__referenceVariantsContainer; + + public String ToString => IsString + ? AsString + : throw new InvalidOperationException( + $"Unable to convert union to 'String', as it is currently representing the '{Variant}' variant."); + +#endregion + +#region Is/As Methods + + // warn that value is guaranteed to be null for class TVariant and false return + // attach [NNW(true)] and TVariant? when not representing any nullable reference variants + public Boolean TryAs(out TVariant value) + { + // TODO: fix variant comparisons not actually checking the currently represented variant + var variant = typeof(TVariant); + value = default!; + + if (variant == typeof(Int32)) + { + if (IsInt32) + { + var fromValue = AsInt32; + value = Unsafe.As(ref fromValue); + return true; + } + } + + if (variant == typeof(Double)) + { + var fromValue = AsDouble; + value = Unsafe.As(ref fromValue); + return IsDouble; + } + + if (variant == typeof(String)) + { + var fromValue = AsString; + value = Unsafe.As(ref fromValue); + return IsString; + } + + if (variant == typeof(IntDoubleString)) + { + return true; + } + + if (variant == typeof(Object)) + { + return true; + } + + // include this check if we have value type variants + if (variant == typeof(Enum)) + { + // disjunct all value type variants + return Variant is VariantKind.Int32 or VariantKind.Double; + } + + // enumerate reference variants that do not directly inherit from object + if (variant.IsAssignableFrom(typeof(String))) + { + return IsString; + } + + return false; + } + + public Boolean Is() + { + var variant = typeof(TVariant); + + if (variant == Variant.Type) + { + return true; + } + + if (variant == typeof(IntDoubleString)) + { + return true; + } + + if (variant == typeof(Object)) + { + return true; + } + + // include this check if we have value type variants, replicate for enum types + if (variant == typeof(ValueType)) + { + // disjunct all value type variants + return Variant is VariantKind.Int32 or VariantKind.Double; + } + + // enumerate reference variants that do not directly inherit from object + if (variant.IsAssignableFrom(typeof(String))) + { + return IsString; + } + + return false; + } + +#endregion + +#region Validation + + static partial void Validate(Int32 value, Boolean throwIfInvalid, ref Boolean isValid); + static partial void Validate(Double value, Boolean throwIfInvalid, ref Boolean isValid); + static partial void Validate(String value, Boolean throwIfInvalid, ref Boolean isValid); + +#endregion + +#region Factories + + public static IntDoubleString Create(IntDoubleString value) => new(value); + + public static Boolean TryCreate(IntDoubleString value, [NotNullWhen(true)] out IntDoubleString? union) + { + union = Create(value); + + return true; + } + + public static IntDoubleString Create(Int32 value) => new IntDoubleString(value, validate: true); + + public static Boolean TryCreate(Int32 value, [NotNullWhen(true)] out IntDoubleString? union) + { + var isValid = true; + Validate(value, throwIfInvalid: false, ref isValid); + union = isValid + ? new IntDoubleString(value, validate: false) + : default; + + return isValid; + } + + public static IntDoubleString Create(Double value) => new IntDoubleString(value, validate: true); + + public static Boolean TryCreate(Double value, [NotNullWhen(true)] out IntDoubleString? union) + { + var isValid = true; + Validate(value, throwIfInvalid: false, ref isValid); + union = isValid + ? new IntDoubleString(value, validate: false) + : default; + + return isValid; + } + + public static IntDoubleString Create(String value) => new IntDoubleString(value, validate: true); + + public static Boolean TryCreate(String value, [NotNullWhen(true)] out IntDoubleString? union) + { + var isValid = true; + Validate(value, throwIfInvalid: false, ref isValid); + union = isValid + ? new IntDoubleString(value, validate: false) + : default; + + return isValid; + } + + public static IntDoubleString Create(T value) + => new Factory().Create(value); + + public static Boolean TryCreate(T value, [NotNullWhen(true)] out IntDoubleString? union) + => new Factory().TryCreate(value, out union); + +#endregion + +#region Mapping + + // this method is intended for inter-union conversion only + public TUnion MapTo( + TFactory factory) + where TFactory : IUnionFactory + { + var result = Variant switch + { + VariantKind.Int32 => factory.Create(__unmanagedVariantsContainer.Int32), + VariantKind.Double => factory.Create(__unmanagedVariantsContainer.Double), + VariantKind.String => factory.Create((String)__referenceVariantsContainer), + _ => throw new InvalidOperationException( + $"Unable to determine the variant of this union, as '{nameof(Variant)}' is not representing a valid variant of this union: '{Variant}'. This could be either because the union itself was not initialized correctly, or due to a bug in the 'UnionsGenerator' that generated this union type. Please report an issue to the maintainer.") + }; + + return result; + } + + // this method is intended for inter-union conversion only + public Boolean TryMapTo( + TFactory factory, + [NotNullWhen(true)] out TUnion? union) + where TFactory : IUnionFactory + { + var result = Variant switch + { + VariantKind.Int32 => factory.TryCreate(__unmanagedVariantsContainer.Int32, out union), + VariantKind.Double => factory.TryCreate(__unmanagedVariantsContainer.Double, out union), + VariantKind.String => factory.TryCreate((String)__referenceVariantsContainer, out union), + _ => throw new InvalidOperationException( + $"Unable to determine the variant of this union, as '{nameof(Variant)}' is not representing a valid variant of this union: '{Variant}'. This could be either because the union itself was not initialized correctly, or due to a bug in the 'UnionsGenerator' that generated this union type. Please report an issue to the maintainer.") + }; + + return result; + } + +#endregion + +#region Equality + + public override Boolean Equals(Object? obj) => obj is IntDoubleString union && Equals(union); + + public Boolean Equals(IntDoubleString other) + { + // only emit for reference unions + if (ReferenceEquals(this, other)) + { + return true; + } + + if (Variant != other.Variant) + { + return false; + } + + var result = Variant switch + { + VariantKind.Int32 => + EqualityComparer.Default.Equals( + __unmanagedVariantsContainer.Int32, + other.__unmanagedVariantsContainer.Int32), + VariantKind.Double => + EqualityComparer.Default.Equals( + __unmanagedVariantsContainer.Double, + other.__unmanagedVariantsContainer.Double), + VariantKind.String => + EqualityComparer.Default.Equals( + (String)__referenceVariantsContainer, + (String)other.__referenceVariantsContainer), + _ => throw new InvalidOperationException( + $"Unable to determine the variant of this union, as '{nameof(Variant)}' is not representing a valid variant of this union: '{Variant}'. This could be either because the union itself was not initialized correctly, or due to a bug in the 'UnionsGenerator' that generated this union type. Please report an issue to the maintainer.") + }; + + return result; + } + + public override Int32 GetHashCode() + { + var result = Variant switch + { + VariantKind.Int32 => HashCode.Combine(Variant, __unmanagedVariantsContainer.Int32), + VariantKind.Double => HashCode.Combine(__unmanagedVariantsContainer.Double), + VariantKind.String => HashCode.Combine((String)__referenceVariantsContainer), + _ => throw new InvalidOperationException( + $"Unable to determine the variant of this union, as '{nameof(Variant)}' is not representing a valid variant of this union: '{Variant}'. This could be either because the union itself was not initialized correctly, or due to a bug in the 'UnionsGenerator' that generated this union type. Please report an issue to the maintainer.") + }; + + return result; + } + +#endregion + +#region Conversion + + public static implicit operator IntDoubleString(Int32 value) => Create(value); + public static explicit operator Int32(IntDoubleString union) => union.ToInt32; + + public static implicit operator IntDoubleString(Double value) => Create(value); + public static explicit operator Double(IntDoubleString union) => union.ToDouble; + + public static implicit operator IntDoubleString(String value) => Create(value); + public static explicit operator String(IntDoubleString union) => union.ToString; + +#endregion +} + +public static partial class IntDoubleStringVariantGroupKindsOperations +{ + extension(IntDoubleString.VariantGroupKinds @this) + { + public Boolean HasFlag(IntDoubleString.VariantGroupKinds flag) => (@this & flag) == flag; + + public static ImmutableArray GetAllValues() => + [ + IntDoubleString.VariantGroupKinds.Number, + IntDoubleString.VariantGroupKinds.Integer, + IntDoubleString.VariantGroupKinds.ReferenceType, + IntDoubleString.VariantGroupKinds.ValueType + ]; + + public Int32 IndividualGroupCount => PopCount((UInt32)@this); + + private void GetIndividualGroups(Span buffer) + { + var count = @this.IndividualGroupCount; + + if (count is 0) + { + return; + } + + if (buffer.Length < count) + { + throw new ArgumentOutOfRangeException( + nameof(buffer), + $"{nameof(buffer)} did not have the required length of IndividualGroupCount. The required length was {count}, but the span provided had a length of {buffer.Length}."); + } + + if (count is 1) + { + buffer[0] = @this; + return; + } + + var groupIndex = 0; + for (var i = LeadingZeroCount((UInt32)@this) + 1; i < 33 && groupIndex < count; i++) + { + var flagPosition = 32 - i; + var flag = 1 << flagPosition; + if (((Int32)@this & flag) == flag) + { + buffer[groupIndex++] = (IntDoubleString.VariantGroupKinds)flag; + } + } + } + + public ImmutableArray GetIndividualGroups() + { + var groups = new IntDoubleString.VariantGroupKinds[@this.IndividualGroupCount]; + @this.GetIndividualGroups(groups); + var result = ImmutableCollectionsMarshal.AsImmutableArray(groups); + + return result; + } + + public String Name + { + get + { + var count = @this.IndividualGroupCount; + + if (count is 0 or 1) + { + return @this.DegenerateName; + } + + Span + groups = stackalloc IntDoubleString.VariantGroupKinds[count]; // Count never exceeds 32 + @this.GetIndividualGroups(groups); + var builder = new StringBuilder(); + for (var i = 0; i < count; i++) + { + var group = groups[i]; + var name = group.DegenerateName; + + if (i is not 0) + { + builder.Append(" | "); + } + + builder.Append(name); + } + + var result = builder.ToString(); + + return result; + } + } + + private String DegenerateName + { + get + { + return @this switch + { + IntDoubleString.VariantGroupKinds.None => nameof(IntDoubleString.VariantGroupKinds.None), + IntDoubleString.VariantGroupKinds.Number => nameof(IntDoubleString.VariantGroupKinds.Number), + IntDoubleString.VariantGroupKinds.Integer => nameof(IntDoubleString.VariantGroupKinds.Integer), + IntDoubleString.VariantGroupKinds.ReferenceType => nameof(IntDoubleString.VariantGroupKinds + .ReferenceType), + IntDoubleString.VariantGroupKinds.ValueType => nameof(IntDoubleString.VariantGroupKinds + .ValueType), + _ => throw new InvalidOperationException( + $"The VariantGroups instance was not initialized correctly and is holding an invalid value: {@this}") + }; + } + } + } + + private static ReadOnlySpan Log2DeBruijn => + [ + 00, 09, 01, 10, 13, 21, 02, 29, + 11, 14, 16, 18, 22, 25, 03, 30, + 08, 12, 20, 28, 15, 17, 24, 07, + 19, 27, 23, 06, 26, 05, 04, 31 + ]; + + private static Int32 LeadingZeroCount(UInt32 value) + { + if (value == 0) + { + return 32; + } + + value |= value >> 01; + value |= value >> 02; + value |= value >> 04; + value |= value >> 08; + value |= value >> 16; + + var result = 31 ^ Unsafe.AddByteOffset( + ref MemoryMarshal.GetReference(Log2DeBruijn), + (IntPtr)(Int32)((value * 0x07C4ACDDu) >> 27)); + + return result; + } + + private static Int32 PopCount(UInt32 value) + { + const UInt32 c1 = 0x_55555555u; + const UInt32 c2 = 0x_33333333u; + const UInt32 c3 = 0x_0F0F0F0Fu; + const UInt32 c4 = 0x_01010101u; + + value -= (value >> 1) & c1; + value = (value & c2) + ((value >> 2) & c2); + value = (((value + (value >> 4)) & c3) * c4) >> 24; + + return (Int32)value; + } +} + +public static partial class IntDoubleStringVariantKindsOperations +{ + extension(IntDoubleString.VariantKind @this) + { + public static ImmutableArray GetAllValues() => + [ + IntDoubleString.VariantKind.Int32, + IntDoubleString.VariantKind.Double, + IntDoubleString.VariantKind.String + ]; + + public String Name + { + get + { + var name = @this switch + { + IntDoubleString.VariantKind.Int32 => nameof(Int32), + IntDoubleString.VariantKind.Double => nameof(Double), + IntDoubleString.VariantKind.String => nameof(String), + _ => throw new InvalidOperationException( + "Unable to determine name, as the variant was not initialized correctly.") + }; + + return name; + } + } + + public Type Type + { + get + { + var type = @this switch + { + IntDoubleString.VariantKind.Int32 => typeof(Int32), + IntDoubleString.VariantKind.Double => typeof(Double), + IntDoubleString.VariantKind.String => typeof(String), + _ => throw new InvalidOperationException( + "Unable to determine type, as the variant was not initialized correctly.") + }; + + return type; + } + } + + public IntDoubleString.VariantGroupKinds Groups + { + get + { + var groups = @this switch + { + IntDoubleString.VariantKind.Int32 => IntDoubleString.VariantGroupKinds.Number | + IntDoubleString.VariantGroupKinds.Integer | + IntDoubleString.VariantGroupKinds.ValueType, + IntDoubleString.VariantKind.Double => IntDoubleString.VariantGroupKinds.Number | + IntDoubleString.VariantGroupKinds.ValueType, + IntDoubleString.VariantKind.String => IntDoubleString.VariantGroupKinds.ReferenceType, + _ => throw new InvalidOperationException( + "Unable to determine groups, as the variant was not initialized correctly.") + }; + + return groups; + } + } + } +} diff --git a/UnionsGenerator.TestLibrary/UnionsGenerator.TestLibrary.csproj b/UnionsGenerator.TestLibrary/UnionsGenerator.TestLibrary.csproj new file mode 100644 index 0000000..d08f80d --- /dev/null +++ b/UnionsGenerator.TestLibrary/UnionsGenerator.TestLibrary.csproj @@ -0,0 +1,21 @@ + + + + netstandard2.0 + preview + enable + + false + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + From c6af13c348702cc2e26bb382ea2606e751a577b3 Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Mon, 27 Oct 2025 00:27:28 +0100 Subject: [PATCH 06/36] migrate to slnx --- RhoMicro.CodeAnalysis.sln | 238 ------------------------------------- RhoMicro.CodeAnalysis.slnx | 43 +++++++ 2 files changed, 43 insertions(+), 238 deletions(-) delete mode 100644 RhoMicro.CodeAnalysis.sln create mode 100644 RhoMicro.CodeAnalysis.slnx diff --git a/RhoMicro.CodeAnalysis.sln b/RhoMicro.CodeAnalysis.sln deleted file mode 100644 index 5c5c884..0000000 --- a/RhoMicro.CodeAnalysis.sln +++ /dev/null @@ -1,238 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.8.34309.116 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CopyToGenerator", "CopyToGenerator\CopyToGenerator.csproj", "{255A7B8C-A679-4A8F-A049-EA39EC0C248F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestApp", "TestApp\TestApp.csproj", "{88C7D33F-6100-46C7-8941-B97C5E769A10}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{518E0314-5813-462C-AD0A-48491B32707D}" - ProjectSection(SolutionItems) = preProject - .gitattributes = .gitattributes - .gitignore = .gitignore - AutoUpdateAssemblyName.txt = AutoUpdateAssemblyName.txt - .github\workflows\buildPublish.yml = .github\workflows\buildPublish.yml - Directory.Build.props = Directory.Build.props - global.json = global.json - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnionsGenerator", "UnionsGenerator\UnionsGenerator.csproj", "{C143CDD6-2A5C-4D7E-89B2-76676A6FA70D}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Generators", "Generators", "{96BBB21A-F476-4234-97AF-D79F88CE0644}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Applications", "Applications", "{D2B8AC38-3B81-453F-A253-6D410D860D6C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UtilityGenerators", "UtilityGenerators\UtilityGenerators.csproj", "{D0545B95-3C8C-4E5F-B1ED-8AF3372BEDA3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DslGenerator", "DslGenerator\DslGenerator.csproj", "{EC3134B3-F85E-4331-93B2-ABE5829FFF8A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DslGenerator.TestApp", "DslGenerator.TestApp\DslGenerator.TestApp.csproj", "{1F8A9A65-94CB-482E-B051-D89307A6A042}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DslGenerator.Tests", "DslGenerator.Tests\DslGenerator.Tests.csproj", "{964E486B-07D7-4771-9330-3BB3D662A8C1}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IndentedStringBuilderTestApp", "IndentedStringBuilderTestApp\IndentedStringBuilderTestApp.csproj", "{08AEFD73-58E0-4EF7-A429-BE9939844D97}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DocReflect.Library", "DocReflect.Library\DocReflect.Library.csproj", "{DB95ECDD-21ED-4180-B8C1-E76BE816D55B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DocReflect", "DocReflect\DocReflect.csproj", "{0D39FED5-B843-454D-9CFD-EEC5CB26FC1C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Project1", "Project1\Project1.csproj", "{A12BC4A0-DCB8-4FCB-8BFA-ACD0DCD4B568}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnionsGenerator.Tests", "UnionsGenerator.Tests\UnionsGenerator.Tests.csproj", "{9743772D-6AEE-4775-AEAA-C4E58DDCE0E9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnionsGenerator.EndToEnd.Tests", "UnionsGenerator.EndToEnd.Tests\UnionsGenerator.EndToEnd.Tests.csproj", "{374C6DDC-DDA9-450F-A449-AA7FC3985C2F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnumConstStringGenerator", "EnumConstStringGenerator\EnumConstStringGenerator.csproj", "{F6AEDF20-AA0A-4789-B8E9-3399C15EED48}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnumStringTestApp", "EnumStringTestApp\EnumStringTestApp.csproj", "{15D08162-340C-4520-97F9-B42665D281E2}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonSchemaGenerator", "JsonSchemaGenerator\JsonSchemaGenerator.csproj", "{40A4A78F-A784-4F79-9C9F-F065AD64255F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonSchemaGenerator.Tests", "JsonSchemaGenerator.Tests\JsonSchemaGenerator.Tests.csproj", "{6F3EB5CA-3DC7-4DF5-97DA-2E7C519C011F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonSchemaGenerator.Cli", "JsonSchemaGenerator.Cli\JsonSchemaGenerator.Cli.csproj", "{8076F7EE-7474-42A6-96E6-60CF266008BA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UtilityGenerators.Dev", "UtilityGenerators.Dev\UtilityGenerators.Dev.csproj", "{0E42553E-91C8-46D9-93EF-FA95EBA517C4}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UtilityGenerators.Benchmarks", "UtilityGenerators.Benchmarks\UtilityGenerators.Benchmarks.csproj", "{E9D11F2B-2694-4240-BFA8-3447FA640A35}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UtilityGenerators.Tests.E2E", "UtilityGenerators.Tests.E2E\UtilityGenerators.Tests.E2E.csproj", "{9F46450C-0927-42F5-B35E-555E0035D42A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UtilityGenerators.Tests", "UtilityGenerators.Tests\UtilityGenerators.Tests.csproj", "{03AE948B-39FF-485C-9B63-38006A5BB20F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UtilityGenerators.Library.Text.SourceTexts", "UtilityGenerators.Library.Text.SourceTexts\UtilityGenerators.Library.Text.SourceTexts.csproj", "{D5A0C9EE-C6DA-4A32-A802-EBB5981367C9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OptionsGenerator", "OptionsGenerator\OptionsGenerator.csproj", "{7253892F-8AF6-41DC-B2E5-C231E3F8E4BF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WorkerService1", "WorkerService1\WorkerService1.csproj", "{876F9DD3-D211-4867-B69E-60BF76946838}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OptionsGenerator.Tests", "OptionsGenerator.Tests\OptionsGenerator.Tests.csproj", "{412EC8AA-7F9B-4A54-A478-0524B791A208}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VisitorGenerator", "VisitorGenerator\VisitorGenerator.csproj", "{D3BF809F-4878-470C-9897-D6C58D7068A9}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnionsGenerator.TestLibrary", "UnionsGenerator.TestLibrary\UnionsGenerator.TestLibrary.csproj", "{A88CF6DB-68BC-4CDF-8151-7C5981A25FB1}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnionsGenerator.TestApplication", "UnionsGenerator.TestApplication\UnionsGenerator.TestApplication.csproj", "{23B13825-06BE-462A-A41D-CD5E26189BE0}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {255A7B8C-A679-4A8F-A049-EA39EC0C248F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {255A7B8C-A679-4A8F-A049-EA39EC0C248F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {255A7B8C-A679-4A8F-A049-EA39EC0C248F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {255A7B8C-A679-4A8F-A049-EA39EC0C248F}.Release|Any CPU.Build.0 = Release|Any CPU - {88C7D33F-6100-46C7-8941-B97C5E769A10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {88C7D33F-6100-46C7-8941-B97C5E769A10}.Debug|Any CPU.Build.0 = Debug|Any CPU - {88C7D33F-6100-46C7-8941-B97C5E769A10}.Release|Any CPU.ActiveCfg = Release|Any CPU - {88C7D33F-6100-46C7-8941-B97C5E769A10}.Release|Any CPU.Build.0 = Release|Any CPU - {C143CDD6-2A5C-4D7E-89B2-76676A6FA70D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C143CDD6-2A5C-4D7E-89B2-76676A6FA70D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C143CDD6-2A5C-4D7E-89B2-76676A6FA70D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C143CDD6-2A5C-4D7E-89B2-76676A6FA70D}.Release|Any CPU.Build.0 = Release|Any CPU - {D0545B95-3C8C-4E5F-B1ED-8AF3372BEDA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D0545B95-3C8C-4E5F-B1ED-8AF3372BEDA3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D0545B95-3C8C-4E5F-B1ED-8AF3372BEDA3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D0545B95-3C8C-4E5F-B1ED-8AF3372BEDA3}.Release|Any CPU.Build.0 = Release|Any CPU - {EC3134B3-F85E-4331-93B2-ABE5829FFF8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EC3134B3-F85E-4331-93B2-ABE5829FFF8A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EC3134B3-F85E-4331-93B2-ABE5829FFF8A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EC3134B3-F85E-4331-93B2-ABE5829FFF8A}.Release|Any CPU.Build.0 = Release|Any CPU - {1F8A9A65-94CB-482E-B051-D89307A6A042}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1F8A9A65-94CB-482E-B051-D89307A6A042}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1F8A9A65-94CB-482E-B051-D89307A6A042}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1F8A9A65-94CB-482E-B051-D89307A6A042}.Release|Any CPU.Build.0 = Release|Any CPU - {964E486B-07D7-4771-9330-3BB3D662A8C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {964E486B-07D7-4771-9330-3BB3D662A8C1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {964E486B-07D7-4771-9330-3BB3D662A8C1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {964E486B-07D7-4771-9330-3BB3D662A8C1}.Release|Any CPU.Build.0 = Release|Any CPU - {08AEFD73-58E0-4EF7-A429-BE9939844D97}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {08AEFD73-58E0-4EF7-A429-BE9939844D97}.Debug|Any CPU.Build.0 = Debug|Any CPU - {08AEFD73-58E0-4EF7-A429-BE9939844D97}.Release|Any CPU.ActiveCfg = Release|Any CPU - {08AEFD73-58E0-4EF7-A429-BE9939844D97}.Release|Any CPU.Build.0 = Release|Any CPU - {DB95ECDD-21ED-4180-B8C1-E76BE816D55B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DB95ECDD-21ED-4180-B8C1-E76BE816D55B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DB95ECDD-21ED-4180-B8C1-E76BE816D55B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DB95ECDD-21ED-4180-B8C1-E76BE816D55B}.Release|Any CPU.Build.0 = Release|Any CPU - {0D39FED5-B843-454D-9CFD-EEC5CB26FC1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0D39FED5-B843-454D-9CFD-EEC5CB26FC1C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0D39FED5-B843-454D-9CFD-EEC5CB26FC1C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0D39FED5-B843-454D-9CFD-EEC5CB26FC1C}.Release|Any CPU.Build.0 = Release|Any CPU - {A12BC4A0-DCB8-4FCB-8BFA-ACD0DCD4B568}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A12BC4A0-DCB8-4FCB-8BFA-ACD0DCD4B568}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A12BC4A0-DCB8-4FCB-8BFA-ACD0DCD4B568}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A12BC4A0-DCB8-4FCB-8BFA-ACD0DCD4B568}.Release|Any CPU.Build.0 = Release|Any CPU - {9743772D-6AEE-4775-AEAA-C4E58DDCE0E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9743772D-6AEE-4775-AEAA-C4E58DDCE0E9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9743772D-6AEE-4775-AEAA-C4E58DDCE0E9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9743772D-6AEE-4775-AEAA-C4E58DDCE0E9}.Release|Any CPU.Build.0 = Release|Any CPU - {374C6DDC-DDA9-450F-A449-AA7FC3985C2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {374C6DDC-DDA9-450F-A449-AA7FC3985C2F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {374C6DDC-DDA9-450F-A449-AA7FC3985C2F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {374C6DDC-DDA9-450F-A449-AA7FC3985C2F}.Release|Any CPU.Build.0 = Release|Any CPU - {F6AEDF20-AA0A-4789-B8E9-3399C15EED48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F6AEDF20-AA0A-4789-B8E9-3399C15EED48}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F6AEDF20-AA0A-4789-B8E9-3399C15EED48}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F6AEDF20-AA0A-4789-B8E9-3399C15EED48}.Release|Any CPU.Build.0 = Release|Any CPU - {15D08162-340C-4520-97F9-B42665D281E2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {15D08162-340C-4520-97F9-B42665D281E2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {15D08162-340C-4520-97F9-B42665D281E2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {15D08162-340C-4520-97F9-B42665D281E2}.Release|Any CPU.Build.0 = Release|Any CPU - {40A4A78F-A784-4F79-9C9F-F065AD64255F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {40A4A78F-A784-4F79-9C9F-F065AD64255F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {40A4A78F-A784-4F79-9C9F-F065AD64255F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {40A4A78F-A784-4F79-9C9F-F065AD64255F}.Release|Any CPU.Build.0 = Release|Any CPU - {6F3EB5CA-3DC7-4DF5-97DA-2E7C519C011F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6F3EB5CA-3DC7-4DF5-97DA-2E7C519C011F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6F3EB5CA-3DC7-4DF5-97DA-2E7C519C011F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6F3EB5CA-3DC7-4DF5-97DA-2E7C519C011F}.Release|Any CPU.Build.0 = Release|Any CPU - {8076F7EE-7474-42A6-96E6-60CF266008BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8076F7EE-7474-42A6-96E6-60CF266008BA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8076F7EE-7474-42A6-96E6-60CF266008BA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8076F7EE-7474-42A6-96E6-60CF266008BA}.Release|Any CPU.Build.0 = Release|Any CPU - {0E42553E-91C8-46D9-93EF-FA95EBA517C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0E42553E-91C8-46D9-93EF-FA95EBA517C4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0E42553E-91C8-46D9-93EF-FA95EBA517C4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0E42553E-91C8-46D9-93EF-FA95EBA517C4}.Release|Any CPU.Build.0 = Release|Any CPU - {E9D11F2B-2694-4240-BFA8-3447FA640A35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E9D11F2B-2694-4240-BFA8-3447FA640A35}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E9D11F2B-2694-4240-BFA8-3447FA640A35}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E9D11F2B-2694-4240-BFA8-3447FA640A35}.Release|Any CPU.Build.0 = Release|Any CPU - {9F46450C-0927-42F5-B35E-555E0035D42A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9F46450C-0927-42F5-B35E-555E0035D42A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9F46450C-0927-42F5-B35E-555E0035D42A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9F46450C-0927-42F5-B35E-555E0035D42A}.Release|Any CPU.Build.0 = Release|Any CPU - {03AE948B-39FF-485C-9B63-38006A5BB20F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {03AE948B-39FF-485C-9B63-38006A5BB20F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {03AE948B-39FF-485C-9B63-38006A5BB20F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {03AE948B-39FF-485C-9B63-38006A5BB20F}.Release|Any CPU.Build.0 = Release|Any CPU - {D5A0C9EE-C6DA-4A32-A802-EBB5981367C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D5A0C9EE-C6DA-4A32-A802-EBB5981367C9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D5A0C9EE-C6DA-4A32-A802-EBB5981367C9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D5A0C9EE-C6DA-4A32-A802-EBB5981367C9}.Release|Any CPU.Build.0 = Release|Any CPU - {7253892F-8AF6-41DC-B2E5-C231E3F8E4BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7253892F-8AF6-41DC-B2E5-C231E3F8E4BF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7253892F-8AF6-41DC-B2E5-C231E3F8E4BF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7253892F-8AF6-41DC-B2E5-C231E3F8E4BF}.Release|Any CPU.Build.0 = Release|Any CPU - {876F9DD3-D211-4867-B69E-60BF76946838}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {876F9DD3-D211-4867-B69E-60BF76946838}.Debug|Any CPU.Build.0 = Debug|Any CPU - {876F9DD3-D211-4867-B69E-60BF76946838}.Release|Any CPU.ActiveCfg = Release|Any CPU - {876F9DD3-D211-4867-B69E-60BF76946838}.Release|Any CPU.Build.0 = Release|Any CPU - {412EC8AA-7F9B-4A54-A478-0524B791A208}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {412EC8AA-7F9B-4A54-A478-0524B791A208}.Debug|Any CPU.Build.0 = Debug|Any CPU - {412EC8AA-7F9B-4A54-A478-0524B791A208}.Release|Any CPU.ActiveCfg = Release|Any CPU - {412EC8AA-7F9B-4A54-A478-0524B791A208}.Release|Any CPU.Build.0 = Release|Any CPU - {D3BF809F-4878-470C-9897-D6C58D7068A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D3BF809F-4878-470C-9897-D6C58D7068A9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D3BF809F-4878-470C-9897-D6C58D7068A9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D3BF809F-4878-470C-9897-D6C58D7068A9}.Release|Any CPU.Build.0 = Release|Any CPU - {A88CF6DB-68BC-4CDF-8151-7C5981A25FB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A88CF6DB-68BC-4CDF-8151-7C5981A25FB1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A88CF6DB-68BC-4CDF-8151-7C5981A25FB1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A88CF6DB-68BC-4CDF-8151-7C5981A25FB1}.Release|Any CPU.Build.0 = Release|Any CPU - {23B13825-06BE-462A-A41D-CD5E26189BE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {23B13825-06BE-462A-A41D-CD5E26189BE0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {23B13825-06BE-462A-A41D-CD5E26189BE0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {23B13825-06BE-462A-A41D-CD5E26189BE0}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {255A7B8C-A679-4A8F-A049-EA39EC0C248F} = {96BBB21A-F476-4234-97AF-D79F88CE0644} - {88C7D33F-6100-46C7-8941-B97C5E769A10} = {D2B8AC38-3B81-453F-A253-6D410D860D6C} - {C143CDD6-2A5C-4D7E-89B2-76676A6FA70D} = {96BBB21A-F476-4234-97AF-D79F88CE0644} - {D0545B95-3C8C-4E5F-B1ED-8AF3372BEDA3} = {96BBB21A-F476-4234-97AF-D79F88CE0644} - {EC3134B3-F85E-4331-93B2-ABE5829FFF8A} = {96BBB21A-F476-4234-97AF-D79F88CE0644} - {1F8A9A65-94CB-482E-B051-D89307A6A042} = {D2B8AC38-3B81-453F-A253-6D410D860D6C} - {964E486B-07D7-4771-9330-3BB3D662A8C1} = {96BBB21A-F476-4234-97AF-D79F88CE0644} - {08AEFD73-58E0-4EF7-A429-BE9939844D97} = {D2B8AC38-3B81-453F-A253-6D410D860D6C} - {DB95ECDD-21ED-4180-B8C1-E76BE816D55B} = {96BBB21A-F476-4234-97AF-D79F88CE0644} - {0D39FED5-B843-454D-9CFD-EEC5CB26FC1C} = {96BBB21A-F476-4234-97AF-D79F88CE0644} - {A12BC4A0-DCB8-4FCB-8BFA-ACD0DCD4B568} = {D2B8AC38-3B81-453F-A253-6D410D860D6C} - {9743772D-6AEE-4775-AEAA-C4E58DDCE0E9} = {96BBB21A-F476-4234-97AF-D79F88CE0644} - {374C6DDC-DDA9-450F-A449-AA7FC3985C2F} = {96BBB21A-F476-4234-97AF-D79F88CE0644} - {F6AEDF20-AA0A-4789-B8E9-3399C15EED48} = {96BBB21A-F476-4234-97AF-D79F88CE0644} - {15D08162-340C-4520-97F9-B42665D281E2} = {D2B8AC38-3B81-453F-A253-6D410D860D6C} - {40A4A78F-A784-4F79-9C9F-F065AD64255F} = {96BBB21A-F476-4234-97AF-D79F88CE0644} - {6F3EB5CA-3DC7-4DF5-97DA-2E7C519C011F} = {96BBB21A-F476-4234-97AF-D79F88CE0644} - {8076F7EE-7474-42A6-96E6-60CF266008BA} = {96BBB21A-F476-4234-97AF-D79F88CE0644} - {0E42553E-91C8-46D9-93EF-FA95EBA517C4} = {96BBB21A-F476-4234-97AF-D79F88CE0644} - {E9D11F2B-2694-4240-BFA8-3447FA640A35} = {96BBB21A-F476-4234-97AF-D79F88CE0644} - {9F46450C-0927-42F5-B35E-555E0035D42A} = {96BBB21A-F476-4234-97AF-D79F88CE0644} - {03AE948B-39FF-485C-9B63-38006A5BB20F} = {96BBB21A-F476-4234-97AF-D79F88CE0644} - {D5A0C9EE-C6DA-4A32-A802-EBB5981367C9} = {96BBB21A-F476-4234-97AF-D79F88CE0644} - {7253892F-8AF6-41DC-B2E5-C231E3F8E4BF} = {96BBB21A-F476-4234-97AF-D79F88CE0644} - {876F9DD3-D211-4867-B69E-60BF76946838} = {D2B8AC38-3B81-453F-A253-6D410D860D6C} - {412EC8AA-7F9B-4A54-A478-0524B791A208} = {96BBB21A-F476-4234-97AF-D79F88CE0644} - {D3BF809F-4878-470C-9897-D6C58D7068A9} = {96BBB21A-F476-4234-97AF-D79F88CE0644} - {A88CF6DB-68BC-4CDF-8151-7C5981A25FB1} = {D2B8AC38-3B81-453F-A253-6D410D860D6C} - {23B13825-06BE-462A-A41D-CD5E26189BE0} = {D2B8AC38-3B81-453F-A253-6D410D860D6C} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {8A219ED5-5120-4C5F-8EB4-FFF44FFCE2CF} - EndGlobalSection -EndGlobal diff --git a/RhoMicro.CodeAnalysis.slnx b/RhoMicro.CodeAnalysis.slnx new file mode 100644 index 0000000..3cee523 --- /dev/null +++ b/RhoMicro.CodeAnalysis.slnx @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 218d7e06d11349f3ff020c7dcbec53a9eb1d33e6 Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Thu, 30 Oct 2025 00:05:27 +0100 Subject: [PATCH 07/36] wip --- Directory.Build.props | 4 +- RhoMicro.CodeAnalysis.slnx | 4 +- UnionsGenerator.TestApplication/Output.g.cs | 32 ++ UnionsGenerator.TestApplication/Program.cs | 201 +++++++++- .../UnionsGenerator.TestApplication.csproj | 16 +- UnionsGenerator.TestLibrary/Foo.cs | 0 UnionsGenerator.TestLibrary/IUnion.cs | 2 + UnionsGenerator.TestLibrary/IUnionFactory.cs | 2 + UnionsGenerator.TestLibrary/IntDouble.cs | 1 + .../IntDoubleString.cs | 356 ++++++++++++++++-- UnionsGenerator/requirements.md | 94 +---- dist/dev/IMPORTANT_README_IMPORTANT.txt | 2 - ...Analysis.UtilityGenerators.Dev.1.0.0.nupkg | Bin 166416 -> 0 bytes global.json | 5 +- 14 files changed, 573 insertions(+), 146 deletions(-) create mode 100644 UnionsGenerator.TestApplication/Output.g.cs create mode 100644 UnionsGenerator.TestLibrary/Foo.cs delete mode 100644 dist/dev/IMPORTANT_README_IMPORTANT.txt delete mode 100644 dist/dev/RhoMicro.CodeAnalysis.UtilityGenerators.Dev.1.0.0.nupkg diff --git a/Directory.Build.props b/Directory.Build.props index eba16d4..2fe2bc3 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -3,7 +3,7 @@ true All true - latest + preview enable enable True @@ -17,7 +17,7 @@ README.md MPL-2.0 Paul Braetz - 2024 + $([System.DateTime]::Now.ToString('yyyy')) RhoMicro $(SolutionName).$(MSBuildProjectName) https://github.com/SleepWellPupper/RhoMicro.CodeAnalysis/tree/master/$(MSBuildProjectName) diff --git a/RhoMicro.CodeAnalysis.slnx b/RhoMicro.CodeAnalysis.slnx index 3cee523..027ae17 100644 --- a/RhoMicro.CodeAnalysis.slnx +++ b/RhoMicro.CodeAnalysis.slnx @@ -1,5 +1,6 @@ + @@ -19,6 +20,7 @@ + @@ -40,4 +42,4 @@ - + \ No newline at end of file diff --git a/UnionsGenerator.TestApplication/Output.g.cs b/UnionsGenerator.TestApplication/Output.g.cs new file mode 100644 index 0000000..b74d2b4 --- /dev/null +++ b/UnionsGenerator.TestApplication/Output.g.cs @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MPL-2.0 +// +// This file was generated using the global::RhoMicro.CodeAnalysis.Lyra.CSharpSourceBuilder. +// +namespace SampleNamespace +{ + partial class IntDoubleString + { + [global::System.FlagsAttribute] + public enum VariantGroups + { + None = 0, + Integer = 1 << 0, + Number = 1 << 1, + ReferenceType = 1 << 2, + ValueType = 1 << 3 + } + } +} +namespace SampleNamespace +{ + partial class IntDoubleString + { + [global::System.FlagsAttribute] + public enum VariantGroups + { + Double = 0, + Int32 = 1, + String = 2 + } + } +} diff --git a/UnionsGenerator.TestApplication/Program.cs b/UnionsGenerator.TestApplication/Program.cs index 3247f00..6d2a85a 100644 --- a/UnionsGenerator.TestApplication/Program.cs +++ b/UnionsGenerator.TestApplication/Program.cs @@ -1,14 +1,191 @@ // See https://aka.ms/new-console-template for more information -var ids = IntDoubleString.Create(32); -Console.WriteLine(ids.Variant); -Console.WriteLine(ids.Value); -var id = IntDouble.Create(ids); -Console.WriteLine(id.Variant); -Console.WriteLine(id.Value); -id = 42d; -Console.WriteLine(id.Variant); -Console.WriteLine(id.Value); -ids = "Foo"; -Console.WriteLine(ids.Variant); -Console.WriteLine(ids.Value); +using System.Collections.Immutable; +using System.ComponentModel; +using System.Net.Http.Headers; +using System.Runtime.InteropServices; +using RhoMicro.CodeAnalysis; +using RhoMicro.CodeAnalysis.Lyra; +using static RhoMicro.CodeAnalysis.Lyra.ComponentFactory; +using Type = System.Type; + +// var ids = IntDoubleString.Create(32); +// Console.WriteLine(ids.Variant); +// Console.WriteLine(ids.Value); +// var id = IntDouble.Create(ids); +// Console.WriteLine(id.Variant); +// Console.WriteLine(id.Value); +// id = 42d; +// Console.WriteLine(id.Variant); +// Console.WriteLine(id.Value); +// ids = "Foo"; +// Console.WriteLine(ids.Variant); +// Console.WriteLine(ids.Value); + +using var sb1 = new CSharpSourceBuilder( + new CSharpSourceBuilderOptions() + { + Prelude = (b,_)=>b.AppendLine("// "), + DefaultIndentation = " ".AsMemory() + }); +sb1.Append("namespace ").AppendLine("Foo").AppendLine('{'); +sb1.Indent(); +sb1.Append( + ComponentFactory.Type( + "enum", + "Bar", + ComponentFactory.Create((b, _) => b.AppendLine("Baz = 0")))); +sb1.Detent(); +sb1.AppendLine('}'); +Console.WriteLine(sb1); + +var placeholder = "\nbar\nbaz"; +sb1.Clear().AppendLine( + $""" + foo{placeholder} + """); +Console.WriteLine(sb1); + + +var variants = new VariantModel[3] +{ + new("Double", 0, new VariantGroupModel[2]), new("Int32", 1, new VariantGroupModel[3]), + new("String", 2, new VariantGroupModel[1]) +}; +var groups = new VariantGroupModel[5] +{ + new("None", 0, []), new("Integer", 1, [variants[1]]), new("Number", 2, [variants[0], variants[1]]), + new("ReferenceType", 3, [variants[2]]), new("ValueType", 4, [variants[0], variants[1]]) +}; + +variants[0].Groups![0] = groups[2]; +variants[0].Groups![1] = groups[4]; + +variants[1].Groups![0] = groups[1]; +variants[1].Groups![1] = groups[2]; +variants[1].Groups![2] = groups[4]; + +variants[2].Groups![0] = groups[3]; + + +var model = new UnionModel( + "class", + "IntDoubleString", + "SampleNamespace", + [new ContainingTypeModel("class", "ContainingType")], + ImmutableCollectionsMarshal.AsImmutableArray(variants), + ImmutableCollectionsMarshal.AsImmutableArray(groups)); + +using var sb = new CSharpSourceBuilder(new CSharpSourceBuilderOptions() +{ + Prelude = (b, _) => b + .SetInterpolationIndentationDetector( + NullInterpolationIndentationDetector.Instance, + out var previous) + .AppendLine( + $""" + // SPDX-License-Identifier: MPL-2.0 + // + // This file was generated using the {typeof(CSharpSourceBuilder)}. + // + """) + .SetInterpolationIndentationDetector(previous) +}); + + +sb.Append(new VariantGroupKindsComponent(model)); +sb.Append(new VariantKindsComponent(model)); + +File.WriteAllText(Path.Combine(Environment.CurrentDirectory, "../../../", "Output.g.cs"), sb.ToString()); + +readonly record struct VariantGroupKindsComponent(UnionModel Model) : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var component = Namespace( + Model.Namespace, + Type( + $"partial {Model.Modifier}", + name: Model.Name, + body: Type( + modifiers: "public enum", + name: "VariantGroups", + attributes: + [ + Attribute(TypeName(typeof(FlagsAttribute)), arguments: []) + ], + body: List( + Model.VariantGroups, + static (v, i, l, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + if (i is 0) + { + b.Append($"{v.Name} = 0"); + } + else + { + b.Append($"{v.Name} = 1 << {i - 1}"); + } + }, + separator: ",\n", + terminator: "\n")))); + + builder.Append(component); + } +} + +readonly record struct VariantKindsComponent(UnionModel Model) : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var component = Namespace( + Model.Namespace, + Type( + modifiers: $"partial {Model.Modifier}", + name: Model.Name, + body: Type( + modifiers: "public enum", + name: "VariantGroups", + attributes: + [ + Attribute(TypeName(typeof(FlagsAttribute)), arguments: []) + ], + body: List( + Model.Variants, + static (v, i, l, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"{v.Name} = {i}"); + }, + separator: ",\n", + terminator: "\n")))); + + builder.Append(component); + } +} + + +readonly record struct VariantModel(String Name, Int32 Index, VariantGroupModel[] Groups); + +readonly record struct VariantGroupModel(String Name, Int32 Index, VariantModel[] Variants); + +readonly record struct ContainingTypeModel(String Modifier, String Name); + +sealed record UnionModel( + String Modifier, + String Name, + String Namespace, + ImmutableArray ContainingTypes, + ImmutableArray Variants, + ImmutableArray VariantGroups); + +// ############### +// generated below +// ############### diff --git a/UnionsGenerator.TestApplication/UnionsGenerator.TestApplication.csproj b/UnionsGenerator.TestApplication/UnionsGenerator.TestApplication.csproj index ef1de10..38a21a2 100644 --- a/UnionsGenerator.TestApplication/UnionsGenerator.TestApplication.csproj +++ b/UnionsGenerator.TestApplication/UnionsGenerator.TestApplication.csproj @@ -2,16 +2,28 @@ Exe - net9.0 + net8.0 preview enable enable false + - + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/UnionsGenerator.TestLibrary/Foo.cs b/UnionsGenerator.TestLibrary/Foo.cs new file mode 100644 index 0000000..e69de29 diff --git a/UnionsGenerator.TestLibrary/IUnion.cs b/UnionsGenerator.TestLibrary/IUnion.cs index b45c69e..7e1b66d 100644 --- a/UnionsGenerator.TestLibrary/IUnion.cs +++ b/UnionsGenerator.TestLibrary/IUnion.cs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 +namespace RhoMicro.CodeAnalysis; + using System.Diagnostics.CodeAnalysis; public interface IUnion diff --git a/UnionsGenerator.TestLibrary/IUnionFactory.cs b/UnionsGenerator.TestLibrary/IUnionFactory.cs index 94391ae..cb090a4 100644 --- a/UnionsGenerator.TestLibrary/IUnionFactory.cs +++ b/UnionsGenerator.TestLibrary/IUnionFactory.cs @@ -1,5 +1,7 @@ // SPDX-License-Identifier: MPL-2.0 +namespace RhoMicro.CodeAnalysis; + using System.Diagnostics.CodeAnalysis; public interface IUnionFactory diff --git a/UnionsGenerator.TestLibrary/IntDouble.cs b/UnionsGenerator.TestLibrary/IntDouble.cs index 98a7986..1e8f755 100644 --- a/UnionsGenerator.TestLibrary/IntDouble.cs +++ b/UnionsGenerator.TestLibrary/IntDouble.cs @@ -6,6 +6,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; +using RhoMicro.CodeAnalysis; public partial class IntDouble : IUnion, IEquatable { diff --git a/UnionsGenerator.TestLibrary/IntDoubleString.cs b/UnionsGenerator.TestLibrary/IntDoubleString.cs index e19ea55..200ab73 100644 --- a/UnionsGenerator.TestLibrary/IntDoubleString.cs +++ b/UnionsGenerator.TestLibrary/IntDoubleString.cs @@ -1,13 +1,13 @@ // SPDX-License-Identifier: MPL-2.0 using System.Collections.Immutable; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; +using RhoMicro.CodeAnalysis; -public partial class IntDoubleString : IUnion, IEquatable +public partial class IntDoubleString : IUnion, IEquatable, IComparable { #region VariantGroupKinds @@ -18,7 +18,7 @@ public enum VariantGroupKinds : byte Number = 1 << 0, Integer = 1 << 1, ReferenceType = 1 << 2, - ValueType = 1 << 3 + ValueType = 1 << 3, } #endregion @@ -38,7 +38,8 @@ public enum VariantKind : byte readonly struct Factory : IUnionFactory { - public Boolean TryCreate(TVariant value, [NotNullWhen(true)] out IntDoubleString? union) + public Boolean TryCreate(TVariant value, + [NotNullWhen(true)] out IntDoubleString? union) { switch (value) { @@ -53,8 +54,7 @@ public Boolean TryCreate(TVariant value, [NotNullWhen(true)] out IntDo return true; // TODO: detect/register related unions, match here (before IUnion box) - // attempt to convert to this union through double dispatch, this will - // effectively convert between related unions, albeit while incurring boxing + // attempt to convert to this union through double dispatch case IUnion v: return v.TryMapTo(this, out union); // switch against nullable variants here /* @@ -98,8 +98,7 @@ public IntDoubleString Create(TVariant value) case IntDoubleString v: return new IntDoubleString(v); // TODO: detect/register related unions, match here (before IUnion box) - // attempt to convert to this union through double dispatch, this will - // effectively convert between related unions, albeit while incurring boxing + // attempt to convert to this union through double dispatch case IUnion v: return v.MapTo(this); case null: // switch against nullable variants here @@ -250,8 +249,7 @@ public Object Value VariantKind.Int32 => __unmanagedVariantsContainer.Int32, VariantKind.Double => __unmanagedVariantsContainer.Double, VariantKind.String => __referenceVariantsContainer, - _ => throw new InvalidOperationException( - $"Unable to determine the variant of this union, as '{nameof(Variant)}' is not representing a valid variant of this union: '{Variant}'. This could be either because the union itself was not initialized correctly, or due to a bug in the 'UnionsGenerator' that generated this union type. Please report an issue to the maintainer.") + _ => throw CreateUnknownVariantException() }; return result; @@ -263,16 +261,14 @@ public Object Value public Int32 ToInt32 => IsInt32 ? AsInt32 - : throw new InvalidOperationException( - $"Unable to convert union to 'Int32', as it is currently representing the '{Variant}' variant."); + : throw CreateInvalidCastException(VariantKind.Int32); public Boolean IsDouble => Variant is VariantKind.Double; public Double AsDouble => __unmanagedVariantsContainer.Double; public Double ToDouble => IsDouble ? AsDouble - : throw new InvalidOperationException( - $"Unable to convert union to 'Double', as it is currently representing the '{Variant}' variant."); + : throw CreateInvalidCastException(VariantKind.Double); // MNNW for non-nullable reference variants [MemberNotNullWhen(true, nameof(AsString))] @@ -282,12 +278,234 @@ public Object Value public String ToString => IsString ? AsString - : throw new InvalidOperationException( - $"Unable to convert union to 'String', as it is currently representing the '{Variant}' variant."); + : throw CreateInvalidCastException(VariantKind.String); #endregion -#region Is/As Methods +#region Switch + + public void Switch( + Action onInt32, + Action onDouble, + Action onString) + { + switch (Variant) + { + case VariantKind.Int32: + onInt32.Invoke(__unmanagedVariantsContainer.Int32); + break; + case VariantKind.Double: + onDouble.Invoke(__unmanagedVariantsContainer.Double); + break; + case VariantKind.String: + onString.Invoke((string)__referenceVariantsContainer); + break; + } + } + + public void Switch( + TState state, + Action onInt32, + Action onDouble, + Action onString) + { + switch (Variant) + { + case VariantKind.Int32: + onInt32.Invoke(__unmanagedVariantsContainer.Int32, state); + break; + case VariantKind.Double: + onDouble.Invoke(__unmanagedVariantsContainer.Double, state); + break; + case VariantKind.String: + onString.Invoke((string)__referenceVariantsContainer, state); + break; + } + } + + public void Switch( + Action defaultHandler, + Action? onInt32 = null, + Action? onDouble = null, + Action? onString = null) + { + switch (Variant) + { + case VariantKind.Int32: + onInt32?.Invoke(__unmanagedVariantsContainer.Int32); + break; + case VariantKind.Double: + onDouble?.Invoke(__unmanagedVariantsContainer.Double); + break; + case VariantKind.String: + onString?.Invoke((string)__referenceVariantsContainer); + break; + } + } + + public void Switch( + TState state, + Action defaultHandler, + Action? onInt32 = null, + Action? onDouble = null, + Action? onString = null) + { + switch (Variant) + { + case VariantKind.Int32: + onInt32?.Invoke(__unmanagedVariantsContainer.Int32, state); + break; + case VariantKind.Double: + onDouble?.Invoke(__unmanagedVariantsContainer.Double, state); + break; + case VariantKind.String: + onString?.Invoke((string)__referenceVariantsContainer, state); + break; + } + } + + public TResult Switch( + Func onInt32, + Func onDouble, + Func onString) + { + switch (Variant) + { + case VariantKind.Int32: + return onInt32.Invoke(__unmanagedVariantsContainer.Int32); + case VariantKind.Double: + return onDouble.Invoke(__unmanagedVariantsContainer.Double); + case VariantKind.String: + return onString.Invoke((string)__referenceVariantsContainer); + default: + throw CreateUnknownVariantException(); + } + } + + public TResult Switch( + Func defaultHandler, + Func? onInt32 = null, + Func? onDouble = null, + Func? onString = null) + { + switch (Variant) + { + case VariantKind.Int32: + return onInt32 is not null + ? onInt32.Invoke(__unmanagedVariantsContainer.Int32) + : defaultHandler.Invoke(); + case VariantKind.Double: + return onDouble is not null + ? onDouble.Invoke(__unmanagedVariantsContainer.Double) + : defaultHandler.Invoke(); + case VariantKind.String: + return onString is not null + ? onString.Invoke((string)__referenceVariantsContainer) + : defaultHandler.Invoke(); + default: + throw CreateUnknownVariantException(); + } + } + + public TResult Switch( + TResult defaultResult, + Func? onInt32 = null, + Func? onDouble = null, + Func? onString = null) + { + switch (Variant) + { + case VariantKind.Int32: + return onInt32 is not null + ? onInt32.Invoke(__unmanagedVariantsContainer.Int32) + : defaultResult; + case VariantKind.Double: + return onDouble is not null + ? onDouble.Invoke(__unmanagedVariantsContainer.Double) + : defaultResult; + case VariantKind.String: + return onString is not null + ? onString.Invoke((string)__referenceVariantsContainer) + : defaultResult; + default: + throw CreateUnknownVariantException(); + } + } + + public TResult Switch( + TState state, + Func onInt32, + Func onDouble, + Func onString) + { + switch (Variant) + { + case VariantKind.Int32: + return onInt32.Invoke(__unmanagedVariantsContainer.Int32, state); + case VariantKind.Double: + return onDouble.Invoke(__unmanagedVariantsContainer.Double, state); + case VariantKind.String: + return onString.Invoke((string)__referenceVariantsContainer, state); + default: + throw CreateUnknownVariantException(); + } + } + + public TResult Switch( + TState state, + Func defaultHandler, + Func? onInt32 = null, + Func? onDouble = null, + Func? onString = null) + { + switch (Variant) + { + case VariantKind.Int32: + return onInt32 is not null + ? onInt32.Invoke(__unmanagedVariantsContainer.Int32, state) + : defaultHandler.Invoke(state); + case VariantKind.Double: + return onDouble is not null + ? onDouble.Invoke(__unmanagedVariantsContainer.Double, state) + : defaultHandler.Invoke(state); + case VariantKind.String: + return onString is not null + ? onString.Invoke((string)__referenceVariantsContainer, state) + : defaultHandler.Invoke(state); + default: + throw CreateUnknownVariantException(); + } + } + + public TResult Switch( + TState state, + TResult defaultResult, + Func? onInt32 = null, + Func? onDouble = null, + Func? onString = null) + { + switch (Variant) + { + case VariantKind.Int32: + return onInt32 is not null + ? onInt32.Invoke(__unmanagedVariantsContainer.Int32, state) + : defaultResult; + case VariantKind.Double: + return onDouble is not null + ? onDouble.Invoke(__unmanagedVariantsContainer.Double, state) + : defaultResult; + case VariantKind.String: + return onString is not null + ? onString.Invoke((string)__referenceVariantsContainer, state) + : defaultResult; + default: + throw CreateUnknownVariantException(); + } + } + +#endregion + +#region Inspection // warn that value is guaranteed to be null for class TVariant and false return // attach [NNW(true)] and TVariant? when not representing any nullable reference variants @@ -386,26 +604,52 @@ public Boolean Is() #region Validation - static partial void Validate(Int32 value, Boolean throwIfInvalid, ref Boolean isValid); - static partial void Validate(Double value, Boolean throwIfInvalid, ref Boolean isValid); - static partial void Validate(String value, Boolean throwIfInvalid, ref Boolean isValid); + static partial void Validate( + Int32 value, + Boolean throwIfInvalid, + ref Boolean isValid); + + static partial void Validate( + Double value, + Boolean throwIfInvalid, + ref Boolean isValid); + + static partial void Validate( + String value, + Boolean throwIfInvalid, + ref Boolean isValid); + + private InvalidCastException CreateInvalidCastException( + VariantKind variant) + => new InvalidCastException( + $"Unable to convert union to '{variant.Name}', as it is currently representing the '{Variant}' variant."); + + private InvalidOperationException CreateUnknownVariantException() + => new InvalidOperationException( + $"Unable to determine the variant of this union, as '{nameof(Variant)}' is not representing a valid variant of this union: '{Variant}'. This could be either because the union itself was not initialized correctly, or due to a bug in the 'UnionsGenerator' that generated this union type. Please report an issue to the maintainer."); #endregion #region Factories - public static IntDoubleString Create(IntDoubleString value) => new(value); + public static IntDoubleString Create( + IntDoubleString value) => new(value); - public static Boolean TryCreate(IntDoubleString value, [NotNullWhen(true)] out IntDoubleString? union) + public static Boolean TryCreate( + IntDoubleString value, + [NotNullWhen(true)] out IntDoubleString? union) { union = Create(value); return true; } - public static IntDoubleString Create(Int32 value) => new IntDoubleString(value, validate: true); + public static IntDoubleString Create( + Int32 value) => new IntDoubleString(value, validate: true); - public static Boolean TryCreate(Int32 value, [NotNullWhen(true)] out IntDoubleString? union) + public static Boolean TryCreate( + Int32 value, + [NotNullWhen(true)] out IntDoubleString? union) { var isValid = true; Validate(value, throwIfInvalid: false, ref isValid); @@ -416,9 +660,12 @@ public static Boolean TryCreate(Int32 value, [NotNullWhen(true)] out IntDoubleSt return isValid; } - public static IntDoubleString Create(Double value) => new IntDoubleString(value, validate: true); + public static IntDoubleString Create( + Double value) => new IntDoubleString(value, validate: true); - public static Boolean TryCreate(Double value, [NotNullWhen(true)] out IntDoubleString? union) + public static Boolean TryCreate( + Double value, + [NotNullWhen(true)] out IntDoubleString? union) { var isValid = true; Validate(value, throwIfInvalid: false, ref isValid); @@ -429,9 +676,12 @@ public static Boolean TryCreate(Double value, [NotNullWhen(true)] out IntDoubleS return isValid; } - public static IntDoubleString Create(String value) => new IntDoubleString(value, validate: true); + public static IntDoubleString Create( + String value) => new IntDoubleString(value, validate: true); - public static Boolean TryCreate(String value, [NotNullWhen(true)] out IntDoubleString? union) + public static Boolean TryCreate( + String value, + [NotNullWhen(true)] out IntDoubleString? union) { var isValid = true; Validate(value, throwIfInvalid: false, ref isValid); @@ -442,10 +692,13 @@ public static Boolean TryCreate(String value, [NotNullWhen(true)] out IntDoubleS return isValid; } - public static IntDoubleString Create(T value) + public static IntDoubleString Create( + T value) => new Factory().Create(value); - public static Boolean TryCreate(T value, [NotNullWhen(true)] out IntDoubleString? union) + public static Boolean TryCreate( + T value, + [NotNullWhen(true)] out IntDoubleString? union) => new Factory().TryCreate(value, out union); #endregion @@ -462,8 +715,7 @@ public TUnion MapTo( VariantKind.Int32 => factory.Create(__unmanagedVariantsContainer.Int32), VariantKind.Double => factory.Create(__unmanagedVariantsContainer.Double), VariantKind.String => factory.Create((String)__referenceVariantsContainer), - _ => throw new InvalidOperationException( - $"Unable to determine the variant of this union, as '{nameof(Variant)}' is not representing a valid variant of this union: '{Variant}'. This could be either because the union itself was not initialized correctly, or due to a bug in the 'UnionsGenerator' that generated this union type. Please report an issue to the maintainer.") + _ => throw CreateUnknownVariantException() }; return result; @@ -480,8 +732,7 @@ public Boolean TryMapTo( VariantKind.Int32 => factory.TryCreate(__unmanagedVariantsContainer.Int32, out union), VariantKind.Double => factory.TryCreate(__unmanagedVariantsContainer.Double, out union), VariantKind.String => factory.TryCreate((String)__referenceVariantsContainer, out union), - _ => throw new InvalidOperationException( - $"Unable to determine the variant of this union, as '{nameof(Variant)}' is not representing a valid variant of this union: '{Variant}'. This could be either because the union itself was not initialized correctly, or due to a bug in the 'UnionsGenerator' that generated this union type. Please report an issue to the maintainer.") + _ => throw CreateUnknownVariantException() }; return result; @@ -491,9 +742,12 @@ public Boolean TryMapTo( #region Equality - public override Boolean Equals(Object? obj) => obj is IntDoubleString union && Equals(union); + public override Boolean Equals( + Object? obj) + => obj is IntDoubleString union && Equals(union); - public Boolean Equals(IntDoubleString other) + public Boolean Equals( + IntDoubleString other) { // only emit for reference unions if (ReferenceEquals(this, other)) @@ -520,8 +774,7 @@ public Boolean Equals(IntDoubleString other) EqualityComparer.Default.Equals( (String)__referenceVariantsContainer, (String)other.__referenceVariantsContainer), - _ => throw new InvalidOperationException( - $"Unable to determine the variant of this union, as '{nameof(Variant)}' is not representing a valid variant of this union: '{Variant}'. This could be either because the union itself was not initialized correctly, or due to a bug in the 'UnionsGenerator' that generated this union type. Please report an issue to the maintainer.") + _ => throw CreateUnknownVariantException() }; return result; @@ -534,8 +787,7 @@ public override Int32 GetHashCode() VariantKind.Int32 => HashCode.Combine(Variant, __unmanagedVariantsContainer.Int32), VariantKind.Double => HashCode.Combine(__unmanagedVariantsContainer.Double), VariantKind.String => HashCode.Combine((String)__referenceVariantsContainer), - _ => throw new InvalidOperationException( - $"Unable to determine the variant of this union, as '{nameof(Variant)}' is not representing a valid variant of this union: '{Variant}'. This could be either because the union itself was not initialized correctly, or due to a bug in the 'UnionsGenerator' that generated this union type. Please report an issue to the maintainer.") + _ => throw CreateUnknownVariantException() }; return result; @@ -554,6 +806,30 @@ public override Int32 GetHashCode() public static implicit operator IntDoubleString(String value) => Create(value); public static explicit operator String(IntDoubleString union) => union.ToString; +#endregion + +#region Interface Implementations + + // + // track required names signatures of all union members and implement conflicting interface + // members explicitly? + // alternatively, explicitly implement all interface members to avoid that complexity? + Int32 IComparable.CompareTo(object obj) + { + switch (Variant) + { + case VariantKind.Int32: + // check if variant is implementing interface explicitly and cast when needed + return __unmanagedVariantsContainer.Int32.CompareTo(obj); + case VariantKind.Double: + return __unmanagedVariantsContainer.Double.CompareTo(obj); + case VariantKind.String: + return ((string)__referenceVariantsContainer).CompareTo(obj); + default: + throw CreateUnknownVariantException(); + } + } + #endregion } diff --git a/UnionsGenerator/requirements.md b/UnionsGenerator/requirements.md index e57cf26..13ec4e1 100644 --- a/UnionsGenerator/requirements.md +++ b/UnionsGenerator/requirements.md @@ -24,10 +24,10 @@ The term `adapter` refers to the type adapting the union onto interfaces impleme | 4 | An error is issued for `ref` like variants. | | × | | 4 | An error is issued for `record` unions. | | × | | 5 | A warning is issued for non-nullable reference variant on struct union. | | × | -| 6 | An error is issued if more than 31 variant groups are defined. This is to allow groups to be modelled using an int backed [Flags] enum. | | × | +| 6 | An error is issued if more than 63 variant groups are defined. This is to allow groups to be modelled using a [Flags] enum. | | × | | 7 | Variant group names have diagnostics mapped onto their definition in the attribute usage. | | × | | 8 | Variant names have diagnostics mapped onto their definition in the attribute usage. | | × | -| 9 | An error is issued if more than 255 variants are defined. This is to allow variants to be modelled using a byte backed enum. | | × | +| 9 | The backing type of the `VariantKind` is chosen as the smallest integral datatype able to accommodate all variants. | | × | | 10 | An option is provided to box managed struct variants. | | × | | 11 | Managed struct variants are stored in dedicated fields by default. | | × | | 12 | An error is issued for static variants. | | × | @@ -38,11 +38,11 @@ The term `adapter` refers to the type adapting the union onto interfaces impleme | 17 | If multiple variants are defined, an explicit conversion to each variant is defined. | | × | | 18 | If a validation method is implemented, then an explicit conversion from the validated variant is defined. | | × | | 19 | If a validation method is not implemented, then an implicit conversion from the variant is defined. | | × | -| 20 | | | × | -| 21 | | | × | -| 22 | | | × | -| 23 | | | × | -| 24 | | | × | +| 20 | Documentation comments are emitted for every generated member. | | × | +| 21 | Parameter lists are wrapped and indented. | | × | +| 22 | The backing type of the `VariantGroupKinds` is chosen as the smallest integral datatype able to accommodate all variants groups. | | × | +| 23 | `Union` methods are grouped by the variant they are specific to. | | × | +| 24 | Variant group names are ordered alphabetically, except for the default group `None`, which must always be the first element. | | × | | 25 | | | × | | 26 | | | × | | 27 | | | × | @@ -50,88 +50,12 @@ The term `adapter` refers to the type adapting the union onto interfaces impleme | 29 | | | × | | 30 | | | × | -## APIs - -### `Union` - -| id | signature | description | conditions | issues | met (×/✓) | -|----|--------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|---------------|--------|-----------| -| 1 | `public T Switch(Func, Func)` | projects the value onto `T`, cases are obligatory | | | × | -| 2 | `public T Switch(Func, Func? = null, Func? = null)` | projects the value onto `T`, cases are optional, default case is obligatory | \> 1 variants | | × | -| 3 | `public T Switch(T, Func? = null, Func? = null)` | projects the value onto `T`, cases are optional, default projection is obligatory | \> 1 variants | | × | -| 4 | `public T Switch(TState, Func, Func)` | projects the value onto `T`, cases are obligatory, state is obligatory | | | × | -| 5 | `public T Switch(TState, Func, Func? = null, Func? = null)` | projects the value onto `T`, cases are optional, default case is obligatory, state is obligatory | \> 1 variants | | × | -| 6 | `public T Switch(TState, T, Func? = null, Func? = null)` | projects the value onto `T`, cases are optional, default projection is obligatory, state is obligatory | \> 1 variants | | × | -| 7 | `public void Switch(Action, Action)` | handles the value, cases are obligatory | | | × | -| 8 | `public void Switch(Action, Action? = null, Action? = null)` | handles the value, cases are optional, default case is obligatory | \> 1 variants | | × | -| 9 | `public void Switch(TState, Action, Action)` | handles the value, cases are obligatory, state is obligatory | | | × | -| 10 | `public void Switch(TState, Action , Action?, Action?)` | handles the value, cases are optional, default case is obligatory, state is obligatory | \> 1 variants | | × | -| 11 | `public Union Create(TValue)` | wraps a value in a union, is polymorphically sensitive (allows for variant instances typed to their superclass), accepts `Union` instances | | #153 | × | -| 12 | `public bool TryCreate(TValue, out Union union)` | wraps a value in a union, is polymorphically sensitive (allows for variant instances typed to their superclass), accepts `Union` instances | | #153 | × | -| 13 | `public object Value { get; }` | gets the underlying value, boxing it if it is a value type | | #146 | × | -| 14 | `public Union.Variant Variant { get; }` | gets the variant | | | × | -| 15 | | | | | × | -| 16 | | | | | × | -| 17 | | | | | × | -| 18 | | | | | × | -| 19 | | | | | × | -| 20 | | | | | × | -| 21 | | | | | × | -| 22 | | | | | × | -| 23 | | | | | × | -| 24 | | | | | × | -| 25 | | | | | × | -| 26 | | | | | × | -| 27 | | | | | × | -| 28 | | | | | × | -| 29 | | | | | × | -| 30 | | | | | × | - -### `Union.Variant` - -| id | signature | description | conditions | issues | met (×/✓) | -|----|--------------------------------------------------------|--------------------------------------------------------|------------|--------|-----------| -| 1 | `public readonly struct Variant` | represents a variant and enumerates all valid variants | | | × | -| 2 | `public static Variant None { get; }` | default value, used for detecting uninitialized unions | | | × | -| 3 | `public static Variant Variant0 { get; }` | enumeration of variants | | | × | -| 4 | `public static ImmutableArray GetAllValues()` | gets an array containing all available variant groups | | | × | -| 5 | `public string Name { get; }` | gets the name of the variant | | | × | -| 6 | `public Type Type { get; }` | gets the `System.Type` of the variant | | | × | -| 7 | `public VariantGroups Groups { get; }` | gets the variant groups containing this variant | | | × | -| 8 | `public bool IsDefault { get; }` | indicates whether the variant is equal to `None` | | | × | -| 9 | | | | | × | -| 10 | | | | | × | - -### `Union.VariantGroups` - -| id | signature | description | conditions | issues | met (×/✓) | -|----|-------------------------------------------------------------------------|-------------------------------------------------------------------------|------------|--------|-----------| -| 1 | `public readonly struct VariantGroups` | represents a variant group and enumerates all valid variant groups | | | × | -| 2 | `public static VariantGroups None { get; }` | default group used for unassociated variants | | | × | -| 3 | `public static VariantGroups VariantGroup0 { get; }` | enumeration of variant groups | | | × | -| 4 | `public static ImmutableArray GetAllValues()` | gets an array containing all available variant groups | | | × | -| 5 | `public bool Contains(VariantGroups groups)` | indicates whether groups is contained in the current group | | | × | -| 6 | `public bool IsDefault { get; }` | indicates whether the variant group is equal to `None` | | | × | -| 7 | `public static VariantGroups operator \|(VariantGroups, VariantGroups)` | bitwise or operator for combining variant groups | | | × | -| 8 | `public int IndividualGroupsCount { get; }` | gets the amount of individual groups combined in this group | | | × | -| 9 | `public void GetIndividualGroups(Span buffer)` | populates a buffer with the individual groups combined in this group | | | × | -| 10 | `public ImmutableArray GetIndividualGroups()` | gets an array containing the individual groups combined in this group | | | × | -| 11 | `public string Name { get; }` | gets the name of or combined names of the groups combined in this group | | | × | -| 12 | | | | | × | -| 13 | | | | | × | -| 14 | | | | | × | -| 15 | | | | | × | -| 16 | | | | | × | -| 17 | | | | | × | -| 18 | | | | | × | -| 19 | | | | | × | -| 20 | | | | | × | - ## Notes ### TODO -- Relations +- relations +- json support ### Table Template diff --git a/dist/dev/IMPORTANT_README_IMPORTANT.txt b/dist/dev/IMPORTANT_README_IMPORTANT.txt deleted file mode 100644 index 3ed96aa..0000000 --- a/dist/dev/IMPORTANT_README_IMPORTANT.txt +++ /dev/null @@ -1,2 +0,0 @@ -DO NOT DELETE THESE PACKAGES. -THEY ARE REQUIRED FOR BOOTSTRAPPING THE GENERATORS BUILDS. \ No newline at end of file diff --git a/dist/dev/RhoMicro.CodeAnalysis.UtilityGenerators.Dev.1.0.0.nupkg b/dist/dev/RhoMicro.CodeAnalysis.UtilityGenerators.Dev.1.0.0.nupkg deleted file mode 100644 index d2a05ac553187bdfecfda9951b812cd7d260b98d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 166416 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%0DX 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 a|7S;Kxo^<_Ap!mMf`2vdYvfk|_P+pnBITa| diff --git a/global.json b/global.json index 7da2763..a11f48e 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,7 @@ { "sdk": { - "version": "8.0.100", - "rollForward": "latestMajor" + "version": "10.0.0", + "rollForward": "latestMajor", + "allowPrerelease": true } } \ No newline at end of file From 9efa8a70f82aff7010ac0a4a70d579b930bfe634 Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Thu, 30 Oct 2025 00:06:15 +0100 Subject: [PATCH 08/36] add lyra --- ...CSharpSourceBuilder.TestApplication.csproj | 15 + .../Program.cs | 7 + ...gregateInterpolationIndentationDetector.cs | 44 ++ Lyra/AttributeArgumentComponent.cs | 191 +++++ Lyra/AttributeComponent.cs | 90 +++ ...SourceBuilder.InterpolatedStringHandler.cs | 156 ++++ Lyra/CSharpSourceBuilder.cs | 730 ++++++++++++++++++ Lyra/CSharpSourceBuilderOptions.cs | 65 ++ ...CommentInterpolationIndentationDetector.cs | 58 ++ Lyra/ComponentFactory.cs | 305 ++++++++ Lyra/EmptyComponent.cs | 28 + Lyra/Generator.cs | 17 + Lyra/ICSharpSourceComponent.cs | 25 + Lyra/IInterpolationIndentationDetector.cs | 56 ++ Lyra/ImmutableArrayEqualityComparer.cs | 89 +++ ...InterpolationIndentationDetectorContext.cs | 31 + Lyra/ListComponent.cs | 112 +++ Lyra/Lyra.csproj | 47 ++ Lyra/NamespaceComponent.cs | 52 ++ Lyra/NullInterpolationIndentationDetector.cs | 47 ++ Lyra/README.md | 143 ++++ Lyra/RentedArrayLifetime.cs | 55 ++ Lyra/StrategyComponent.cs | 34 + Lyra/TypeComponent.cs | 178 +++++ Lyra/TypeNameComponent.cs | 60 ++ ...tespaceInterpolationIndentationDetector.cs | 58 ++ 26 files changed, 2693 insertions(+) create mode 100644 CSharpSourceBuilder.TestApplication/CSharpSourceBuilder.TestApplication.csproj create mode 100644 CSharpSourceBuilder.TestApplication/Program.cs create mode 100644 Lyra/AggregateInterpolationIndentationDetector.cs create mode 100644 Lyra/AttributeArgumentComponent.cs create mode 100644 Lyra/AttributeComponent.cs create mode 100644 Lyra/CSharpSourceBuilder.InterpolatedStringHandler.cs create mode 100644 Lyra/CSharpSourceBuilder.cs create mode 100644 Lyra/CSharpSourceBuilderOptions.cs create mode 100644 Lyra/CommentInterpolationIndentationDetector.cs create mode 100644 Lyra/ComponentFactory.cs create mode 100644 Lyra/EmptyComponent.cs create mode 100644 Lyra/Generator.cs create mode 100644 Lyra/ICSharpSourceComponent.cs create mode 100644 Lyra/IInterpolationIndentationDetector.cs create mode 100644 Lyra/ImmutableArrayEqualityComparer.cs create mode 100644 Lyra/InterpolationIndentationDetectorContext.cs create mode 100644 Lyra/ListComponent.cs create mode 100644 Lyra/Lyra.csproj create mode 100644 Lyra/NamespaceComponent.cs create mode 100644 Lyra/NullInterpolationIndentationDetector.cs create mode 100644 Lyra/README.md create mode 100644 Lyra/RentedArrayLifetime.cs create mode 100644 Lyra/StrategyComponent.cs create mode 100644 Lyra/TypeComponent.cs create mode 100644 Lyra/TypeNameComponent.cs create mode 100644 Lyra/WhitespaceInterpolationIndentationDetector.cs diff --git a/CSharpSourceBuilder.TestApplication/CSharpSourceBuilder.TestApplication.csproj b/CSharpSourceBuilder.TestApplication/CSharpSourceBuilder.TestApplication.csproj new file mode 100644 index 0000000..2fc683e --- /dev/null +++ b/CSharpSourceBuilder.TestApplication/CSharpSourceBuilder.TestApplication.csproj @@ -0,0 +1,15 @@ + + + + Exe + net9.0 + preview + enable + enable + + + + + + + diff --git a/CSharpSourceBuilder.TestApplication/Program.cs b/CSharpSourceBuilder.TestApplication/Program.cs new file mode 100644 index 0000000..cd0d93f --- /dev/null +++ b/CSharpSourceBuilder.TestApplication/Program.cs @@ -0,0 +1,7 @@ +// See https://aka.ms/new-console-template for more information + +using RhoMicro.CodeAnalysis; + +using var builder = new CSharpSourceBuilder(); + + diff --git a/Lyra/AggregateInterpolationIndentationDetector.cs b/Lyra/AggregateInterpolationIndentationDetector.cs new file mode 100644 index 0000000..b9aa68f --- /dev/null +++ b/Lyra/AggregateInterpolationIndentationDetector.cs @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +using System; +using System.Collections.Immutable; + +/// +/// Aggregates several indentation detectors into a chain of responsibility. +/// +/// +/// The detectors to aggregate. +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +internal sealed class AggregateInterpolationIndentationDetector( + params ImmutableArray detectors) : IInterpolationIndentationDetector +{ + /// + /// Attempts to detect indentation using all the registered detectors, + /// returning upon the first successful detection. + /// + /// + public Boolean TryDetectIndentation( + ReadOnlyMemory text, + InterpolationIndentationDetectorContext context, + out ReadOnlyMemory indentation) + { + foreach (var detector in detectors) + { + if (!detector.TryDetectIndentation(text, context, out var i)) + { + continue; + } + + indentation = i; + return true; + } + + indentation = default; + return false; + } +} diff --git a/Lyra/AttributeArgumentComponent.cs b/Lyra/AttributeArgumentComponent.cs new file mode 100644 index 0000000..7b3e596 --- /dev/null +++ b/Lyra/AttributeArgumentComponent.cs @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +using System; +using System.Collections.Generic; +using System.Threading; + +/// +/// Represents an argument in an argument syntax. +/// +/// +/// This type supports value equality. +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +internal readonly record struct AttributeArgumentComponent : ICSharpSourceComponent +{ + private AttributeArgumentComponent( + Char assignmentOperator, + TypeNameComponent? type = null, + ICSharpSourceComponent? expressionComponent = null, + String? expression = null, + String? name = null) + { + _type = type; + _expressionComponent = expressionComponent; + _expression = expression; + _name = name; + _assignmentOperator = assignmentOperator; + } + + private readonly TypeNameComponent? _type; + private readonly ICSharpSourceComponent? _expressionComponent; + private readonly String? _expression; + private readonly String? _name; + private readonly Char _assignmentOperator; + + /// + /// Creates a positional (constructor) argument of type . + /// + /// + /// The type to append into the typeof() expression assigned to the parameter. + /// + /// + /// The name of the parameter. + /// + /// + /// A new positional argument component. + /// + public static AttributeArgumentComponent CreatePositional(TypeNameComponent type, String? parameterName = null) + => new(assignmentOperator: ':', + type: type, + expressionComponent: null, + expression: null, + name: parameterName); + + /// + /// Creates a positional (constructor) argument. + /// + /// + /// The component representing the expression of the argument. + /// + /// + /// The name of the parameter. + /// + /// + /// A new positional argument component. + /// + public static AttributeArgumentComponent CreatePositional(ICSharpSourceComponent expressionComponent, + String? parameterName = null) + => new(assignmentOperator: ':', + type: null, + expressionComponent: expressionComponent, + expression: null, + name: parameterName); + + /// + /// Creates a positional (constructor) argument. + /// + /// + /// The expression to pass as the argument. + /// + /// + /// The name of the parameter. + /// + /// + /// A new positional argument component. + /// + public static AttributeArgumentComponent CreatePositional(String expression, String? parameterName = null) + => new(assignmentOperator: ':', + type: null, + expressionComponent: null, + expression: expression, + name: parameterName); + + /// + /// Creates a named (property) argument of type . + /// + /// + /// The type to append into the typeof() expression assigned to the parameter. + /// + /// + /// The name of the property. + /// + /// + /// A new positional argument component. + /// + public static AttributeArgumentComponent CreateNamed(TypeNameComponent type, String propertyName) + => new(assignmentOperator: '=', + type: type, + expressionComponent: null, + expression: null, + name: propertyName); + + /// + /// Creates a named (property) argument. + /// + /// + /// The component representing the expression of the argument. + /// + /// + /// The name of the property. + /// + /// + /// A new positional argument component. + /// + public static AttributeArgumentComponent CreateNamed(ICSharpSourceComponent expressionComponent, + String propertyName) + => new(assignmentOperator: '=', + type: null, + expressionComponent: expressionComponent, + expression: null, + name: propertyName); + + /// + /// Creates a named (property) argument. + /// + /// + /// The expression to pass as the argument. + /// + /// + /// The name of the property. + /// + /// + /// A new positional argument component. + /// + public static AttributeArgumentComponent CreateNamed(String expression, String propertyName) + => new(assignmentOperator: '=', + type: null, + expressionComponent: null, + expression: expression, + name: propertyName); + + /// + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (_assignmentOperator == 0) + { + return; + } + + if (_name is not null) + { + builder.Append($"{_name}"); + + if (_assignmentOperator is '=') + { + builder.Append(" "); + } + + builder.Append($"{_assignmentOperator} "); + } + + if (_type is { } type) + { + builder.Append($"typeof({type})"); + } + else if (_expressionComponent is not null) + { + builder.Append(_expressionComponent); + } + else if (_expression is not null) + { + builder.Append(_expression); + } + } +} diff --git a/Lyra/AttributeComponent.cs b/Lyra/AttributeComponent.cs new file mode 100644 index 0000000..3a56ef5 --- /dev/null +++ b/Lyra/AttributeComponent.cs @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +using System; +using System.Collections.Immutable; +using System.Threading; + +/// +/// Represents an attribute syntax. +/// +/// +/// This type supports value equality. +/// +/// +/// The type of the attribute to append. +/// +/// +/// The arguments to append into the attribute. +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +internal readonly record struct AttributeComponent( + TypeNameComponent Type, + ImmutableArray Arguments) + : ICSharpSourceComponent +{ + /// + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (Arguments.IsDefault) + { + return; + } + + builder.Append($"[{Type}"); + + if (Arguments is not []) + { + builder.Append('('); + + for (var i = 0; i < Arguments.Length; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (i is not 0) + { + builder.Append(", "); + } + + var argument = Arguments[i]; + builder.Append(argument); + } + + builder.Append(')'); + } + + builder.Append(']'); + } + + /// + public Boolean Equals(AttributeComponent other) + { + if (other.Type != Type) + { + return false; + } + + if (!ImmutableArrayEqualityComparer.Equals(other.Arguments, Arguments)) + { + return false; + } + + return true; + } + + /// + public override Int32 GetHashCode() + { + var hc = new HashCode(); + hc.Add(Type); + hc.Add(Arguments, ImmutableArrayEqualityComparer.Default); + var result = hc.ToHashCode(); + + return result; + } +} diff --git a/Lyra/CSharpSourceBuilder.InterpolatedStringHandler.cs b/Lyra/CSharpSourceBuilder.InterpolatedStringHandler.cs new file mode 100644 index 0000000..cc5d929 --- /dev/null +++ b/Lyra/CSharpSourceBuilder.InterpolatedStringHandler.cs @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +using System; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +partial class CSharpSourceBuilder +{ + /// + /// Interpolated string handler for supporting interpolated strings on Append methods efficiently. + /// This type is not intended to be used directly and doing so may corrupt the builders state. + /// + /// + /// + /// + [InterpolatedStringHandler, EditorBrowsable(EditorBrowsableState.Never)] + public partial struct InterpolatedStringHandler( +#pragma warning disable CS9113 // Parameter is unread. Needed for interpolated string handler pattern. + Int32 literalLength, + Int32 formattedCount, +#pragma warning restore CS9113 // Parameter is unread. + CSharpSourceBuilder writer) : IDisposable + { + private Boolean _isIndented; + + /// + /// Appends a literal to the builder. + /// + /// + /// The literal to append. + /// + public void AppendLiteral(String literal) + { + if (!writer._appendCondition) + { + return; + } + + TryDetent(); + writer.AppendCore(literal, out var lastLineStartText); + + if (!writer._detector.TryDetectIndentation( + lastLineStartText, + writer._indentationDetectorContext, + out var indentation)) + { + return; + } + + writer.Indent(indentation); + _isIndented = true; + } + + /// + /// Formats and appends a placeholder to the builder. + /// + /// + /// The placeholder to append. + /// + /// + /// Indicates whether to invoke the default implementation of . + /// Implementers of this method should set this parameter to . + /// + /// + /// The type of the placeholder to format and append. + /// + partial void AppendFormatted(T placeholder, ref Boolean invokeDefaultLogic); + + /// + /// Formats and appends a placeholder to the builder. + /// + /// + /// The placeholder to append. + /// + public void AppendFormatted(String placeholder) + { + writer.Append(placeholder); + } + + /// + public void AppendFormatted(Type placeholder) + { + writer.AppendTypeName(placeholder); + } + + /// + public void AppendFormatted(ICSharpSourceComponent placeholder) + { + writer.Append(placeholder); + } + + /// + /// + /// The type of the placeholder to format and append. + /// + public void AppendFormatted(T placeholder) + { + if (!writer._appendCondition) + { + return; + } + + var invokeDefaultLogic = true; + AppendFormatted(placeholder, ref invokeDefaultLogic); + + if (!invokeDefaultLogic) + { + return; + } + + switch (placeholder) + { + case String @string: + AppendFormatted(@string); + break; + case Type type: + AppendFormatted(type); + break; + case ICSharpSourceComponent component: + // invert call in order to allow jit to remove boxing conversion + component.AppendTo(writer, writer._cancellationToken); + break; + case null: + break; + default: + { + if (placeholder.ToString() is { } s) + { + AppendFormatted(s); + } + + break; + } + } + } + + private void TryDetent() + { + if (!_isIndented) + { + return; + } + + writer.Detent(); + _isIndented = false; + } + + /// + public void Dispose() => TryDetent(); + } +} diff --git a/Lyra/CSharpSourceBuilder.cs b/Lyra/CSharpSourceBuilder.cs new file mode 100644 index 0000000..6b5a106 --- /dev/null +++ b/Lyra/CSharpSourceBuilder.cs @@ -0,0 +1,730 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Threading; + +/// +/// Builds indented texts, primarily intended for C# source code. +/// The builder is indentation and newline aware. +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +internal partial class CSharpSourceBuilder : IDisposable +{ + /// + /// Initializes a new instance. + /// + /// + /// The options to use. + /// + public CSharpSourceBuilder(CSharpSourceBuilderOptions options) + { + Options = options; + _rentedArrayLifetime = new(options.CharBufferOwner); + _buffer = options.CharBufferOwner.Rent(8); + _indentations = [.. options.InitialIndentation]; + _indentationDetectorContext = new InterpolationIndentationDetectorContext(options, _rentedArrayLifetime); + _detector = options.InitialInterpolationIndentationDetector; + } + + /// + /// Initializes a new instance, using . + /// + public CSharpSourceBuilder() : this(CSharpSourceBuilderOptions.Default) + { + } + + private const Int32 AppendMethodHighOverloadPriority = 3; + private const Int32 AppendMethodMediumOverloadPriority = 2; + private const Int32 AppendMethodLowOverloadPriority = 1; + private const Int32 AppendMethodNoOverloadPriority = 0; + + private readonly List> _indentations; + private readonly RentedArrayLifetime _rentedArrayLifetime; + private readonly InterpolationIndentationDetectorContext _indentationDetectorContext; + + /// + /// Gets the options used by the builder. + /// + public CSharpSourceBuilderOptions Options { get; } + + /// + /// Gets the capacity (size) of the builder. + /// + public Int32 Capacity => _buffer.Length; + + /// + /// Gets the amount of characters written to the builder so far. + /// + public Int32 Length { get; private set; } + + /// + /// Gets the amount of newlines written to the builder. + /// + /// + /// The value returned may not correspond to the amount of lines written to the builder. + /// For example, the following string: + /// + /// "foo\nbar\nbaz" + /// + /// will result in returning 2 (as 2 newlines will have + /// been written, but intuitively, the number of lines would be 3. + /// + public Int32 Lines { get; private set; } + + private IInterpolationIndentationDetector _detector; + private Boolean _lastWasNewLine = true; + private Boolean _lastWasEmptyLine = false; + private Boolean _preludeWritten = false; + private Char[] _buffer; + private Boolean _appendCondition = true; + private CancellationToken _cancellationToken; + + private static Int32 RoundUpToPowerOf2(Int32 value) + { + if (value >= 0x40000000) + { + return Int32.MaxValue; + } + + if (value < 1) + { + return 1; + } + + --value; + value |= value >> 1; + value |= value >> 2; + value |= value >> 4; + value |= value >> 8; + value |= value >> 16; + return value + 1; + } + + private Memory GetMemory(Int32 lengthHint) + { + var requiredLength = Length + lengthHint; + + if (_buffer.Length < requiredLength) + { + var newLength = RoundUpToPowerOf2(requiredLength); + var newBuffer = Options.CharBufferOwner.Rent((Int32)newLength); + _buffer.CopyTo(newBuffer); + Options.CharBufferOwner.Return(_buffer); + _buffer = newBuffer; + } + + return _buffer.AsMemory(Length); + } + + private Span GetSpan(Int32 lengthHint) => GetMemory(lengthHint).Span; + + private void AppendCore(ReadOnlySpan text, out ReadOnlyMemory lastLineStartText) + { + _cancellationToken.ThrowIfCancellationRequested(); + + lastLineStartText = default; + + if (text is []) + { + return; + } + + TryWritePrelude(); + + var tail = text; + while (true) + { + _cancellationToken.ThrowIfCancellationRequested(); + + var nextNewline = tail.IndexOfAny('\n', '\r'); + if (nextNewline is -1) + { + if (tail.Length is 0) + { + break; + } + + TryWriteIndentation(); + tail.CopyTo(GetSpan(tail.Length)); + lastLineStartText = _buffer.AsMemory(Length, tail.Length); + + Length += tail.Length; + + break; + } + + var head = tail[..nextNewline]; + if (head.Length is not 0) + { + TryWriteIndentation(); + } + + head.CopyTo(GetSpan(head.Length)); + Length += head.Length; + AppendLine(); + lastLineStartText = default; + + tail = tail[nextNewline..]; + + if (tail is ['\r', '\n', ..]) + { + tail = tail[2..]; + } + else if (tail is [_, ..]) + { + tail = tail[1..]; + } + else + { + tail = []; + } + } + } + + private void TryWritePrelude() + { + if (_preludeWritten) + { + return; + } + + _preludeWritten = true; + Options.Prelude.Invoke(this, _cancellationToken); + } + + private void TryWriteIndentation() + { + _cancellationToken.ThrowIfCancellationRequested(); + + if (!_lastWasNewLine) + { + return; + } + + _lastWasNewLine = false; + _lastWasEmptyLine = false; + + foreach (var indentation in _indentations) + { + AppendCore(indentation.Span, out _); + } + } + + /// + /// Clears the builders contents. + /// + /// + /// A reference to the builder, for chaining of further method calls. + /// + public CSharpSourceBuilder Clear() + { + Length = 0; + _preludeWritten = false; + return this; + } + + /// + /// Begins a conditional scope. + /// Methods executed after this method returns will execute if the + /// condition evaluates to . + /// + /// + /// The condition to conditionalize operations against. + /// + /// + /// A reference to the builder, for chaining of further method calls. + /// + public CSharpSourceBuilder SetCondition(Boolean condition) + { + _appendCondition = condition; + return this; + } + + /// + /// Unsets the conditional scope. + /// Methods executed after this method returns will no longer execute + /// depending on the condition previously set. + /// + /// + /// A reference to the builder, for chaining of further method calls. + /// + public CSharpSourceBuilder UnsetCondition() + { + _appendCondition = true; + return this; + } + +#region Indentation + + /// + /// Indents the builder. + /// + /// + /// The indentation to add to the builder. + /// + /// + /// A reference to the builder, for chaining of further method calls. + /// + public CSharpSourceBuilder Indent(ReadOnlyMemory indentation) + { + if (!_appendCondition) + { + return this; + } + + _indentations.Add(indentation); + return this; + } + + /// + /// Indents the builder. + /// + /// + /// The indentation to add to the builder. + /// + /// + /// A reference to the builder, for chaining of further method calls. + /// + public CSharpSourceBuilder Indent(String indentation) => Indent(indentation.AsMemory()); + + /// + /// Indents the builder using the default indentation. + /// + /// + /// A reference to the builder, for chaining of further method calls. + /// + public CSharpSourceBuilder Indent() => Indent(Options.DefaultIndentation); + + /// + /// Detents the builder once. + /// + /// + /// A reference to the builder, for chaining of further method calls. + /// + public CSharpSourceBuilder Detent() + { + if (!_appendCondition) + { + return this; + } + + if (_indentations.Count is not 0) + { + _indentations.RemoveAt(_indentations.Count - 1); + } + + return this; + } + +#endregion + +#region Append + + /// + /// Appends a text to the builder. + /// + /// + /// The text to append to the builder. + /// + /// + /// A reference to the builder, for chaining of further method calls. + /// + [OverloadResolutionPriority(AppendMethodLowOverloadPriority)] + public CSharpSourceBuilder Append(String text) => + Append(text.AsSpan()); + + /// + [OverloadResolutionPriority(AppendMethodLowOverloadPriority)] + public CSharpSourceBuilder Append(ReadOnlySpan text) + { + _cancellationToken.ThrowIfCancellationRequested(); + + if (!_appendCondition) + { + return this; + } + + AppendCore(text, out _); + return this; + } + + /// + [OverloadResolutionPriority(AppendMethodLowOverloadPriority)] + public CSharpSourceBuilder Append(Char text) => + Append([text]); + + /// + [OverloadResolutionPriority(AppendMethodMediumOverloadPriority)] + public CSharpSourceBuilder Append( + [InterpolatedStringHandlerArgument("")] + InterpolatedStringHandler text) + { + _cancellationToken.ThrowIfCancellationRequested(); + + if (!_appendCondition) + { + return this; + } + + // handler performs writing operations + text.Dispose(); + return this; + } + + /// + /// + /// The type of component to append, + /// + [OverloadResolutionPriority(AppendMethodLowOverloadPriority)] + public CSharpSourceBuilder Append(T component) + where T : ICSharpSourceComponent + { + _cancellationToken.ThrowIfCancellationRequested(); + + component.AppendTo(this, _cancellationToken); + + return this; + } + + /// + /// Appends a component to the builder. + /// + /// + /// The component to append to the builder. + /// + /// + /// A reference to the builder, for chaining of further method calls. + /// + [OverloadResolutionPriority(AppendMethodNoOverloadPriority)] + public CSharpSourceBuilder Append(ICSharpSourceComponent component) + { + _cancellationToken.ThrowIfCancellationRequested(); + + component.AppendTo(this, _cancellationToken); + + return this; + } + +#endregion + +#region AppendLine + + /// + /// Appends a newline to the builder + /// + /// + /// A reference to the builder, for chaining of further method calls. + /// + [OverloadResolutionPriority(AppendMethodHighOverloadPriority)] + public CSharpSourceBuilder AppendLine() + { + _cancellationToken.ThrowIfCancellationRequested(); + + if (!_appendCondition) + { + return this; + } + + if (Options.Newline.Length is 0) + { + return this; + } + + if (_lastWasEmptyLine && !Options.AllowConsecutiveEmptyLines) + { + return this; + } + + TryWritePrelude(); + + var buffer = GetSpan(Options.Newline.Length); + Options.Newline.Span.CopyTo(buffer); + Length += Options.Newline.Length; + Lines++; + _lastWasEmptyLine = _lastWasNewLine; + _lastWasNewLine = true; + + return this; + } + + /// + /// Appends a text to the builder, followed by a newline. + /// + /// + /// The text to append to the builder. + /// + /// + /// A reference to the builder, for chaining of further method calls. + /// + [OverloadResolutionPriority(AppendMethodLowOverloadPriority)] + public CSharpSourceBuilder AppendLine(String text) + { + _cancellationToken.ThrowIfCancellationRequested(); + + Append(text); + AppendLine(); + + return this; + } + + /// + [OverloadResolutionPriority(AppendMethodLowOverloadPriority)] + public CSharpSourceBuilder AppendLine(ReadOnlySpan text) + { + _cancellationToken.ThrowIfCancellationRequested(); + + Append(text); + AppendLine(); + + return this; + } + + /// + [OverloadResolutionPriority(AppendMethodLowOverloadPriority)] + public CSharpSourceBuilder AppendLine(Char text) + { + _cancellationToken.ThrowIfCancellationRequested(); + + Append(text); + AppendLine(); + + return this; + } + + /// + [OverloadResolutionPriority(AppendMethodMediumOverloadPriority)] + public CSharpSourceBuilder AppendLine( + [InterpolatedStringHandlerArgument("")] + InterpolatedStringHandler text) + { + _cancellationToken.ThrowIfCancellationRequested(); + + if (!_appendCondition) + { + return this; + } + + // handler performs writing operations + text.Dispose(); + AppendLine(); + + return this; + } + + /// + /// + /// The type of component to append, + /// + [OverloadResolutionPriority(AppendMethodLowOverloadPriority)] + public CSharpSourceBuilder AppendLine(T component) + where T : ICSharpSourceComponent + { + _cancellationToken.ThrowIfCancellationRequested(); + + component.AppendTo(this, _cancellationToken); + AppendLine(); + + return this; + } + + /// + /// Appends a component to the builder, followed by a newline. + /// + /// + /// The component to append to the builder. + /// + /// + /// A reference to the builder, for chaining of further method calls. + /// + [OverloadResolutionPriority(AppendMethodNoOverloadPriority)] + public CSharpSourceBuilder AppendLine(ICSharpSourceComponent component) + { + _cancellationToken.ThrowIfCancellationRequested(); + + component.AppendTo(this, _cancellationToken); + AppendLine(); + + return this; + } + +#endregion + +#region Append Type Name + + /// + /// + /// The type whose name to append. + /// + public CSharpSourceBuilder AppendTypeName() + { + AppendTypeName(typeof(T)); + + return this; + } + + /// + /// Appends the globally qualified C# name of a type to the builder. + /// + /// + /// The type whose name to append. + /// + /// + /// A reference to the builder, for chaining of further method calls. + /// + public CSharpSourceBuilder AppendTypeName(Type type) + { + _cancellationToken.ThrowIfCancellationRequested(); + + if (!_appendCondition) + { + return this; + } + + Append("global::"); + + if (type.Namespace is { } ns) + { + Append(ns); + Append('.'); + } + + var firstIllegalIndex = type.Name.AsSpan().IndexOfAny('`', '['); + var length = firstIllegalIndex is -1 ? type.Name.Length : firstIllegalIndex; + var name = type.Name.AsSpan(0, length); + + Append(name); + + if (type.IsArray) + { + Append("[]"); + } + + if (type.IsGenericType) + { + Append('<'); + + foreach (var parameter in type.GetGenericArguments()) + { + _cancellationToken.ThrowIfCancellationRequested(); + + AppendTypeName(parameter); + } + + Append('>'); + } + + return this; + } + +#endregion + +#region Cancellation + + /// + /// Sets the ambient cancellation token used by other methods in this builder. + /// + /// + /// A reference to the builder, for chaining of further method calls. + /// + public CSharpSourceBuilder SetCancellationToken(CancellationToken cancellationToken) + { + _cancellationToken = cancellationToken; + return this; + } + + /// + /// Unsets the ambient cancellation token used by other methods in this builder. + /// After calling this method, cancellation will no longer be checked. + /// + /// + /// A reference to the builder, for chaining of further method calls. + /// + public CSharpSourceBuilder UnsetCancellationToken() + { + _cancellationToken = default; + return this; + } + +#endregion + +#region Interpolation Indentation Detector + + /// + /// Sets the indentation detector used by the builder. + /// + /// + /// The detector to use. + /// + /// + /// A reference to the builder, for chaining of further method calls. + /// + public CSharpSourceBuilder SetInterpolationIndentationDetector( + IInterpolationIndentationDetector detector) + { + if (!_appendCondition) + { + return this; + } + + _detector = detector; + + return this; + } + + /// + /// Sets the indentation detector used by the builder. + /// + /// + /// The detector to use. + /// + /// + /// Upon returning, contains the previously used indentation detector. + /// + /// + /// A reference to the builder, for chaining of further method calls. + /// + public CSharpSourceBuilder SetInterpolationIndentationDetector( + IInterpolationIndentationDetector detector, + out IInterpolationIndentationDetector previousDetector) + { + previousDetector = _detector; + + if (!_appendCondition) + { + return this; + } + + _detector = detector; + + return this; + } + +#endregion + + /// + /// Builds the source string. + /// + /// + /// The source string. + /// + public override String ToString() + { + TryWritePrelude(); + var result = new String(_buffer, 0, Length); + + return result; + } + + /// + public void Dispose() + { + _rentedArrayLifetime.Dispose(); + _buffer = []; + } +} diff --git a/Lyra/CSharpSourceBuilderOptions.cs b/Lyra/CSharpSourceBuilderOptions.cs new file mode 100644 index 0000000..1671355 --- /dev/null +++ b/Lyra/CSharpSourceBuilderOptions.cs @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +using System; +using System.Buffers; +using System.Collections.Immutable; + +/// +/// Provides options for the . +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +internal partial class CSharpSourceBuilderOptions +{ + /// + /// Gets the default instance. + /// + public static CSharpSourceBuilderOptions Default { get; } = new(); + + /// + /// Gets the initial indentation to apply to the builder. + /// + public virtual ImmutableArray> InitialIndentation { get; init; } = []; + + /// + /// Gets the indentation to apply when calling + /// + public virtual ReadOnlyMemory DefaultIndentation { get; init; } = " ".AsMemory(); + + /// + /// Gets the newline character sequence to use when emitting a newline. + /// + public virtual ReadOnlyMemory Newline { get; init; } = "\n".AsMemory(); + + /// + /// Gets the to use when creating buffers. + /// + public virtual ArrayPool CharBufferOwner { get; init; } = ArrayPool.Shared; + + /// + /// Gets a callback to invoke before the first writing operation. + /// + public virtual Action Prelude { get; init; } = + (w, _) => w.AppendLine( + $""" + // + // This file was generated using the {typeof(CSharpSourceBuilder)}. + // + """); + + /// + /// Gets a value indicating if successive newlines are allowed. + /// + public virtual Boolean AllowConsecutiveEmptyLines { get; init; } = false; + + /// + /// Gets the initial interpolation indentation detector. + /// + public virtual IInterpolationIndentationDetector InitialInterpolationIndentationDetector { get; init; } = + new AggregateInterpolationIndentationDetector( + WhitespaceInterpolationIndentationDetector.Instance, + CommentInterpolationIndentationDetector.Instance); +} diff --git a/Lyra/CommentInterpolationIndentationDetector.cs b/Lyra/CommentInterpolationIndentationDetector.cs new file mode 100644 index 0000000..ccad86e --- /dev/null +++ b/Lyra/CommentInterpolationIndentationDetector.cs @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +using System; + +/// +/// Detects leading comments and whitespace as interpolation indentation. +/// +/// +/// For example, given the following interpolated string: +/// +/// $"// foo{"\nbar\nbaz"}" +/// +/// , the builder using this detector strategy will build the following string: +/// +/// // foo +/// // bar +/// // baz +/// +/// The detector supports docs comments (///) and single line comment (//). +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +internal sealed class CommentInterpolationIndentationDetector : IInterpolationIndentationDetector +{ + private CommentInterpolationIndentationDetector() + { + } + + /// + /// Gets the singleton instance. + /// + public static CommentInterpolationIndentationDetector Instance { get; } = new(); + + /// + public Boolean TryDetectIndentation( + ReadOnlyMemory text, + InterpolationIndentationDetectorContext context, + out ReadOnlyMemory indentation) + { + if (text.Span is not ['/', '/', ..]) + { + indentation = default; + return false; + } + + var whitespaceCount = text.Span is ['/', '/', '/', ..] ? 3 : 2; + while (text.Span.Length > whitespaceCount && Char.IsWhiteSpace(text.Span[whitespaceCount])) + { + whitespaceCount++; + } + + indentation = text[..whitespaceCount]; + return true; + } +} diff --git a/Lyra/ComponentFactory.cs b/Lyra/ComponentFactory.cs new file mode 100644 index 0000000..92786ab --- /dev/null +++ b/Lyra/ComponentFactory.cs @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +using System; +using System.Collections.Immutable; +using System.Threading; + +/// +/// Creates instances. +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +internal static partial class ComponentFactory +{ + /// + /// Creates a new list component. + /// + /// + /// The elements to enumerate. + /// + /// + /// The separator separating each element. + /// + /// + /// The terminator appended after the final element. + /// + /// + /// A new list component. + /// + public static ListComponent List( + ImmutableArray elements, + String separator = "", + String terminator = "") + => new(Elements: elements, + Append: static (e, _, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + b.Append(e); + }, + Separator: separator, + Separate: static (s, _, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + b.Append(s); + }, + Terminator: terminator); + + /// + /// Creates a new list component. + /// + /// + /// The elements to enumerate. + /// + /// + /// The callback to invoke to append an element. + /// + /// + /// The separator separating each element. + /// + /// + /// The terminator appended after the final element. + /// + /// + /// A new list component. + /// + public static ListComponent List( + ImmutableArray elements, + Action append, + String separator = "", + String terminator = "") + => new(Elements: elements, + Append: append, + Separator: separator, + Separate: static (s, _, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + b.Append(s); + }, + Terminator: terminator); + + /// + /// Creates a new namespace component. + /// + /// + /// The namespace to use in the component. + /// + /// + /// The body to append into the namespace block. + /// + /// + /// The type of body to append into the namespace block. + /// + /// + /// A new namespace component. + /// + public static NamespaceComponent Namespace(String @namespace, TBody body) + where TBody : ICSharpSourceComponent + => new(@namespace, body); + + /// + /// Creates a new type name component. + /// + /// + /// The type whose name to append. + /// + /// + /// A new type name component. + /// + public static TypeNameComponent TypeName(Type type) => new(type); + + /// + /// Creates a new type name component. + /// + /// + /// The name to append. + /// + /// + /// A new type name component. + /// + public static TypeNameComponent TypeName(String typeName) => new(typeName); + + /// + /// Creates a new attribute argument component. + /// + /// + /// The type to use as the appended typeof() expression. + /// + /// + /// The name of the parameter to assign. + /// + /// + /// A new attribute argument component. + /// + public static AttributeArgumentComponent PositionalAttributeArgument( + TypeNameComponent type, + String? parameterName = null) + => AttributeArgumentComponent.CreatePositional(type, parameterName); + + /// + /// Creates a new attribute argument component. + /// + /// + /// The component to append as the appended expression. + /// + /// + /// The name of the parameter to assign. + /// + /// + /// A new attribute argument component. + /// + public static AttributeArgumentComponent PositionalAttributeArgument( + ICSharpSourceComponent expressionComponent, + String? parameterName = null) + => AttributeArgumentComponent.CreatePositional(expressionComponent, parameterName); + + /// + /// Creates a new attribute argument component. + /// + /// + /// The expression to append as the appended expression. + /// + /// + /// The name of the parameter to assign. + /// + /// + /// A new attribute argument component. + /// + public static AttributeArgumentComponent PositionalAttributeArgument( + String expression, + String? parameterName = null) + => AttributeArgumentComponent.CreatePositional(expression, parameterName); + + /// + /// Creates a new attribute argument component. + /// + /// + /// The type to use as the appended typeof() expression. + /// + /// + /// The name of the property to assign. + /// + /// + /// A new attribute argument component. + /// + public static AttributeArgumentComponent NamedAttributeArgument( + TypeNameComponent type, + String propertyName) + => AttributeArgumentComponent.CreateNamed(type, propertyName); + + /// + /// Creates a new attribute argument component. + /// + /// + /// The component to append as the appended expression. + /// + /// + /// The name of the property to assign. + /// + /// + /// A new attribute argument component. + /// + public static AttributeArgumentComponent NamedAttributeArgument( + ICSharpSourceComponent expressionComponent, + String propertyName) + => AttributeArgumentComponent.CreateNamed(expressionComponent, propertyName); + + /// + /// Creates a new attribute argument component. + /// + /// + /// The expression to append as the appended expression. + /// + /// + /// The name of the property to assign. + /// + /// + /// A new attribute argument component. + /// + public static AttributeArgumentComponent NamedAttributeArgument( + String expression, + String propertyName) + => AttributeArgumentComponent.CreateNamed(expression, propertyName); + + /// + /// Creates a new attribute component. + /// + /// + /// The type of the attribute to append. + /// + /// + /// The list of arguments to append into the attribute. + /// + /// + /// A new attribute component. + /// + public static AttributeComponent Attribute( + TypeNameComponent type, + ImmutableArray arguments) + => new(type, arguments); + + /// + /// Creates a new type component. + /// + /// + /// The modifiers to prepend to the name of the type. + /// + /// + /// The name of the type. + /// + /// + /// The type parameters of the type. + /// + /// + /// The list of names of the types implemented by the type. + /// + /// + /// The list of attributes applied to the type. + /// + /// + /// The body to append to the type. + /// + /// + /// The type of body to append to the type. + /// + /// + /// A new type component. + /// + public static TypeComponent Type( + String modifiers, + String name, + TBody body, + ImmutableArray typeParameters = default, + ImmutableArray baseTypeList = default, + ImmutableArray attributes = default) + where TBody : ICSharpSourceComponent + => new( + Modifiers: modifiers, + Name: name, + TypeParameters: typeParameters.IsDefault ? [] : typeParameters, + BaseTypeList: baseTypeList.IsDefault ? [] : baseTypeList, + Attributes: attributes.IsDefault ? [] : attributes, + Body: body); + + /// + public static TypeComponent Type( + String modifiers, + String name, + ImmutableArray typeParameters = default, + ImmutableArray baseTypeList = default, + ImmutableArray attributes = default) + => Type(modifiers, name, EmptyComponent.Instance, typeParameters, baseTypeList, attributes); + + /// + /// Creates a new strategy based component. + /// + /// + /// The strategy to use when appending. + /// + /// + /// A new strategy component. + /// + public static StrategyComponent Create(Action append) => new(append); +} diff --git a/Lyra/EmptyComponent.cs b/Lyra/EmptyComponent.cs new file mode 100644 index 0000000..c72efbd --- /dev/null +++ b/Lyra/EmptyComponent.cs @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +/// +/// Implements an empty component. +/// +/// +/// This type supports value equality. +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +sealed class EmptyComponent : ICSharpSourceComponent +{ + private EmptyComponent(){} + + /// + /// Gets the singleton instance. + /// + public static EmptyComponent Instance { get; } = new(); + + /// + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + } +} diff --git a/Lyra/Generator.cs b/Lyra/Generator.cs new file mode 100644 index 0000000..2f8ade2 --- /dev/null +++ b/Lyra/Generator.cs @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +using Generated; +using Microsoft.CodeAnalysis; + +/// +/// Generates sources for using the and related types. +/// +[Generator(LanguageNames.CSharp)] +public class Generator : IIncrementalGenerator +{ + /// + public void Initialize(IncrementalGeneratorInitializationContext context) + => IncludedFileSources.RegisterToContext(context); +} diff --git a/Lyra/ICSharpSourceComponent.cs b/Lyra/ICSharpSourceComponent.cs new file mode 100644 index 0000000..e6fd377 --- /dev/null +++ b/Lyra/ICSharpSourceComponent.cs @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +using System.Threading; + +/// +/// Represents a component that may be appended to a . +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +internal interface ICSharpSourceComponent +{ + /// + /// Appends the component to a builder. + /// + /// + /// The builder to append the component to. + /// + /// + /// The cancellation token used to request appending to be cancelled. + /// + void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default); +} diff --git a/Lyra/IInterpolationIndentationDetector.cs b/Lyra/IInterpolationIndentationDetector.cs new file mode 100644 index 0000000..09203be --- /dev/null +++ b/Lyra/IInterpolationIndentationDetector.cs @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +using System; + +/// +/// Used to detect indentations for interpolated strings passed to . +/// +/// +/// For example, consider this interpolated string: +/// +/// $"// foo{bar}" +/// +/// It is possible that bar evaluates to a string containing linebreaks. +/// This is not immediately obvious from the interpolated string itself. +/// Therefore, this interface provides the ability to detect and use the comment +/// preceding it for further indentation. +/// In this particular case, the +/// will recognize the '// ' preceding 'foo' and utilize it for +/// indentation on all lines that bar evaluates to. +/// Assuming that bar evaluates to \nbaz\nfaz, the following output will be built as a result: +/// +/// // foo +/// // baz +/// // faz +/// +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +internal interface IInterpolationIndentationDetector +{ + /// + /// Attempts to detect indentation from the provided text. + /// + /// + /// The text to scan for indentation. + /// + /// + /// A context object providing access to buffers and builder options. + /// Buffers may be obtained from this object in order to create artificial + /// indentation. + /// + /// + /// Upon returning from the method, contains the indentation if one could be + /// detected; otherwise, . + /// + /// + /// if indentation could be determined; otherwise, . + /// + Boolean TryDetectIndentation( + ReadOnlyMemory text, + InterpolationIndentationDetectorContext context, + out ReadOnlyMemory indentation); +} diff --git a/Lyra/ImmutableArrayEqualityComparer.cs b/Lyra/ImmutableArrayEqualityComparer.cs new file mode 100644 index 0000000..a857ae4 --- /dev/null +++ b/Lyra/ImmutableArrayEqualityComparer.cs @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; + +/// +/// Provides static access to equality operations defined by . +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +internal static class ImmutableArrayEqualityComparer +{ + /// + public static Boolean Equals(ImmutableArray x, ImmutableArray y) => + ImmutableArrayEqualityComparer.Default.Equals(x, y); + + /// + public static Int32 GetHashCode(ImmutableArray obj) => + ImmutableArrayEqualityComparer.Default.GetHashCode(obj); +} + +/// +/// Implements value equality for . +/// +/// +/// The comparer to use for comparing individual elements. +/// +/// +/// The type of elements contained in compared arrays. +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +internal sealed class ImmutableArrayEqualityComparer(IEqualityComparer elementComparer) + : IEqualityComparer> +{ + /// + /// Gets an instance using . + /// + public static ImmutableArrayEqualityComparer Default { get; } = new(EqualityComparer.Default); + + /// + public Boolean Equals(ImmutableArray x, ImmutableArray y) + { + if (x.IsDefault) + { + return y.IsDefault; + } + + if (y.IsDefault) + { + return false; + } + + if (x.Length != y.Length) + { + return false; + } + + for (var i = 0; i < x.Length; i++) + { + if (!elementComparer.Equals(x[i], y[i])) + { + return false; + } + } + + return true; + } + + /// + public Int32 GetHashCode(ImmutableArray obj) + { + var hc = new HashCode(); + + foreach (var element in obj) + { + hc.Add(element, elementComparer); + } + + var result = hc.ToHashCode(); + + return result; + } +} diff --git a/Lyra/InterpolationIndentationDetectorContext.cs b/Lyra/InterpolationIndentationDetectorContext.cs new file mode 100644 index 0000000..43a58aa --- /dev/null +++ b/Lyra/InterpolationIndentationDetectorContext.cs @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +/// +/// Provides contextual apis to instances of . +/// +/// +/// The builder options used by the invoking builder. +/// +/// +/// An object from which buffers may be rented. +/// The lifetime of rented buffers is tied to the lifetime of the invoking builder, +/// meaning they will be returned once the builder has been disposed. +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +internal readonly struct InterpolationIndentationDetectorContext(CSharpSourceBuilderOptions options, RentedArrayLifetime buffers) +{ + /// + /// Gets the options used by the invoking builder. + /// + public CSharpSourceBuilderOptions Options { get; } = options; + /// + /// Gets an object from which buffers may be rented. + /// The lifetime of rented buffers is tied to the lifetime of the invoking builder, + /// meaning they will be returned once the builder has been disposed. + /// + public RentedArrayLifetime Buffers { get; } = buffers; +} diff --git a/Lyra/ListComponent.cs b/Lyra/ListComponent.cs new file mode 100644 index 0000000..89f7c98 --- /dev/null +++ b/Lyra/ListComponent.cs @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading; + +/// +/// Renders a separated list of elements to a . +/// +/// +/// This type supports value equality. The and +/// members are not taken into account when calculating equality. +/// +/// +/// The elements to append to the builder. +/// +/// +/// The callback invoked to append elements to the builder. +/// +/// +/// The separator separating each element. +/// +/// +/// The callback invoked to append a separator to the builder. +/// +/// +/// The terminator to append to the final element. +/// +/// +/// The type of element to append. +/// +/// +/// The type of separator to append. +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +internal readonly record struct ListComponent( + ImmutableArray Elements, + Action Append, + TSeparator Separator, + Action Separate, + TSeparator Terminator) + : ICSharpSourceComponent +{ + /// + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (Elements.IsDefault) + { + return; + } + + var length = Elements.Length; + + for (var index = 0; index < length; index++) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (index is not 0) + { + Separate.Invoke(Separator, index, length, builder, cancellationToken); + } + + var element = Elements[index]; + Append.Invoke(element, index, length, builder, cancellationToken); + + if (index == length - 1) + { + Separate.Invoke(Terminator, index, length, builder, cancellationToken); + } + } + } + + /// + public Boolean Equals(ListComponent other) + { + if (!EqualityComparer.Default.Equals(other.Separator, Separator)) + { + return false; + } + + if (!EqualityComparer.Default.Equals(other.Terminator, Terminator)) + { + return false; + } + + if (!ImmutableArrayEqualityComparer.Equals(other.Elements, Elements)) + { + return false; + } + + return true; + } + + /// + public override Int32 GetHashCode() + { + var hc = new HashCode(); + hc.Add(Separator); + hc.Add(Terminator); + hc.Add(Elements, ImmutableArrayEqualityComparer.Default); + var result = hc.ToHashCode(); + + return result; + } +} diff --git a/Lyra/Lyra.csproj b/Lyra/Lyra.csproj new file mode 100644 index 0000000..85e029b --- /dev/null +++ b/Lyra/Lyra.csproj @@ -0,0 +1,47 @@ + + + + netstandard2.0 + false + true + true + true + + + + + true + true + + Provides types for generating (indented) C# source texts. + + Source Generator; StringBuilder; Indented; Indented StringBuilder; SourceBuilder; SourceText; CSharp; CSharp Source + + + + $(DefineConstants);CSHARPSOURCEBUILDER_GENERATOR + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/Lyra/NamespaceComponent.cs b/Lyra/NamespaceComponent.cs new file mode 100644 index 0000000..1fce513 --- /dev/null +++ b/Lyra/NamespaceComponent.cs @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +using System; +using System.Collections.Generic; +using System.Threading; + +/// +/// Represents a block scoped namespace. +/// +/// +/// This type supports value equality. +/// +/// +/// The name of the namespace. +/// +/// +/// The body of the namespace block. +/// +/// +/// The type of body of the namespace block. +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +readonly record struct NamespaceComponent(String Name, TBody Body) : ICSharpSourceComponent + where TBody : ICSharpSourceComponent +{ + /// + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (Name is null) + { + return; + } + + if (Name is not []) + { + builder.AppendLine($"namespace {Name}").AppendLine("{").Indent(); + } + + builder.Append(Body); + + if (Name is not []) + { + builder.Detent().AppendLine("}"); + } + } +} diff --git a/Lyra/NullInterpolationIndentationDetector.cs b/Lyra/NullInterpolationIndentationDetector.cs new file mode 100644 index 0000000..ac45a61 --- /dev/null +++ b/Lyra/NullInterpolationIndentationDetector.cs @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +using System; + +/// +/// Implements an empty interpolation indentation detector that will never detect indentations. +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +internal sealed class NullInterpolationIndentationDetector : IInterpolationIndentationDetector +{ + private NullInterpolationIndentationDetector() + { + } + + /// + /// Gets the singleton instance. + /// + public static NullInterpolationIndentationDetector Instance { get; } = new(); + + /// + /// Never detects indentation. + /// + /// + /// Upon returning, will always contain . + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public Boolean TryDetectIndentation( + ReadOnlyMemory text, + InterpolationIndentationDetectorContext context, + out ReadOnlyMemory indentation) + { + indentation = default; + return false; + } +} diff --git a/Lyra/README.md b/Lyra/README.md new file mode 100644 index 0000000..e02068d --- /dev/null +++ b/Lyra/README.md @@ -0,0 +1,143 @@ +# Lyra + +This is a source generator generating library classes for writing beautiful C# source code; e.g. in source generators. + +## Licensing + +This project is licensed under the `MPL-2.0` license. + +## Features + +- indentation and newline aware string building +- reusable component system +- advanced interpolated strings for efficient source building + +## Installation + +.NET CLI: + +``` +dotnet add package RhoMicro.CodeAnalysis.Lyra --version 1.0.0 +``` + +PackageReference: + +```xml + + + + +``` + +## How To Use + +### Simple Example + +Instantiate a new `CSharpSourceBuilder`: + +```cs +using var sb = new CSharpSourceBuilder(); +``` + +Append text: + +```cs +sb.Append("namespace ").AppendLine("Foo").AppendLine('{'); +``` + +Indent text: + +```cs +sb.Indent(); +``` + +Use builtin components: + +```cs +sb.Append( + ComponentFactory.Type( + "enum", + "Bar", + ComponentFactory.Create((b, _) => b.AppendLine("Baz = 0")))); +``` + +Detent text: + +```cs +sb.Detent(); +``` + +Generate output: + +```cs +sb.AppendLine('}'); +Console.WriteLine(sb); +``` + +The generated output will look like this: + +``` +// +// This file was generated using the global::RhoMicro.CodeAnalysis.Lyra.CSharpSourceBuilder. +// +namespace Foo +{ + enum Bar + { + Baz = 0 + } +} + +``` + +The `CSharpSourceBuilder` can be configured using the `CSharpSourceBuilderOptions` passed to its constructor: + +```cs +using var sb1 = new CSharpSourceBuilder( + new CSharpSourceBuilderOptions() + { + Prelude = (b,_)=>b.AppendLine("// "), + DefaultIndentation = " ".AsMemory() + }); +``` + +The output changes to: + +``` +// +namespace Foo +{ + enum Bar + { + Baz = 0 + } +} + +``` + +### Interpolated Strings + +Interpolated strings are compiled to `Append` invocations, with indentation being automatically detected: + +``` +var placeholder = "\nbar\nbaz"; +sb.Clear().AppendLine( + $""" + foo{placeholder} + """); +Console.WriteLine(sb); +``` + +The output changes as follows: + +``` +// + foo + bar + baz +``` + +Note how the initial indentation in front of `foo` is carried along. + +The detection of these indentations can be configured using the +`CSharpSourceBuilderOptions.InitialInterpolationIndentationDetector` property. diff --git a/Lyra/RentedArrayLifetime.cs b/Lyra/RentedArrayLifetime.cs new file mode 100644 index 0000000..5b2bd94 --- /dev/null +++ b/Lyra/RentedArrayLifetime.cs @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +using System; +using System.Buffers; +using System.Collections.Generic; + +/// +/// Manages the lifetime of arrays rented from a . +/// Arrays may be rented from the lifetime and are returned upon disposal of the lifetime. +/// +/// +/// The pool whose arrays to manage lifetimes for. +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +internal readonly struct RentedArrayLifetime(ArrayPool pool) : IDisposable +{ + private readonly List _rentedArrays = []; + + /// + /// Gets the pool whose rented arrays lifetimes this instance manages. + /// + public ArrayPool Pool => pool; + + /// + /// Rents an array from . + /// The array is returned to the pool upon disposal of this instance. + /// + /// + /// + /// + /// + /// An array rented from , whose lifetime is managed by this instance. + /// + public Char[] Rent(Int32 minimumLength) + { + var result = Pool.Rent(minimumLength); + _rentedArrays.Add(result); + return result; + } + + /// + public void Dispose() + { + foreach (var rentedBuffer in _rentedArrays) + { + Pool.Return(rentedBuffer); + } + + _rentedArrays.Clear(); + } +} diff --git a/Lyra/StrategyComponent.cs b/Lyra/StrategyComponent.cs new file mode 100644 index 0000000..fbe9250 --- /dev/null +++ b/Lyra/StrategyComponent.cs @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +/// +/// Implements an arbitrary component. +/// +/// +/// This type does not support value equality. +/// +/// +/// The callback to invoke when appending to a builder. +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +readonly record struct StrategyComponent(Action append) : ICSharpSourceComponent +{ + /// + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + append.Invoke(builder, cancellationToken); + } + + /// + public Boolean Equals(StrategyComponent other) => + throw new NotSupportedException("Equals is not supported on this type."); + + /// + public override Int32 GetHashCode() => + throw new NotSupportedException("GetHashCode is not supported on this type."); +} diff --git a/Lyra/TypeComponent.cs b/Lyra/TypeComponent.cs new file mode 100644 index 0000000..6f8cdb0 --- /dev/null +++ b/Lyra/TypeComponent.cs @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Threading; + +/// +/// Represents a type declaration. +/// +/// +/// This type supports value equality. +/// +/// +/// The modifiers to prepend in front of the name. +/// +/// +/// The name of the type. +/// +/// +/// The types generic type parameters. +/// +/// +/// The list of types to append into the base type list. +/// +/// +/// The list of attributes to prepend to the type declaration. +/// +/// +/// The body of the type. +/// +/// +/// The type of the body. +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +internal readonly record struct TypeComponent( + String Modifiers, + String Name, + ImmutableArray TypeParameters, + ImmutableArray BaseTypeList, + ImmutableArray Attributes, + TBody Body) : ICSharpSourceComponent + where TBody : ICSharpSourceComponent +{ + /// + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (Name is null) + { + return; + } + + if (Attributes is not []) + { + foreach (var attribute in Attributes) + { + builder.AppendLine(attribute); + } + } + + if (Modifiers is not []) + { + builder.Append($"{Modifiers} "); + } + + builder.Append($"{Name}"); + + if (TypeParameters is not []) + { + builder.Append('<'); + + for (var i = 0; i < TypeParameters.Length; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (i > 0) + { + builder.Append(", "); + } + + var parameter = TypeParameters[i]; + builder.Append(parameter); + } + + builder.Append('>'); + } + + if (BaseTypeList is not []) + { + builder.Append(" : "); + + for (var i = 0; i < BaseTypeList.Length; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (i > 0) + { + builder.Append(", "); + } + + var baseType = BaseTypeList[i]; + builder.Append(baseType); + } + } + + builder.AppendLine() + .AppendLine('{') + .Indent() + .Append(Body) + .Detent() + .AppendLine('}'); + } + + /// + public Boolean Equals(TypeComponent other) + { + if (other.Modifiers != Modifiers) + { + return false; + } + + if (other.Modifiers != Modifiers) + { + return false; + } + + if (other.Name != Name) + { + return false; + } + + if (!EqualityComparer.Default.Equals(other.Body, Body)) + { + return false; + } + + if (!ImmutableArrayEqualityComparer.Equals(other.TypeParameters, TypeParameters)) + { + return false; + } + + if (!ImmutableArrayEqualityComparer.Equals(other.BaseTypeList, BaseTypeList)) + { + return false; + } + + if (!ImmutableArrayEqualityComparer.Equals(other.Attributes, Attributes)) + { + return false; + } + + return true; + } + + /// + public override Int32 GetHashCode() + { + var hc = new HashCode(); + + hc.Add(Modifiers); + hc.Add(Modifiers); + hc.Add(Name); + hc.Add(Body, EqualityComparer.Default); + hc.Add(TypeParameters, ImmutableArrayEqualityComparer.Default); + hc.Add(BaseTypeList, ImmutableArrayEqualityComparer.Default); + hc.Add(Attributes, ImmutableArrayEqualityComparer.Default); + + var result = hc.ToHashCode(); + + return result; + } +} diff --git a/Lyra/TypeNameComponent.cs b/Lyra/TypeNameComponent.cs new file mode 100644 index 0000000..63034ae --- /dev/null +++ b/Lyra/TypeNameComponent.cs @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +using System; +using System.Threading; + +/// +/// Represents the name of a type. +/// +/// +/// This type supports value equality. +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +internal readonly record struct TypeNameComponent : ICSharpSourceComponent +{ + /// + /// Initializes a new instance. + /// + /// + /// The type whose name to append. + /// + public TypeNameComponent(Type type) + { + _type = type; + _typeName = null; + } + + /// + /// Initializes a new instance. + /// + /// + /// The name to append. + /// + public TypeNameComponent(String typeName) + { + _type = null; + _typeName = typeName; + } + + private readonly Type? _type; + private readonly String? _typeName; + + /// + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (_type is not null) + { + builder.AppendTypeName(_type); + } + else if (_typeName is not null) + { + builder.Append(_typeName); + } + } +} diff --git a/Lyra/WhitespaceInterpolationIndentationDetector.cs b/Lyra/WhitespaceInterpolationIndentationDetector.cs new file mode 100644 index 0000000..4d39c54 --- /dev/null +++ b/Lyra/WhitespaceInterpolationIndentationDetector.cs @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +using System; + +/// +/// Detects leading whitespace as interpolation indentation. +/// +/// +/// For example, given the following interpolated string: +/// +/// $" foo{"\nbar\nbaz"}" +/// +/// , the builder using this detector strategy will build the following string +/// (lines are surrounded with single quotes for clarity and not emitted by the builder): +/// +/// ' foo' +/// ' bar' +/// ' baz' +/// +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +internal sealed class WhitespaceInterpolationIndentationDetector : IInterpolationIndentationDetector +{ + private WhitespaceInterpolationIndentationDetector() + { + } + + /// + /// Gets the singleton instance. + /// + public static WhitespaceInterpolationIndentationDetector Instance { get; } = new(); + + /// + public Boolean TryDetectIndentation( + ReadOnlyMemory text, + InterpolationIndentationDetectorContext context, + out ReadOnlyMemory indentation) + { + var whitespaceCount = 0; + while (text.Span.Length > whitespaceCount && Char.IsWhiteSpace(text.Span[whitespaceCount])) + { + whitespaceCount++; + } + + if (whitespaceCount is 0) + { + indentation = default; + return false; + } + + indentation = text[..whitespaceCount]; + return true; + } +} From 13ff8b41e4acfe98472ce05922a2508a8004d876 Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Thu, 30 Oct 2025 00:11:34 +0100 Subject: [PATCH 09/36] update README.md --- Lyra/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lyra/README.md b/Lyra/README.md index e02068d..a3174d6 100644 --- a/Lyra/README.md +++ b/Lyra/README.md @@ -23,10 +23,10 @@ dotnet add package RhoMicro.CodeAnalysis.Lyra --version 1.0.0 PackageReference: ```xml - - - - + + all + runtime; build; native; contentfiles; analyzers + ``` ## How To Use From 54efcc1707061a235c73549360414d2cfb7b0d4a Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Thu, 30 Oct 2025 00:20:26 +0100 Subject: [PATCH 10/36] update lyra version --- Lyra/Lyra.csproj | 2 +- Lyra/README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Lyra/Lyra.csproj b/Lyra/Lyra.csproj index 85e029b..a0188df 100644 --- a/Lyra/Lyra.csproj +++ b/Lyra/Lyra.csproj @@ -6,7 +6,7 @@ true true true - + diff --git a/Lyra/README.md b/Lyra/README.md index a3174d6..9d161a1 100644 --- a/Lyra/README.md +++ b/Lyra/README.md @@ -17,13 +17,13 @@ This project is licensed under the `MPL-2.0` license. .NET CLI: ``` -dotnet add package RhoMicro.CodeAnalysis.Lyra --version 1.0.0 +dotnet add package RhoMicro.CodeAnalysis.Lyra --version 1.0.1 ``` PackageReference: ```xml - + all runtime; build; native; contentfiles; analyzers From 18acb18137731c57950dccf64d5856af24391a10 Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Sun, 7 Dec 2025 17:54:41 +0100 Subject: [PATCH 11/36] add polyfills generator --- Polyfills/Generator.cs | 15 ++++++++++ Polyfills/Polyfills.csproj | 19 ++++++++++++ .../Generic/KeyValuePairExtensions.cs | 30 +++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 Polyfills/Generator.cs create mode 100644 Polyfills/Polyfills.csproj create mode 100644 Polyfills/System/Collections/Generic/KeyValuePairExtensions.cs diff --git a/Polyfills/Generator.cs b/Polyfills/Generator.cs new file mode 100644 index 0000000..155035b --- /dev/null +++ b/Polyfills/Generator.cs @@ -0,0 +1,15 @@ +namespace RhoMicro.CodeAnalysis.Polyfills; + +using Generated; +using Microsoft.CodeAnalysis; + +/// +/// Generates polyfills for netstandard2 libraries. +/// +[Generator(LanguageNames.CSharp)] +public class Generator : IIncrementalGenerator +{ + /// + public void Initialize(IncrementalGeneratorInitializationContext context) + => IncludedFileSources.RegisterToContext(context); +} diff --git a/Polyfills/Polyfills.csproj b/Polyfills/Polyfills.csproj new file mode 100644 index 0000000..7181c26 --- /dev/null +++ b/Polyfills/Polyfills.csproj @@ -0,0 +1,19 @@ + + + + netstandard2.0 + preview + enable + true + $(DefineConstants);RHOMICRO_CODEANALYSIS_POLYFILLS + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/Polyfills/System/Collections/Generic/KeyValuePairExtensions.cs b/Polyfills/System/Collections/Generic/KeyValuePairExtensions.cs new file mode 100644 index 0000000..7a017d8 --- /dev/null +++ b/Polyfills/System/Collections/Generic/KeyValuePairExtensions.cs @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace System.Collections.Generic; + +/// +/// Provides extensions for instances of . +/// +#if RHOMICRO_CODEANALYSIS_POLYFILLS +[RhoMicro.CodeAnalysis.IncludeFile] +#endif +internal static class KeyValuePairExtensions +{ + extension(KeyValuePair kvp) + { + /// + /// Deconstructs the into its key and value. + /// + /// + /// The key contained in the . + /// + /// + /// The value contained in the . + /// + public void Deconstruct(out TKey key, out TValue value) + { + key = kvp.Key; + value = kvp.Value; + } + } +} From 1cd62df3fa0ab9bb8d1534c0088e59e2e6fbdcb3 Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Tue, 9 Dec 2025 02:53:23 +0100 Subject: [PATCH 12/36] implement janus --- Janus.Analyzers/AnalyzerReleases.Shipped.md | 25 + Janus.Analyzers/AnalyzerReleases.Unshipped.md | 0 .../Components/ComponentFactory.cs | 105 ++ .../Components/ConstructorComponent.cs | 133 ++ .../Components/EqualityComponent.cs | 86 + .../Components/EqualityOperatorComponent.cs | 40 + .../Components/FactoriesComponent.cs | 250 +++ .../Components/FactoryComponent.cs | 184 ++ Janus.Analyzers/Components/FieldsComponent.cs | 89 + .../Components/InspectionsComponent.cs | 236 +++ .../Components/JsonConverterComponent.cs | 273 +++ .../Components/MappingComponent.cs | 76 + .../Components/OperatorsComponent.cs | 50 + .../Components/PropertiesComponent.cs | 102 ++ Janus.Analyzers/Components/SwitchComponent.cs | 413 +++++ .../Components/ToStringComponent.cs | 110 ++ Janus.Analyzers/Components/UnionComponent.cs | 176 ++ .../UnionTypeMetadataNameComponent.cs | 37 + .../Components/UnionTypeNameComponent.cs | 150 ++ .../UnmanagedVariantsContainerComponent.cs | 95 + .../Components/ValidationComponent.cs | 73 + .../Components/VariantAccessorComponent.cs | 38 + .../Components/VariantGroupKindsComponent.cs | 68 + .../Components/VariantGroupModelComponent.cs | 280 +++ .../Components/VariantKindComponent.cs | 68 + .../Components/VariantModelComponent.cs | 171 ++ .../Components/VariantsSwitchComponent.cs | 60 + Janus.Analyzers/DiagnosticDescriptors.cs | 166 ++ Janus.Analyzers/DiagnosticIds.cs | 31 + Janus.Analyzers/Janus.Analyzers.csproj | 40 + .../JanusAnalyzer.AttributeContext.cs | 212 +++ Janus.Analyzers/JanusAnalyzer.cs | 1623 +++++++++++++++++ Janus.Analyzers/JanusGenerator.cs | 212 +++ Janus.Analyzers/Models/ContainingTypeModel.cs | 11 + Janus.Analyzers/Models/TypeNames.cs | 16 + Janus.Analyzers/Models/UnionModel.cs | 323 ++++ .../Models/UnionModelValidation.cs | 215 +++ .../Models/UnionTypeAttribute.Model.cs | 149 ++ Janus.Analyzers/Models/UnionTypeKind.cs | 9 + .../UnionTypeSettingsAttribute.Model.cs | 34 + Janus.Analyzers/Models/VariantTypeKind.cs | 11 + Janus.Analyzers/Models/VariantTypeModel.cs | 26 + .../Properties/launchSettings.json | 8 + Janus.Analyzers/UnionTypeAttribute.cs | 146 ++ Janus.Analyzers/UnionTypeSettingsAttribute.cs | 126 ++ Janus.CodeFixes/Janus.CodeFixes.csproj | 19 + Janus.CodeFixes/JanusCodeFixProvider.cs | 124 ++ Janus.Library/IUnion.cs | 52 + Janus.Library/IUnionFactory.cs | 47 + Janus.Library/Janus.Library.csproj | 16 + Janus.Library/NotNullWhenAttribute.cs | 28 + Janus.Library/README.md | 1 + .../AsPropertyTests.cs | 12 +- Janus.Tests.EndToEnd/EqualityTests.cs | 20 + Janus.Tests.EndToEnd/FactoryTests.cs | 16 + .../GlobalUsings.cs | 1 - .../IsPropertyTests.cs | 12 +- .../Janus.Tests.EndToEnd.csproj | 31 + .../JsonConverterTests.cs | 28 +- Janus.Tests.EndToEnd/NullableTests.cs | 37 + .../ReadMeAssertions.cs | 39 +- .../RelatedTypeConversionTests.cs | 71 +- .../RepresentableTypeConversionTests.cs | 12 +- Janus.Tests.EndToEnd/ToStringTests.cs | 79 + .../TryAsFunctionsTests.cs | 24 +- Janus.Tests/CodeFixTests.cs | 30 + Janus.Tests/Janus.Tests.csproj | 41 + Janus.Tests/JanusAnalyzerTests.cs | 421 +++++ Janus.Tests/JanusTest.cs | 39 + ...oadResolutionPriorityAttributeGenerator.cs | 51 + Janus.Tests/xunit.runner.json | 3 + Janus/Janus.csproj | 49 + .../Logo_Explorations.odg | Bin {UnionsGenerator => Janus}/PackageLogo.svg | 0 {UnionsGenerator => Janus}/README.md | 0 {UnionsGenerator => Janus}/ReadmeLogo.svg | 0 Janus/requirements.md | 123 ++ 77 files changed, 8079 insertions(+), 93 deletions(-) create mode 100644 Janus.Analyzers/AnalyzerReleases.Shipped.md create mode 100644 Janus.Analyzers/AnalyzerReleases.Unshipped.md create mode 100644 Janus.Analyzers/Components/ComponentFactory.cs create mode 100644 Janus.Analyzers/Components/ConstructorComponent.cs create mode 100644 Janus.Analyzers/Components/EqualityComponent.cs create mode 100644 Janus.Analyzers/Components/EqualityOperatorComponent.cs create mode 100644 Janus.Analyzers/Components/FactoriesComponent.cs create mode 100644 Janus.Analyzers/Components/FactoryComponent.cs create mode 100644 Janus.Analyzers/Components/FieldsComponent.cs create mode 100644 Janus.Analyzers/Components/InspectionsComponent.cs create mode 100644 Janus.Analyzers/Components/JsonConverterComponent.cs create mode 100644 Janus.Analyzers/Components/MappingComponent.cs create mode 100644 Janus.Analyzers/Components/OperatorsComponent.cs create mode 100644 Janus.Analyzers/Components/PropertiesComponent.cs create mode 100644 Janus.Analyzers/Components/SwitchComponent.cs create mode 100644 Janus.Analyzers/Components/ToStringComponent.cs create mode 100644 Janus.Analyzers/Components/UnionComponent.cs create mode 100644 Janus.Analyzers/Components/UnionTypeMetadataNameComponent.cs create mode 100644 Janus.Analyzers/Components/UnionTypeNameComponent.cs create mode 100644 Janus.Analyzers/Components/UnmanagedVariantsContainerComponent.cs create mode 100644 Janus.Analyzers/Components/ValidationComponent.cs create mode 100644 Janus.Analyzers/Components/VariantAccessorComponent.cs create mode 100644 Janus.Analyzers/Components/VariantGroupKindsComponent.cs create mode 100644 Janus.Analyzers/Components/VariantGroupModelComponent.cs create mode 100644 Janus.Analyzers/Components/VariantKindComponent.cs create mode 100644 Janus.Analyzers/Components/VariantModelComponent.cs create mode 100644 Janus.Analyzers/Components/VariantsSwitchComponent.cs create mode 100644 Janus.Analyzers/DiagnosticDescriptors.cs create mode 100644 Janus.Analyzers/DiagnosticIds.cs create mode 100644 Janus.Analyzers/Janus.Analyzers.csproj create mode 100644 Janus.Analyzers/JanusAnalyzer.AttributeContext.cs create mode 100644 Janus.Analyzers/JanusAnalyzer.cs create mode 100644 Janus.Analyzers/JanusGenerator.cs create mode 100644 Janus.Analyzers/Models/ContainingTypeModel.cs create mode 100644 Janus.Analyzers/Models/TypeNames.cs create mode 100644 Janus.Analyzers/Models/UnionModel.cs create mode 100644 Janus.Analyzers/Models/UnionModelValidation.cs create mode 100644 Janus.Analyzers/Models/UnionTypeAttribute.Model.cs create mode 100644 Janus.Analyzers/Models/UnionTypeKind.cs create mode 100644 Janus.Analyzers/Models/UnionTypeSettingsAttribute.Model.cs create mode 100644 Janus.Analyzers/Models/VariantTypeKind.cs create mode 100644 Janus.Analyzers/Models/VariantTypeModel.cs create mode 100644 Janus.Analyzers/Properties/launchSettings.json create mode 100644 Janus.Analyzers/UnionTypeAttribute.cs create mode 100644 Janus.Analyzers/UnionTypeSettingsAttribute.cs create mode 100644 Janus.CodeFixes/Janus.CodeFixes.csproj create mode 100644 Janus.CodeFixes/JanusCodeFixProvider.cs create mode 100644 Janus.Library/IUnion.cs create mode 100644 Janus.Library/IUnionFactory.cs create mode 100644 Janus.Library/Janus.Library.csproj create mode 100644 Janus.Library/NotNullWhenAttribute.cs create mode 100644 Janus.Library/README.md rename {UnionsGenerator.EndToEnd.Tests => Janus.Tests.EndToEnd}/AsPropertyTests.cs (83%) create mode 100644 Janus.Tests.EndToEnd/EqualityTests.cs create mode 100644 Janus.Tests.EndToEnd/FactoryTests.cs rename {UnionsGenerator.Tests => Janus.Tests.EndToEnd}/GlobalUsings.cs (56%) rename {UnionsGenerator.EndToEnd.Tests => Janus.Tests.EndToEnd}/IsPropertyTests.cs (83%) create mode 100644 Janus.Tests.EndToEnd/Janus.Tests.EndToEnd.csproj rename {UnionsGenerator.EndToEnd.Tests => Janus.Tests.EndToEnd}/JsonConverterTests.cs (64%) create mode 100644 Janus.Tests.EndToEnd/NullableTests.cs rename {UnionsGenerator.EndToEnd.Tests => Janus.Tests.EndToEnd}/ReadMeAssertions.cs (78%) rename {UnionsGenerator.EndToEnd.Tests => Janus.Tests.EndToEnd}/RelatedTypeConversionTests.cs (76%) rename {UnionsGenerator.EndToEnd.Tests => Janus.Tests.EndToEnd}/RepresentableTypeConversionTests.cs (80%) create mode 100644 Janus.Tests.EndToEnd/ToStringTests.cs rename {UnionsGenerator.EndToEnd.Tests => Janus.Tests.EndToEnd}/TryAsFunctionsTests.cs (75%) create mode 100644 Janus.Tests/CodeFixTests.cs create mode 100644 Janus.Tests/Janus.Tests.csproj create mode 100644 Janus.Tests/JanusAnalyzerTests.cs create mode 100644 Janus.Tests/JanusTest.cs create mode 100644 Janus.Tests/OverloadResolutionPriorityAttributeGenerator.cs create mode 100644 Janus.Tests/xunit.runner.json create mode 100644 Janus/Janus.csproj rename {UnionsGenerator => Janus}/Logo_Explorations.odg (100%) rename {UnionsGenerator => Janus}/PackageLogo.svg (100%) rename {UnionsGenerator => Janus}/README.md (100%) rename {UnionsGenerator => Janus}/ReadmeLogo.svg (100%) create mode 100644 Janus/requirements.md diff --git a/Janus.Analyzers/AnalyzerReleases.Shipped.md b/Janus.Analyzers/AnalyzerReleases.Shipped.md new file mode 100644 index 0000000..7df4a24 --- /dev/null +++ b/Janus.Analyzers/AnalyzerReleases.Shipped.md @@ -0,0 +1,25 @@ +## Release 23.0.0-rc0 + +### New Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|-------------------- + RMJ0001 | Usage | Warning | `ToStringSetting` is ignored due to user defined `ToString` implementation + RMJ0002 | Usage | Error | `record` unions are disallowed + RMJ0003 | Usage | Error | Generic unions cannot be json serializable + RMJ0004 | Usage | Error | No more than 31 variant groups may be defined + RMJ0005 | Usage | Error | `static` unions are disallowed + RMJ0006 | Usage | Error | Duplicate variant names are disallowed + RMJ0007 | Design | Warning | At least one unmanaged or managed struct or nullable reference type variant must be defined for struct unions + RMJ0008 | Design | Warning | `interface` variants are excluded from conversion operators + RMJ0009 | Usage | Error | Duplicate variant types are disallowed + RMJ0010 | Usage | Error | `object` cannot be used as a variant + RMJ0012 | Usage | Error | Variants that are the union itself are disallowed + RMJ0013 | Usage | Error | Explicitly defined base classes are disallowed + RMJ0014 | Usage | Warning | Prefer `Nullable` over `IsNullable = true` + RMJ0015 | Usage | Error | `Nullable` and `T` variants for the same type `T` are disallowed + RMJ0016 | Usage | Warning | `UnionTypeSettings` are ignored if missing `UnionTypeAttribute` + RMJ0017 | Usage | Warning | Duplicate variant group names are ignored + RMJ0018 | Design | Warning | Class unions should be sealed + RMJ0019 | Usage | Error | `ValueType` cannot be used as a variant of struct unions + RMJ0020 | Usage | Error | `ref struct` cannot be union diff --git a/Janus.Analyzers/AnalyzerReleases.Unshipped.md b/Janus.Analyzers/AnalyzerReleases.Unshipped.md new file mode 100644 index 0000000..e69de29 diff --git a/Janus.Analyzers/Components/ComponentFactory.cs b/Janus.Analyzers/Components/ComponentFactory.cs new file mode 100644 index 0000000..98e892a --- /dev/null +++ b/Janus.Analyzers/Components/ComponentFactory.cs @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using Janus; +using Library.Models.Collections; + +partial class ComponentFactory +{ + partial class Docs + { + public static DocsCommentElementComponent SwitchReturns() => + Returns("The result produced by the invoked handler."); + + public static DocsCommentElementComponent SwitchSummary() => + Summary("Invokes a handler based on the represented variant."); + + public static DocsCommentElementComponent<(String, String)> SwitchStateParam() => + Param("state", "The state to pass to handlers."); + + public static DocsCommentElementComponent<(String, String)> SwitchStateTypeparam() => + TypeParam("TState", "The type of state to pass to handlers."); + + public static DocsCommentElementComponent<(String, String)> SwitchResultTypeparam() => + TypeParam("TResult", "The type of result returned by handlers."); + + public static DocsCommentElementComponent<(String, String)> SwitchDefaultHandlerParam() => + Param("defaultHandler", "The handler to invoke if no handler for the represented variant was passed."); + + public static DocsCommentElementComponent<(String, String)> SwitchDefaultResultParam() => + Param("defaultResult", "The result to return if no handler for the represented variant was passed."); + + public static StrategyComponent SwitchHandlerParams(UnionModel model) => + Create(model, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append(ComponentFactory.List(m.Variants, static (v, i, l, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append(SwitchHandlerParam(v)); + }, separator: "\n")); + }); + + public static + DocsCommentElementComponent<( + String, + UnionTypeAttribute.Model, + Action)> + SwitchHandlerParam(UnionTypeAttribute.Model variant) => + Param($"on{variant.Name}", variant, static (v, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The handler to invoke if the union is of the {C(v.Name)} variant."); + }); + } + + public static ListComponent, TElement, String> List( + EquatableList list, + Action append, + String separator = "", + String terminator = "") + => List, TElement>( + list, + append, + separator, + terminator); + + public static ListComponent, String, String> List( + EquatableList list, + Action append, + String separator = "", + String terminator = "") + => List, String>( + list, + append, + separator, + terminator); + + public static ListComponent, UnionTypeAttribute.Model, String> List( + EquatableList list, + Action append, + String separator = "", + String terminator = "") + => List, UnionTypeAttribute.Model>( + list, + append, + separator, + terminator); + + public static ListComponent List( + String[] list, + Action append, + String separator = "", + String terminator = "") + => List( + list, + append, + separator, + terminator); +} diff --git a/Janus.Analyzers/Components/ConstructorComponent.cs b/Janus.Analyzers/Components/ConstructorComponent.cs new file mode 100644 index 0000000..70c5475 --- /dev/null +++ b/Janus.Analyzers/Components/ConstructorComponent.cs @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using System.Runtime.CompilerServices; +using Lyra; +using static Lyra.ComponentFactory; +using static Lyra.ComponentFactory.Docs; + +internal readonly record struct ConstructorComponent(UnionModel Model) : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var body = Create(this, static (@this, b, _) => + { + var model = @this.Model; + + foreach (var variant in model.Variants) + { + b.AppendLine( + $$""" + {{Summary("Initializes a new instance.")}} + {{Param("value", "The variant value to initialize the new instance with.")}} + [{{typeof(OverloadResolutionPriorityAttribute)}}(1)] + public {{model.Name}}({{variant.Type.NullableName}} value) : this(value, validate: true) { } + + {{Summary("Initializes a new instance.")}} + {{Param("value", "The variant value to initialize the new instance with.")}} + {{Param("validate", "Indicates whether to validate the value.")}} + private {{model.Name}}({{variant.Type.NullableName}} value, bool validate) + { + if (validate) + { + var isValid = true; + Validate(value, throwIfInvalid: true, ref isValid); + } + + {{Create(variant, static (v, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + switch (v.Type.Kind) + { + case VariantTypeKind.Unmanaged: + b.Append($"this._unmanagedVariantsContainer = new UnmanagedVariantsContainer(value);"); + break; + case VariantTypeKind.Value or VariantTypeKind.Unknown: + b.Append($"this._{v.Name} = value;"); + break; + case VariantTypeKind.Reference: + b.Append("this._referenceVariantsContainer = value;"); + break; + } + })}} + this.Variant = VariantModel.{{variant.Name}}; + } + """ + ); + } + + b.Append( + $$""" + {{Summary("Initializes a new instance.")}} + {{Param("value", "The instance whose variant value to use when initializing the new instance.")}} + [{{typeof(OverloadResolutionPriorityAttribute)}}(0)] + public {{model.Name}}({{new UnionTypeNameComponent(model)}} value) + { + {{Create(model, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var unmanagedAssigned = false; + var referenceAssigned = false; + + for (var i = 0; i < m.Variants.Count; i++) + { + ct.ThrowIfCancellationRequested(); + + var variant = m.Variants[i]; + switch (variant.Type.Kind) + { + case VariantTypeKind.Unmanaged: + if (unmanagedAssigned) + { + continue; + } + + if (i is not 0) + { + b.AppendLine(); + } + + b.Append("this._unmanagedVariantsContainer = value._unmanagedVariantsContainer;"); + unmanagedAssigned = true; + break; + case VariantTypeKind.Reference: + if (referenceAssigned) + { + continue; + } + + if (i is not 0) + { + b.AppendLine(); + } + + referenceAssigned = true; + b.Append("this._referenceVariantsContainer = value._referenceVariantsContainer;"); + break; + case VariantTypeKind.Value or VariantTypeKind.Unknown: + if (i is not 0) + { + b.AppendLine(); + } + + b.Append($"this._{variant.Name} = value._{variant.Name};"); + break; + } + } + })}} + this.Variant = value.Variant; + } + """ + ); + }); + + var region = Region("Constructors", body); + + builder.Append(region); + } +} diff --git a/Janus.Analyzers/Components/EqualityComponent.cs b/Janus.Analyzers/Components/EqualityComponent.cs new file mode 100644 index 0000000..0cd6488 --- /dev/null +++ b/Janus.Analyzers/Components/EqualityComponent.cs @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using Lyra; +using static Lyra.ComponentFactory; +using static Lyra.ComponentFactory.Docs; + +internal readonly record struct EqualityComponent(UnionModel Model) : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var methods = ComponentFactory.Create(Model, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $$""" + {{Inheritdoc()}} + public override bool Equals(object? obj) => obj is {{new UnionTypeNameComponent(m)}} other && Equals(other); + + {{Inheritdoc()}} + public bool Equals({{new UnionTypeNameComponent(m, RenderNullable: true)}} other) + { + {{ComponentFactory.Create(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + if (m.TypeKind is not UnionTypeKind.Class) + { + return; + } + + b.Append( + $$""" + if(other is null) + { + return false; + } + + if(object.ReferenceEquals(this, other)) + { + return true; + } + """ + ); + })}} + + if(Variant != other.Variant) + { + return false; + } + + {{new VariantsSwitchComponent(m, static (v, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"return global::System.Collections.Generic.EqualityComparer<{v.Type.NullableName}>.Default.Equals({new VariantAccessorComponent(v)}, {new VariantAccessorComponent(v, InstanceExpression: "other")});"); + })}} + } + + {{Inheritdoc()}} + public override int GetHashCode() + { + {{new VariantsSwitchComponent(m, static (v, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"return {typeof(HashCode)}.Combine(Variant, {new VariantAccessorComponent(v)});"); + })}} + } + + {{new EqualityOperatorComponent(m)}} + + {{new ToStringComponent(m)}} + """ + ); + }); + + var region = Region("Equality & ToString", methods); + + builder.Append(region); + } +} diff --git a/Janus.Analyzers/Components/EqualityOperatorComponent.cs b/Janus.Analyzers/Components/EqualityOperatorComponent.cs new file mode 100644 index 0000000..ed11b31 --- /dev/null +++ b/Janus.Analyzers/Components/EqualityOperatorComponent.cs @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using Lyra; +using static Lyra.ComponentFactory; +using static Lyra.ComponentFactory.Docs; + +internal readonly record struct EqualityOperatorComponent(UnionModel Model) : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (Model is + { + Settings.EqualityOperatorsSetting: EqualityOperatorsSetting.OmitOperators + } or not + { + Settings.EqualityOperatorsSetting: EqualityOperatorsSetting.EmitOperators + } and not + { + Settings.EqualityOperatorsSetting: EqualityOperatorsSetting.EmitOperatorsIfValueType, + TypeKind: UnionTypeKind.Class + }) + { + return; + } + + builder.Append($$""" + {{Inheritdoc()}} + public static bool operator ==({{new UnionTypeNameComponent(Model)}} x, {{new UnionTypeNameComponent(Model)}} y) => + global::System.Collections.Generic.EqualityComparer<{{new UnionTypeNameComponent(Model)}}>.Default.Equals(x, y); + {{Inheritdoc()}} + public static bool operator !=({{new UnionTypeNameComponent(Model)}} x, {{new UnionTypeNameComponent(Model)}} y) => + !(x == y); + """ + ); + } +} diff --git a/Janus.Analyzers/Components/FactoriesComponent.cs b/Janus.Analyzers/Components/FactoriesComponent.cs new file mode 100644 index 0000000..2ee34ac --- /dev/null +++ b/Janus.Analyzers/Components/FactoriesComponent.cs @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using System.Diagnostics.CodeAnalysis; +using Lyra; +using static Lyra.ComponentFactory; +using static Lyra.ComponentFactory.Docs; + +internal readonly record struct FactoriesComponent(UnionModel Model) : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var methods = Create(Model, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.AppendLine( + $$""" + {{Summary(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Creates an instance of {Cref(m.DocsCommentId)}."); + })}} + {{Param("value", m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The value to create an instance of {Cref(m.DocsCommentId)} from."); + })}} + {{TypeParam("TVariant", m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The type of the value to create an instance of {Cref(m.DocsCommentId)} from."); + })}} + {{Remarks(static (b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"If more than one variant of the union implement {TypeParamRef("TVariant")}, the selected variant is not specified and may change in future versions."); + })}} + {{Returns(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The new instance of {Cref(m.DocsCommentId)}."); + })}} + public static {{new UnionTypeNameComponent(m)}} Create( + TVariant value) + => new Factory().Create(value); + + {{Summary(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Attempts to create an instance of {Cref(m.DocsCommentId)}."); + })}} + {{Param("value", m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The value to create an instance of {Cref(m.DocsCommentId)} from."); + })}} + {{Param("union", m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $""" + The instance of {Cref(m.DocsCommentId)} if one could be created; otherwise, + {(m.TypeKind is UnionTypeKind.Class ? Langword("null") : Langword("default"))}. + """); + })}} + {{TypeParam("TVariant", m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The type of the value to create an instance of {Cref(m.DocsCommentId)} from."); + })}} + {{Remarks(static (b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"If more than one variant of the union implement {TypeParamRef("TVariant")}, the selected variant is not specified and may change in future versions."); + })}} + {{Returns(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"{Langword("true")} if an instance of {Cref(m.DocsCommentId)} could be created; otherwise, {Langword("false")}."); + })}} + public static bool TryCreate( + TVariant value, + [{{typeof(NotNullWhenAttribute)}}(true)] + out {{new UnionTypeNameComponent(m, RenderNullable: true)}} union) + => new Factory().TryCreate(value, out union); + + {{Summary(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Creates an instance of {Cref(m.DocsCommentId)}."); + })}} + {{Param("value", m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The value to create an instance of {Cref(m.DocsCommentId)} from."); + })}} + {{Returns(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The new instance of {Cref(m.DocsCommentId)}."); + })}} + public static {{new UnionTypeNameComponent(m)}} Create( + {{new UnionTypeNameComponent(m)}} value) + => new(value); + + {{Summary(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Attempts to create an instance of {Cref(m.DocsCommentId)}."); + })}} + {{Param("value", m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The value to create an instance of {Cref(m.DocsCommentId)} from."); + })}} + {{Param("union", m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $""" + The instance of {Cref(m.DocsCommentId)} if one could be created; otherwise, + {(m.TypeKind is UnionTypeKind.Class ? Langword("null") : Langword("default"))}. + """); + })}} + {{Returns(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"{Langword("true")} if an instance of {Cref(m.DocsCommentId)} could be created; otherwise, {Langword("false")}."); + })}} + public static bool TryCreate( + {{new UnionTypeNameComponent(m)}} value, + [{{typeof(NotNullWhenAttribute)}}(true)] + out {{new UnionTypeNameComponent(m, RenderNullable: true)}} union) + { + union = Create(value); + + return true; + } + + """ + ); + + for (var i = 0; i < m.Variants.Count; i++) + { + ct.ThrowIfCancellationRequested(); + + if (i is not 0) + { + b.AppendLine().AppendLine(); + } + + var variant = m.Variants[i]; + + b.Append( + $$""" + {{Summary(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Creates an instance of {Cref(m.DocsCommentId)}."); + })}} + {{Param("value", m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The value to create an instance of {Cref(m.DocsCommentId)} from."); + })}} + {{Returns(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The new instance of {Cref(m.DocsCommentId)}."); + })}} + public static {{new UnionTypeNameComponent(m)}} Create( + {{variant.Type.NullableName}} value) + => new {{new UnionTypeNameComponent(m)}}(value, validate: true); + + {{Summary(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Attempts to create an instance of {Cref(m.DocsCommentId)}."); + })}} + {{Param("value", m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The value to create an instance of {Cref(m.DocsCommentId)} from."); + })}} + {{Param("union", m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $""" + The instance of {Cref(m.DocsCommentId)} if one could be created; otherwise, + {(m.TypeKind is UnionTypeKind.Class ? Langword("null") : Langword("default"))}. + """); + })}} + {{Returns(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"{Langword("true")} if an instance of {Cref(m.DocsCommentId)} could be created; otherwise, {Langword("false")}."); + })}} + public static bool TryCreate( + {{variant.Type.NullableName}} value, + [{{typeof(NotNullWhenAttribute)}}(true)] + out {{new UnionTypeNameComponent(m, RenderNullable: true)}} union) + { + var isValid = true; + Validate(value, throwIfInvalid: false, ref isValid); + union = isValid + ? new {{new UnionTypeNameComponent(m)}}(value, validate: false) + : default; + + return isValid; + } + """ + ); + } + }); + + var region = Region("Factories", methods); + + builder.Append(region); + } +} diff --git a/Janus.Analyzers/Components/FactoryComponent.cs b/Janus.Analyzers/Components/FactoryComponent.cs new file mode 100644 index 0000000..3dbbdcb --- /dev/null +++ b/Janus.Analyzers/Components/FactoryComponent.cs @@ -0,0 +1,184 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using System.Diagnostics.CodeAnalysis; +using Lyra; +using static Lyra.ComponentFactory; +using static Lyra.ComponentFactory.Docs; + +internal readonly record struct FactoryComponent(UnionModel Model) : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var members = Create( + Model, + static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.AppendLine( + $$""" + {{Summary(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Attempts to create an instance of {Cref(m.DocsCommentId)}."); + })}} + {{Param("value", m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The value to create an instance of {Cref(m.DocsCommentId)} from."); + })}} + {{Param("union", m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $""" + The instance of {Cref(m.DocsCommentId)} if one could be created; otherwise, + {(m.TypeKind is UnionTypeKind.Class ? Langword("null") : Langword("default"))}. + """); + })}} + {{TypeParam("TVariant", m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The type of the value to create an instance of {Cref(m.DocsCommentId)} from."); + })}} + {{Remarks(static (b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"If more than one variant of the union implement {TypeParamRef("TVariant")}, the selected variant is not specified and may change in future versions."); + })}} + {{Returns(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"{Langword("true")} if an instance of {Cref(m.DocsCommentId)} could be created; otherwise, {Langword("false")}."); + })}} + public {{typeof(Boolean)}} TryCreate( + TVariant value, + [{{TypeName()}}(true)] + out {{new UnionTypeNameComponent(m, RenderNullable: true)}} union) + { + switch (value) + { + {{Create(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + for (var i = 0; i < m.Variants.Count; i++) + { + ct.ThrowIfCancellationRequested(); + var variant = m.Variants[i]; + + if (i is not 0) + { + b.AppendLine(); + } + + b.Append($"case {variant.Type.Name} v: return {new UnionTypeNameComponent(m)}.TryCreate(v, out union);"); + } + })}} + case {{new UnionTypeNameComponent(m)}} v: + union = new {{new UnionTypeNameComponent(m)}}(v); + return true; + case {{m.TypeNames.IUnion}} v: return v.TryMapTo(this, out union); + default: + union = default; + return false; + } + } + + {{Summary(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Creates an instance of {Cref(m.DocsCommentId)}."); + })}} + {{Param("value", m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The value to create an instance of {Cref(m.DocsCommentId)} from."); + })}} + {{TypeParam("TVariant", m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The type of the value to create an instance of {Cref(m.DocsCommentId)} from."); + })}} + {{Remarks(static (b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"If more than one variant of the union implement {TypeParamRef("TVariant")}, the selected variant is not specified and may change in future versions."); + })}} + {{Returns(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The new instance of {Cref(m.DocsCommentId)}."); + })}} + public {{new UnionTypeNameComponent(m)}} Create(TVariant value) + { + switch(value) + { + {{Create(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + for (var i = 0; i < m.Variants.Count; i++) + { + ct.ThrowIfCancellationRequested(); + var variant = m.Variants[i]; + + if (i is not 0) + { + b.AppendLine(); + } + + b.Append($"case {variant.Type.Name} v: return new {new UnionTypeNameComponent(m)}(v);"); + } + })}} + case {{new UnionTypeNameComponent(m)}} v: return new {{new UnionTypeNameComponent(m)}}(v); + case {{m.TypeNames.IUnion}} v: return v.MapTo<{{new UnionTypeNameComponent(m)}}, Factory>(this); + default: + throw new {{typeof(ArgumentOutOfRangeException)}}(nameof(value), value, $"Unable to create an instance of '{typeof({{new UnionTypeNameComponent(m)}})}' from a value of type '{value?.GetType() ?? typeof(TVariant)}': {(value is null ? "null" : $"'{value}'")}"); + } + } + """ + ); + }); + + var type = Create((Model, members), static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (model, members) = t; + + b.Append($""" + {Summary(model, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Creates instances of {Cref(m.DocsCommentId)}."); + })} + {Type( + "private readonly struct", + "Factory", + members, + baseTypeList: [model.TypeNames.IUnionFactory])} + """); + }); + + var region = Region("Factory", type); + + builder.Append(region); + } +} diff --git a/Janus.Analyzers/Components/FieldsComponent.cs b/Janus.Analyzers/Components/FieldsComponent.cs new file mode 100644 index 0000000..3621c0d --- /dev/null +++ b/Janus.Analyzers/Components/FieldsComponent.cs @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using Lyra; +using static Lyra.ComponentFactory; +using static Lyra.ComponentFactory.Docs; + +internal readonly record struct FieldsComponent(UnionModel Model) : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var fields = Create(Model, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var unmanagedDefined = false; + var referenceDefined = false; + + for (var i = 0; i < m.Variants.Count; i++) + { + ct.ThrowIfCancellationRequested(); + + var variant = m.Variants[i]; + + switch (variant.Type.Kind) + { + case VariantTypeKind.Unmanaged: + if (unmanagedDefined) + { + continue; + } + + if (i is not 0) + { + b.AppendLine(); + } + + b.Append( + $""" + {Summary("The container used to store values for unmanaged variants of the union.")} + private readonly UnmanagedVariantsContainer _unmanagedVariantsContainer = default; + """); + unmanagedDefined = true; + break; + case VariantTypeKind.Value or VariantTypeKind.Unknown: + if (i is not 0) + { + b.AppendLine(); + } + + b.Append($""" + {Summary(variant, static (v, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The value when representing the {C(v.Name)} variant."); + })} + private readonly {variant.Type.NullableName} _{variant.Name} = default; + """); + break; + case VariantTypeKind.Reference: + if (referenceDefined) + { + continue; + } + + if (i is not 0) + { + b.AppendLine(); + } + + b.Append($""" + {Summary("The container used to store values for reference type variants.")} + private readonly object? _referenceVariantsContainer = default; + """); + referenceDefined = true; + break; + } + } + }); + + var region = Region("Fields", fields); + + builder.Append(region); + } +} diff --git a/Janus.Analyzers/Components/InspectionsComponent.cs b/Janus.Analyzers/Components/InspectionsComponent.cs new file mode 100644 index 0000000..1a11a4e --- /dev/null +++ b/Janus.Analyzers/Components/InspectionsComponent.cs @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using Lyra; +using static Lyra.ComponentFactory; +using static Lyra.ComponentFactory.Docs; + +internal readonly record struct InspectionsComponent(UnionModel Model) : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var methods = Create(Model, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + // TODO: + // when mayBeNull in Is(out u): + // comment explaining that value is guaranteed to be null if returning false, even without annotation + + var mayBeNull = m.Variants.Any(static v => + v.Type is { IsNullable: true } or { Kind: VariantTypeKind.Unknown }); + + b.Append( + $$""" + {{Summary(""" + Converts the value contained by the union to the provided type. If the variant of the union + is convertible to the provided type, a conversion will be performed; otherwise, an exception + is thrown. + """)}} + {{TypeParam("TVariant", "The type to convert the unions value to.")}} + {{Returns("The converted value.")}} + public TVariant{{(mayBeNull ? "?" : String.Empty)}} CastTo() + { + {{new VariantsSwitchComponent(m, static (v, m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var annotation = v.Type is { IsNullable: true } or { Kind: VariantTypeKind.Unknown } ? "?" : String.Empty; + + b.Append( + $""" + // The jit will optimize this conversion and prevent boxing. + var result = (TVariant{annotation})(object{annotation}){new VariantAccessorComponent(v)}; + + return result; + """ + ); + })}} + } + + {{Summary("Attempts to convert the value contained by the union to the provided type.")}} + {{TypeParam("TVariant", "The type to convert the unions value to.")}} + {{Returns(static (b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The converted value if the conversion is possible; otherwise, {Langword("default")}."); + })}} + public TVariant? As() + { + {{new VariantsSwitchComponent(m, static (v, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var annotation = v.Type is { IsNullable: true } or { Kind: VariantTypeKind.Unknown } ? "?" : String.Empty; + + b.Append( + $$""" + if(typeof(TVariant) == typeof({{v.Type.NullableValueTypeName}}) || typeof(TVariant).IsAssignableFrom(typeof({{v.Type.NullableValueTypeName}}))) + { + // The jit will optimize this conversion and prevent boxing. + var result = (TVariant{{annotation}})(object{{annotation}}){{new VariantAccessorComponent(v)}}; + + return result; + } + else + { + return default; + } + """ + ); + })}} + } + + {{Summary("Attempts to convert the value contained by the union to the provided type.")}} + {{Param("value", static (b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The converted value if the conversion is possible; otherwise, {Langword("default")}."); + })}} + {{TypeParam("TVariant", "The type to convert the unions value to.")}}{{Create(mayBeNull, static (f, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + if (!f) + { + return; + } + + b.Append($""" + + {Remarks(static (b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Because this union may have a nullable value, {ParamRef("value")} is not guaranteed to be a non-{Langword("null")} value upon returning {Langword("true")}."); + })} + """); + })}} + {{Returns(static (b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"{Langword("true")} if the conversion is possible; otherwise, {Langword("false")}."); + })}} + public bool TryCastTo({{Create(mayBeNull, static (f, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + if (f) + { + return; + } + + b.Append($"[{typeof(NotNullWhenAttribute)}(true)] "); + })}}out TVariant? value) + { + {{new VariantsSwitchComponent(m, static (v, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var annotation = v.Type is { IsNullable: true } or { Kind: VariantTypeKind.Unknown } ? "?" : String.Empty; + + b.Append( + $$""" + if(typeof(TVariant) == typeof({{v.Type.NullableValueTypeName}}) || typeof(TVariant).IsAssignableFrom(typeof({{v.Type.NullableValueTypeName}}))) + { + // The jit will optimize this conversion and prevent boxing. + value = (TVariant{{annotation}})(object{{annotation}}){{new VariantAccessorComponent(v)}}; + + return true; + } + else + { + value = default; + return false; + } + """ + ); + })}} + } + + {{Summary("Gets a value indicating whether the unions variant is convertible to the specified type.")}} + {{TypeParam("TVariant", "The type to check against the variant of the union.")}} + {{Returns(static (b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"{Langword("true")} if the variant of the union is convertible to {TypeParamRef("TVariant")}; otherwise, {Langword("false")}."); + })}} + public bool Is() + { + {{new VariantsSwitchComponent(m, static (v, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($""" + var result = typeof(TVariant) == typeof({v.Type.NullableValueTypeName}) || typeof(TVariant).IsAssignableFrom(typeof({v.Type.NullableValueTypeName})); + + return result; + """); + })}} + } + + {{List(m.Variants, static (v, i, l, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $$""" + {{Summary(v, static (v, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Attempt to get the represented value if the union is of the {C(v.Name)} variant."); + })}} + {{Param("value", v, static (v, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The unions value if the union is of the {C(v.Name)} variant; otherwise, {(v.Type.Kind is VariantTypeKind.Reference ? Langword("null") : Langword("default"))}."); + })}} + {{Remarks(v, static (v, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($""" + If the union is not of the {C(v.Name)} variant, no conversion is attempted. + In order to cast to a specific type, use {Cref("CastTo{TVariant}")} instead. + """); + })}} + {{Returns(v, static (v, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"{Langword("true")} if the union is of the {C(v.Name)}; otherwise, {Langword("false")}."); + })}} + public bool TryCastTo{{v.Name}}({{(v.Type is { IsNullable: false, Kind: VariantTypeKind.Reference } ? "[global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)]" : String.Empty)}}out {{v.Type.NullableName}}{{(v.Type is { IsNullable: false, Kind: VariantTypeKind.Reference } ? "?" : String.Empty)}} value) + { + if(Is{{v.Name}}) + { + value = CastTo{{v.Name}}; + return true; + } + + value = default; + return false; + } + """ + ); + }, "\n")}} + """ + ); + }); + + var region = Region("Inspection", methods); + + builder.Append(region); + } +} diff --git a/Janus.Analyzers/Components/JsonConverterComponent.cs b/Janus.Analyzers/Components/JsonConverterComponent.cs new file mode 100644 index 0000000..e6412f5 --- /dev/null +++ b/Janus.Analyzers/Components/JsonConverterComponent.cs @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using Lyra; +using static Lyra.ComponentFactory; +using static Lyra.ComponentFactory.Docs; + +internal readonly record struct JsonConverterComponent(UnionModel Model) : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var members = Create(Model, static (m, b, _) => + { + b.Append( + $$""" + private static readonly global::System.Collections.Generic.HashSet _knownNamingPolicies = + [ + global::System.Text.Json.JsonNamingPolicy.CamelCase, + global::System.Text.Json.JsonNamingPolicy.KebabCaseLower, + global::System.Text.Json.JsonNamingPolicy.KebabCaseUpper, + global::System.Text.Json.JsonNamingPolicy.SnakeCaseLower, + global::System.Text.Json.JsonNamingPolicy.SnakeCaseUpper + ]; + + private static readonly byte[] _variantDefaultCase = global::System.Text.Encoding.UTF8.GetBytes("Variant"); + private static readonly byte[] _variantLowerCase = global::System.Text.Encoding.UTF8.GetBytes("variant"); + private static readonly byte[] _valueDefaultCase = global::System.Text.Encoding.UTF8.GetBytes("Value"); + private static readonly byte[] _valueLowerCase = global::System.Text.Encoding.UTF8.GetBytes("value"); + + {{Inheritdoc()}} + public override {{new UnionTypeNameComponent(m, RenderNullable: true)}} Read( + ref global::System.Text.Json.Utf8JsonReader reader, + global::System.Type typeToConvert, + global::System.Text.Json.JsonSerializerOptions options) + { + if (!global::System.Text.Json.JsonElement.TryParseValue(ref reader, out var e) || + e is not { ValueKind: global::System.Text.Json.JsonValueKind.Object } unionObject) + { + throw new global::System.Text.Json.JsonException("Unable to read a union object value from the reader."); + } + + if (!TryReadVariantPropertyValue(unionObject, options, out var variantElement)) + { + throw new global::System.Text.Json.JsonException( + $"Unable to read a value for the '{nameof(Variant)}' property of the union object."); + } + + var variant = global::System.Text.Json.JsonSerializer.Deserialize(variantElement, options); + + if (!TryReadValuePropertyValue(unionObject, options, out var valueElement)) + { + throw new global::System.Text.Json.JsonException( + $"Unable to read a value for the '{nameof(Value)}' property of the union object."); + } + + {{new VariantsSwitchComponent(m, static (v, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"return global::System.Text.Json.JsonSerializer.Deserialize<{v.Type.NullableName}>(valueElement, options)"); + + if (v.Type is { IsNullable: false, Kind: VariantTypeKind.Reference }) + { + b.Append($$""" + ?? throw new global::System.Text.Json.JsonException($"Unable to deserialize a non-null value for the represented non-nullable variant '{nameof(VariantKind.{{v.Name}})}' of type '{typeof({{v.Type.NullableName}})}'.") + """ + ); + } + + b.Append(';'); + }, static (_, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + """ + var exception = new global::System.Text.Json.JsonException( + $"Unable to read a value for the '{nameof(Value)}' property of the union object, as the " + + $"'{nameof(Variant)}' property is not representing a valid variant of this union: " + + $"'{variant}'. This could be either because the union itself was not serialized " + + "correctly, or due to a bug in the 'Janus' Janussource generator that generated this union " + + "type. Please verify that the serialized data is in the correct format, and, if " + + "necessary, report an issue to the maintainer. The json used for deserialization " + + "has been attached to this exception."); + + exception.Data[$"{GetType()}.Data"] = + global::System.Text.Json.JsonSerializer.Serialize(unionObject, options); + + throw exception; + """ + ); + }, "variant")}} + } + + private bool TryReadVariantPropertyValue( + global::System.Text.Json.JsonElement unionObject, + global::System.Text.Json.JsonSerializerOptions options, + out global::System.Text.Json.JsonElement value) + => TryReadPropertyValue( + unionObject, + options, + defaultName: nameof(Variant), + defaultUtf8Name: _variantDefaultCase, + lowerCaseUtf8Name: _variantLowerCase, + out value); + + private bool TryReadValuePropertyValue( + global::System.Text.Json.JsonElement unionObject, + global::System.Text.Json.JsonSerializerOptions options, + out global::System.Text.Json.JsonElement value) + => TryReadPropertyValue( + unionObject, + options, + defaultName: nameof(Value), + defaultUtf8Name: _valueDefaultCase, + lowerCaseUtf8Name: _valueLowerCase, + out value); + + private bool TryReadPropertyValue( + global::System.Text.Json.JsonElement unionObject, + global::System.Text.Json.JsonSerializerOptions options, + string defaultName, + global::System.ReadOnlySpan defaultUtf8Name, + global::System.ReadOnlySpan lowerCaseUtf8Name, + out global::System.Text.Json.JsonElement value) + { + if (options.PropertyNamingPolicy == null || // Pascal case (for our properties) + options.PropertyNamingPolicy == global::System.Text.Json.JsonNamingPolicy.SnakeCaseUpper || + options.PropertyNamingPolicy == global::System.Text.Json.JsonNamingPolicy.KebabCaseUpper) + { + return unionObject.TryGetProperty(defaultUtf8Name, out value) || + options.PropertyNameCaseInsensitive && + unionObject.TryGetProperty(lowerCaseUtf8Name, out value); + } + + if (options.PropertyNamingPolicy == global::System.Text.Json.JsonNamingPolicy.CamelCase || + options.PropertyNamingPolicy == global::System.Text.Json.JsonNamingPolicy.SnakeCaseLower || + options.PropertyNamingPolicy == global::System.Text.Json.JsonNamingPolicy.KebabCaseLower) + { + return unionObject.TryGetProperty(lowerCaseUtf8Name, out value) || + options.PropertyNameCaseInsensitive && + unionObject.TryGetProperty(defaultUtf8Name, out value); + } + + var convertedName = options.PropertyNamingPolicy.ConvertName(defaultName); + var comparison = options.PropertyNameCaseInsensitive + ? global::System.StringComparison.OrdinalIgnoreCase + : global::System.StringComparison.Ordinal; + foreach (var property in unionObject.EnumerateObject()) + { + if (!property.Name.Equals(convertedName, comparison)) + { + continue; + } + + value = property.Value; + return true; + } + + value = default; + return false; + } + + private void WriteVariantPropertyName(global::System.Text.Json.Utf8JsonWriter writer, global::System.Text.Json.JsonSerializerOptions options) + => WritePropertyName( + writer, + options, + defaultName: nameof(Variant), + defaultUtf8Name: _variantDefaultCase, + lowerCaseUtf8Name: _variantLowerCase); + + private void WriteValuePropertyName(global::System.Text.Json.Utf8JsonWriter writer, global::System.Text.Json.JsonSerializerOptions options) + => WritePropertyName( + writer, + options, + defaultName: nameof(Value), + defaultUtf8Name: _valueDefaultCase, + lowerCaseUtf8Name: _valueLowerCase); + + private void WritePropertyName( + global::System.Text.Json.Utf8JsonWriter writer, + global::System.Text.Json.JsonSerializerOptions options, + string defaultName, + global::System.ReadOnlySpan defaultUtf8Name, + global::System.ReadOnlySpan lowerCaseUtf8Name) + { + if (options.PropertyNamingPolicy == null || + options.PropertyNamingPolicy == global::System.Text.Json.JsonNamingPolicy.SnakeCaseUpper || + options.PropertyNamingPolicy == global::System.Text.Json.JsonNamingPolicy.KebabCaseUpper) + { + writer.WritePropertyName(defaultUtf8Name); + return; + } + + if (options.PropertyNamingPolicy == global::System.Text.Json.JsonNamingPolicy.CamelCase || + options.PropertyNamingPolicy == global::System.Text.Json.JsonNamingPolicy.SnakeCaseLower || + options.PropertyNamingPolicy == global::System.Text.Json.JsonNamingPolicy.KebabCaseLower) + { + writer.WritePropertyName(lowerCaseUtf8Name); + return; + } + + var convertedName = options.PropertyNamingPolicy.ConvertName(defaultName); + writer.WritePropertyName(convertedName); + } + + {{Inheritdoc()}} + public override void Write( + global::System.Text.Json.Utf8JsonWriter writer, + {{new UnionTypeNameComponent(m)}} value, + global::System.Text.Json.JsonSerializerOptions options) + { + writer.WriteStartObject(); + WriteVariantPropertyName(writer, options); + global::System.Text.Json.JsonSerializer.Serialize(writer, value.Variant.Kind, options); + WriteValuePropertyName(writer, options); + {{new VariantsSwitchComponent(m, static (v, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $$""" + global::System.Text.Json.JsonSerializer.Serialize(writer, value.CastTo{{v.Name}}, options); + break; + """ + ); + }, static (_, b, ct) => + { + ct.ThrowIfCancellationRequested(); + b.Append("throw value.CreateUnknownVariantException();"); + }, "value.Variant.Kind")}} + + writer.WriteEndObject(); + } + """ + ); + }); + + var type = Create((Model, members), static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (model, members) = t; + + b.Append( + $""" + {Summary(model, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Implements JSON conversion logic for serializing and deserializing instances of {Cref(m.DocsCommentId)}."); + })} + {Type( + "private sealed class", + "JsonConverter", + members, + baseTypeList: + [ + TypeName( + $"global::System.Text.Json.Serialization.JsonConverter<{new UnionTypeNameComponent(model)}>") + ])} + """ + ); + }); + + var region = Region("Json Converter", type); + + builder.Append(region); + } +} diff --git a/Janus.Analyzers/Components/MappingComponent.cs b/Janus.Analyzers/Components/MappingComponent.cs new file mode 100644 index 0000000..d063cdf --- /dev/null +++ b/Janus.Analyzers/Components/MappingComponent.cs @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using System.Diagnostics.CodeAnalysis; +using Lyra; +using static Lyra.ComponentFactory; +using static Lyra.ComponentFactory.Docs; + +internal readonly record struct MappingComponent(UnionModel Model) : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var methods = Create(Model, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $$""" + {{Summary("Maps this union to another.")}} + {{Param("factory", "The factory used to create the target union instance.")}} + {{TypeParam("TUnion", "The type of union to map to.")}} + {{TypeParam("TFactory", "The type of factory used to perform the mapping operation.")}} + {{Returns("The created union instance.")}} + public TUnion MapTo( + TFactory factory) + where TFactory : global::RhoMicro.CodeAnalysis.IUnionFactory + { + {{new VariantsSwitchComponent(m, static (v, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"return factory.Create({new VariantAccessorComponent(v)});"); + })}} + } + + {{Summary("Attempts to map this union to another.")}} + {{Param("factory", "The factory used to create the target union instance.")}} + {{Param("union", static (b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The union instance if one could be created; otherwise, {Langword("default")}."); + })}} + {{TypeParam("TUnion", "The type of union to map to.")}} + {{TypeParam("TFactory", "The type of factory used to perform the mapping operation.")}} + {{Returns(static (b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"{Langword("true")} if a union instance could be created; otherwise, {Langword("false")}."); + })}} + public bool TryMapTo( + TFactory factory, + [{{typeof(NotNullWhenAttribute)}}(true)] + out TUnion? union) + where TFactory : global::RhoMicro.CodeAnalysis.IUnionFactory + { + {{new VariantsSwitchComponent(m, static (v, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"return factory.TryCreate({new VariantAccessorComponent(v)}, out union);"); + })}} + } + """ + ); + }); + + var region = Region("Mapping", methods); + + builder.Append(region); + } +} diff --git a/Janus.Analyzers/Components/OperatorsComponent.cs b/Janus.Analyzers/Components/OperatorsComponent.cs new file mode 100644 index 0000000..73dd739 --- /dev/null +++ b/Janus.Analyzers/Components/OperatorsComponent.cs @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using Lyra; +using static Lyra.ComponentFactory; +using static Lyra.ComponentFactory.Docs; + +internal readonly record struct OperatorsComponent(UnionModel Model) : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var operators = ComponentFactory.Create(Model, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + for (var i = 0; i < m.Variants.Count; i++) + { + ct.ThrowIfCancellationRequested(); + + var variant = m.Variants[i]; + + if (variant.Type.IsInterface) + { + continue; + } + + if (i is not 0) + { + b.AppendLine().AppendLine(); + } + + b.Append( + $""" + {Inheritdoc()} + public static implicit operator {new UnionTypeNameComponent(m)}({variant.Type.NullableName} value) => Create(value); + {Inheritdoc()} + public static {(m.Variants.Count is 1 ? "implicit" : "explicit")} operator {variant.Type.NullableName}({new UnionTypeNameComponent(m)} union) => union.CastTo{variant.Name}; + """ + ); + } + }); + + var region = ComponentFactory.Region("Operators", operators); + + builder.Append(region); + } +} diff --git a/Janus.Analyzers/Components/PropertiesComponent.cs b/Janus.Analyzers/Components/PropertiesComponent.cs new file mode 100644 index 0000000..8d93ded --- /dev/null +++ b/Janus.Analyzers/Components/PropertiesComponent.cs @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using System.Diagnostics.CodeAnalysis; +using Lyra; +using static Lyra.ComponentFactory; +using static Lyra.ComponentFactory.Docs; + +internal readonly record struct PropertiesComponent(UnionModel Model) : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var properties = Create(Model, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var isNullable = m.Variants.Any(v => v.Type is { IsNullable: true } or { Kind: VariantTypeKind.Unknown }); + + b.AppendLine( + $$""" + {{Summary("Gets the variant of the union.")}} + public VariantModel Variant { get; } = VariantModel.Unknown; + {{Summary("Gets the value of the union.")}} + public object{{(isNullable ? "?" : String.Empty)}} Value + { + get + { + {{new VariantsSwitchComponent(m, static (v, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"return {new VariantAccessorComponent(v)};"); + }, static (_, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append("throw CreateUnknownVariantException();"); + })}} + } + } + """ + ); + + foreach (var variant in m.Variants) + { + b.AppendLine(Summary(variant, static (v, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $"Gets a value indicating whether the union is of the {C(v.Name)} variant, which is of type {Cref(v.Type.DocsId)}."); + })) + .SetCondition(variant.Type.Kind is VariantTypeKind.Reference) + .AppendLine($"[{typeof(MemberNotNullWhenAttribute)}(true, nameof(As{variant.Name}))]") + .UnsetCondition() + .AppendLine( + $""" + public bool Is{variant.Name} => Variant.Kind is VariantKind.{variant.Name}; + {Summary(variant, static (v, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Gets the unions value if the union is of the {C(v.Name)} variant; otherwise, {(v.Type.Kind is VariantTypeKind.Reference ? Langword("null") : Langword("default"))}."); + })} + {Remarks(variant, static (v, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($""" + If the union is not of the {C(v.Name)} variant, no conversion is attempted. + In order to cast to a specific type, use {Cref("CastTo{TVariant}")} instead. + """); + })} + public {variant.Type.NullableName}{(variant.Type is { Kind: VariantTypeKind.Reference, IsNullable: false } ? "?" : String.Empty)} As{variant.Name} => {new VariantAccessorComponent(variant, NullableCast: true)}; + {Summary(variant, static (v, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Gets the represented value if the union is of the {C(v.Name)} variant; otherwise, an exception is thrown."); + })} + {Remarks(variant, static (v,b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($""" + If the union is not of the {C(v.Name)} variant, no conversion is attempted. + In order to cast to a specific type, use {Cref("CastTo{TVariant}")} instead. + """); + })} + public {variant.Type.NullableName} CastTo{variant.Name} => Variant.Kind is VariantKind.{variant.Name} ? {new VariantAccessorComponent(variant)} : throw CreateInvalidCastException(VariantKind.{variant.Name}); + """); + } + }); + + var region = Region("Properties", properties); + + builder.Append(region); + } +} diff --git a/Janus.Analyzers/Components/SwitchComponent.cs b/Janus.Analyzers/Components/SwitchComponent.cs new file mode 100644 index 0000000..4f77ec0 --- /dev/null +++ b/Janus.Analyzers/Components/SwitchComponent.cs @@ -0,0 +1,413 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using Lyra; +using static Lyra.ComponentFactory; +using static Lyra.ComponentFactory.Docs; + +internal readonly record struct SwitchComponent(UnionModel Model) : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var methods = Create(Model, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var statelessHandler = Create(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $$""" + {{SwitchSummary()}} + {{SwitchHandlerParams(m)}} + public void Switch( + {{List(m.Variants, static (v, _, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"{typeof(Action)}<{v.Type.NullableName}> on{v.Name}"); + }, separator: ",\n") + }}) + { + {{new VariantsSwitchComponent(m, static (v, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $""" + on{v.Name}.Invoke({new VariantAccessorComponent(v)}); + return; + """); + }) + }} + } + """); + }); + + var statefulHandler = Create(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $$""" + {{SwitchSummary()}} + {{SwitchStateParam()}} + {{SwitchHandlerParams(m)}} + {{SwitchStateTypeparam()}} + public void Switch( + TState state, + {{List(m.Variants, static (v, _, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"{typeof(Action)}<{v.Type.NullableName}, TState> on{v.Name}"); + }, separator: ",\n")}}) + { + {{new VariantsSwitchComponent(m, static (v, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $""" + on{v.Name}.Invoke({new VariantAccessorComponent(v)}, state); + return; + """); + })}} + } + """); + }); + + var statelessDefaultHandler = Create(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $$""" + {{SwitchSummary()}} + {{SwitchDefaultHandlerParam()}} + {{SwitchHandlerParams(m)}} + public void Switch( + {{typeof(Action)}} defaultHandler, + {{List(m.Variants, static (v, _, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"{typeof(Action)}<{v.Type.NullableName}>? on{v.Name} = null"); + }, separator: ",\n")}}) + { + {{new VariantsSwitchComponent(m, static (v, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($$""" + if(on{{v.Name}} is not null) + { + on{{v.Name}}.Invoke({{new VariantAccessorComponent(v)}}); + } + else + { + defaultHandler.Invoke(); + } + + return; + """ + ); + })}} + } + """); + }); + + var statefulDefaultHandler = Create(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $$""" + {{SwitchSummary()}} + {{SwitchStateParam()}} + {{SwitchDefaultHandlerParam()}} + {{SwitchHandlerParams(m)}} + {{SwitchStateTypeparam()}} + public void Switch( + TState state, + {{typeof(Action)}} defaultHandler, + {{List(m.Variants, static (v, _, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"{typeof(Action)}<{v.Type.NullableName}, TState>? on{v.Name} = null"); + }, separator: ",\n")}}) + { + {{new VariantsSwitchComponent(m, static (v, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($$""" + if(on{{v.Name}} is not null) + { + on{{v.Name}}.Invoke({{new VariantAccessorComponent(v)}}, state); + } + else + { + defaultHandler.Invoke(state); + } + + return; + """ + ); + })}} + } + """); + }); + + const String func = "global::System.Func"; + + var statelessProjection = Create(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $$""" + {{SwitchSummary()}} + {{SwitchHandlerParams(m)}} + {{SwitchResultTypeparam()}} + {{SwitchReturns()}} + public TResult Switch( + {{List(m.Variants, static (v, _, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"{func}<{v.Type.NullableName}, TResult> on{v.Name}"); + }, separator: ",\n")}}) + { + {{new VariantsSwitchComponent(m, static (v, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"return on{v.Name}.Invoke({new VariantAccessorComponent(v)});"); + })}} + } + """); + }); + + var statefulProjection = Create(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $$""" + {{SwitchSummary()}} + {{SwitchStateParam()}} + {{SwitchHandlerParams(m)}} + {{SwitchStateTypeparam()}} + {{SwitchResultTypeparam()}} + {{SwitchReturns()}} + public TResult Switch( + TState state, + {{List(m.Variants, static (v, _, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"{func}<{v.Type.NullableName}, TState, TResult> on{v.Name}"); + }, separator: ",\n")}}) + { + {{new VariantsSwitchComponent(m, static (v, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"return on{v.Name}.Invoke({new VariantAccessorComponent(v)}, state);"); + })}} + } + """); + }); + + var statelessDefaultProjection = Create(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $$""" + {{SwitchSummary()}} + {{SwitchDefaultHandlerParam()}} + {{SwitchHandlerParams(m)}} + {{SwitchResultTypeparam()}} + {{SwitchReturns()}} + public TResult Switch( + {{func}} defaultHandler, + {{List(m.Variants, static (v, _, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"{func}<{v.Type.NullableName}, TResult>? on{v.Name} = null"); + }, separator: ",\n")}}) + { + {{new VariantsSwitchComponent(m, static (v, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($$""" + if(on{{v.Name}} is not null) + { + return on{{v.Name}}.Invoke({{new VariantAccessorComponent(v)}}); + } + else + { + return defaultHandler.Invoke(); + } + """ + ); + })}} + } + """); + }); + + var statefulDefaultProjection = Create(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $$""" + {{SwitchSummary()}} + {{SwitchStateParam()}} + {{SwitchDefaultHandlerParam()}} + {{SwitchHandlerParams(m)}} + {{SwitchStateTypeparam()}} + {{SwitchResultTypeparam()}} + {{SwitchReturns()}} + public TResult Switch( + TState state, + {{func}} defaultHandler, + {{List(m.Variants, static (v, _, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"{func}<{v.Type.NullableName}, TState, TResult>? on{v.Name} = null"); + }, separator: ",\n")}}) + { + {{new VariantsSwitchComponent(m, static (v, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($$""" + if(on{{v.Name}} is not null) + { + return on{{v.Name}}.Invoke({{new VariantAccessorComponent(v)}}, state); + } + else + { + return defaultHandler.Invoke(state); + } + """ + ); + })}} + } + """); + }); + + var statelessDefaultResultProjection = Create(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $$""" + {{SwitchSummary()}} + {{SwitchDefaultResultParam()}} + {{SwitchHandlerParams(m)}} + {{SwitchResultTypeparam()}} + {{SwitchReturns()}} + public TResult Switch( + TResult defaultResult, + {{List(m.Variants, static (v, _, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"{func}<{v.Type.NullableName}, TResult>? on{v.Name} = null"); + }, separator: ",\n")}}) + { + {{new VariantsSwitchComponent(m, static (v, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($$""" + if(on{{v.Name}} is not null) + { + return on{{v.Name}}.Invoke({{new VariantAccessorComponent(v)}}); + } + else + { + return defaultResult; + } + """ + ); + })}} + } + """); + }); + + var statefulDefaultResultProjection = Create(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $$""" + {{SwitchSummary()}} + {{SwitchStateParam()}} + {{SwitchDefaultResultParam()}} + {{SwitchHandlerParams(m)}} + {{SwitchStateTypeparam()}} + {{SwitchResultTypeparam()}} + {{SwitchReturns()}} + public TResult Switch( + TState state, + TResult defaultResult, + {{List(m.Variants, static (v, _, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"{func}<{v.Type.NullableName}, TState, TResult>? on{v.Name} = null"); + }, separator: ",\n")}}) + { + {{new VariantsSwitchComponent(m, static (v, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($$""" + if(on{{v.Name}} is not null) + { + return on{{v.Name}}.Invoke({{new VariantAccessorComponent(v)}}, state); + } + else + { + return defaultResult; + } + """ + ); + })}} + } + """); + }); + + b.Append($""" + {statelessHandler} + {statelessDefaultHandler} + {statefulHandler} + {statefulDefaultHandler} + {statelessProjection} + {statelessDefaultProjection} + {statelessDefaultResultProjection} + {statefulProjection} + {statefulDefaultProjection} + {statefulDefaultResultProjection} + """ + ); + }); + + var region = Region("Switch", methods); + + builder.Append(region); + } +} diff --git a/Janus.Analyzers/Components/ToStringComponent.cs b/Janus.Analyzers/Components/ToStringComponent.cs new file mode 100644 index 0000000..c8085a5 --- /dev/null +++ b/Janus.Analyzers/Components/ToStringComponent.cs @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using Lyra; +using static Lyra.ComponentFactory; +using static Lyra.ComponentFactory.Docs; + +internal readonly record struct ToStringComponent(UnionModel Model) : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (Model.IsToStringUserProvided || Model.Settings.ToStringSetting is ToStringSetting.None) + { + return; + } + + switch (Model.Settings.ToStringSetting) + { + case ToStringSetting.Simple: + AppendSimple(builder); + break; + case ToStringSetting.Detailed: + AppendDetailed(builder); + break; + } + } + + private void AppendDetailed(CSharpSourceBuilder builder) => + builder.Append( + $$""" + {{Inheritdoc()}} + public override string ToString() + { + {{new VariantsSwitchComponent(Model, static (v, m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $$$""" + return $"{{{ComponentFactory.Create(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append(m.Name); + + if (m.TypeParameters is []) + { + return; + } + + b.Append($"<{ComponentFactory.List(m.TypeParameters, static (p, _, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $$""" + {{p}}{(typeof({{p}}).IsGenericTypeParameter ? string.Empty : $":{typeof({{p}})}" )} + """ + ); + }, separator: ", ")}>"); + })}}} {{ Variants: [{{{ComponentFactory.Create((v, m), static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (variant, model) = t; + + for (var i = 0; i < model.Variants.Count; i++) + { + ct.ThrowIfCancellationRequested(); + + var isCaseVariant = ReferenceEquals(variant.Name, model.Variants[i].Name); + + b.SetCondition(i is not 0) + .Append(", ") + .UnsetCondition() + .SetCondition(isCaseVariant) + .Append('<') + .UnsetCondition() + .Append(model.Variants[i].Name) + .SetCondition(isCaseVariant) + .Append('>') + .UnsetCondition(); + } + })}}}], Value: {Value} }}"; + """ + ); + })}} + } + """ + ); + + private void AppendSimple(CSharpSourceBuilder builder) => + builder.Append( + $$""" + {{Inheritdoc()}} + public override string ToString() + { + {{new VariantsSwitchComponent(Model, static (v, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"return {new VariantAccessorComponent(v)}.ToString();"); + })}} + } + """ + ); +} diff --git a/Janus.Analyzers/Components/UnionComponent.cs b/Janus.Analyzers/Components/UnionComponent.cs new file mode 100644 index 0000000..20cf4e4 --- /dev/null +++ b/Janus.Analyzers/Components/UnionComponent.cs @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using Lyra; +using static Lyra.ComponentFactory; +using static Lyra.ComponentFactory.Docs; + +internal readonly record struct UnionComponent(UnionModel Model) : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + var body = Create( + Model, + static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + if (m.Settings.JsonConverterSetting is JsonConverterSetting.EmitJsonConverter) + { + b.AppendLine(new JsonConverterComponent(m)); + } + + b.AppendLine( + $""" + {new VariantGroupKindsComponent(m)} + {new VariantGroupModelComponent(m)} + {new VariantKindComponent(m)} + {new VariantModelComponent(m)} + {new FactoryComponent(m)} + """); + + if (ContainsUnmanagedVariants(m)) + { + b.AppendLine(new UnmanagedVariantsContainerComponent(m)); + } + + b.Append( + $""" + {new ConstructorComponent(m)} + {new FieldsComponent(m)} + {new PropertiesComponent(m)} + {new SwitchComponent(m)} + {new InspectionsComponent(m)} + {new ValidationComponent(m)} + {new FactoriesComponent(m)} + {new MappingComponent(m)} + {new EqualityComponent(m)} + {new OperatorsComponent(m)} + """); + }); + + var type = Create((Model, body), static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (model, body) = t; + + if (model.EmitDocsComment) + { + b.AppendLine(Summary(model, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($""" + Implements a tagged union for the following variant types: + {List("table", m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append(ListHeader("Name", "Type and Description")); + + for (var i = 0; i < m.Variants.Count; i++) + { + ct.ThrowIfCancellationRequested(); + + var variant = m.Variants[i]; + b.Append($""" + + {Item(variant, static (v, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append(v.Name); + }, static (v, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + if (v.Description is [_, ..]) + { + b.Append("Type: "); + } + + b.Append(Cref(v.Type.DocsId)); + + if (v.Description is [_, ..]) + { + b.Append($""" + {Br()} + Description: {v.Description} + """); + } + })} + """); + } + })} + """); + })); + } + + if (model.Settings.JsonConverterSetting is JsonConverterSetting.EmitJsonConverter) + { + b.AppendLine( + $"[global::System.Text.Json.Serialization.JsonConverterAttribute(typeof({new UnionTypeNameComponent(model, RenderOpenGeneric: true)}.JsonConverter))]"); + } + + b.Append(Type( + model.TypeKind is UnionTypeKind.Class ? "partial class" : "partial struct", + model.Name, + body, + baseTypeList: + [ + model.TypeNames.IUnion, + TypeName( + $"global::System.IEquatable<{new UnionTypeNameComponent(model)}>") + ], + typeParameters: [..model.TypeParameters])); + }); + + var containingTypes = Create((Model, type), static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (model, type) = t; + + foreach (var containingType in model.ContainingTypes) + { + b.Append($"partial {containingType.Modifier} {containingType.Name}"); + if (containingType.TypeParameters is not []) + { + b.Append('<') + .Append(List(containingType.TypeParameters, separator: ", ")) + .Append('>'); + } + + b.AppendLine().AppendLine('{').Indent(); + } + + b.AppendLine(type); + + for (var i = 0; i < model.ContainingTypes.Count; i++) + { + b.Detent().AppendLine('}'); + } + }); + + var @namespace = Namespace( + Model.Namespace, + containingTypes); + + builder.Append(@namespace); + } + + private static Boolean ContainsUnmanagedVariants(UnionModel m) + { + foreach (var variant in m.Variants) + { + if (variant.Type.Kind is VariantTypeKind.Unmanaged) + { + return true; + } + } + + return false; + } +} diff --git a/Janus.Analyzers/Components/UnionTypeMetadataNameComponent.cs b/Janus.Analyzers/Components/UnionTypeMetadataNameComponent.cs new file mode 100644 index 0000000..90be655 --- /dev/null +++ b/Janus.Analyzers/Components/UnionTypeMetadataNameComponent.cs @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using Lyra; + +internal readonly record struct UnionTypeMetadataNameComponent(UnionModel Model) : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (Model.Namespace is not []) + { + builder.Append($"{Model.Namespace}."); + } + + foreach (var containingType in Model.ContainingTypes) + { + builder.Append(containingType.Name); + + if (containingType.TypeParameters is { Count: > 0 and var containingTypeParameterCount }) + { + builder.Append($"`{containingTypeParameterCount}"); + } + + builder.Append('.'); + } + + builder.Append(Model.Name); + + if (Model.TypeParameters is { Count: > 0 and var count }) + { + builder.Append($"`{count}"); + } + } +} diff --git a/Janus.Analyzers/Components/UnionTypeNameComponent.cs b/Janus.Analyzers/Components/UnionTypeNameComponent.cs new file mode 100644 index 0000000..7d4786e --- /dev/null +++ b/Janus.Analyzers/Components/UnionTypeNameComponent.cs @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using System.Buffers; +using System.Runtime.InteropServices; +using System.Text; +using Lyra; +using static Lyra.ComponentFactory; + +internal readonly record struct UnionTypeNameComponent( + UnionModel Model, + Boolean RenderOpenGeneric = false, + Boolean RenderNullable = false) + : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + builder.Append(Model.Name); + + if (Model.TypeParameters is []) + { + if (RenderNullable && Model.TypeKind is UnionTypeKind.Class) + { + builder.Append('?'); + } + + return; + } + + var rented = RenderOpenGeneric + ? ArrayPool.Shared.Rent(Model.TypeParameters.Count) + : null; + try + { + var emptyNames = rented; + if (emptyNames is not null) + { + if (emptyNames.Length != Model.TypeParameters.Count) + { + emptyNames = new String[Model.TypeParameters.Count]; + } + + for (var i = 0; i < emptyNames.Length; i++) + { + emptyNames[i] = String.Empty; + } + } + + var typeParameters = RenderOpenGeneric + ? emptyNames! + : [..Model.TypeParameters]; + + builder.Append($"<{List(typeParameters, static (p, _, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append(p); + }, separator: RenderOpenGeneric ? "," : ", ")}>"); + + if (RenderNullable && Model.TypeKind is UnionTypeKind.Class) + { + builder.Append('?'); + } + } + finally + { + if (rented is not null) + { + ArrayPool.Shared.Return(rented); + } + } + } + + public override String ToString() + { + var builder = new StringBuilder(); + + builder.Append(Model.Name); + + if (Model.TypeParameters is []) + { + if (RenderNullable && Model.TypeKind is UnionTypeKind.Class) + { + builder.Append('?'); + } + + return builder.ToString(); + } + + var rented = RenderOpenGeneric + ? ArrayPool.Shared.Rent(Model.TypeParameters.Count) + : null; + try + { + var emptyNames = rented; + if (emptyNames is not null) + { + if (emptyNames.Length != Model.TypeParameters.Count) + { + emptyNames = new String[Model.TypeParameters.Count]; + } + + for (var i = 0; i < emptyNames.Length; i++) + { + emptyNames[i] = String.Empty; + } + } + + var typeParameters = RenderOpenGeneric + ? emptyNames! + : [..Model.TypeParameters]; + + builder.Append('<'); + + for (var i = 0; i < typeParameters.Length; i++) + { + if (i is not 0) + { + builder.Append(RenderOpenGeneric ? "," : ", "); + } + + builder.Append(typeParameters[i]); + } + + builder.Append('>'); + + if (RenderNullable && Model.TypeKind is UnionTypeKind.Class) + { + builder.Append('?'); + } + } + finally + { + if (rented is not null) + { + ArrayPool.Shared.Return(rented); + } + } + + if (RenderNullable && Model.TypeKind is UnionTypeKind.Class) + { + builder.Append('?'); + } + + return builder.ToString(); + } +} diff --git a/Janus.Analyzers/Components/UnmanagedVariantsContainerComponent.cs b/Janus.Analyzers/Components/UnmanagedVariantsContainerComponent.cs new file mode 100644 index 0000000..0403aaf --- /dev/null +++ b/Janus.Analyzers/Components/UnmanagedVariantsContainerComponent.cs @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using System.Runtime.InteropServices; +using Lyra; +using static Lyra.ComponentFactory; +using static Lyra.ComponentFactory.Docs; + +internal readonly record struct UnmanagedVariantsContainerComponent(UnionModel Model) : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var body = Create( + Model, + static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + foreach (var variant in m.Variants) + { + if (variant.Type.Kind is not VariantTypeKind.Unmanaged) + { + continue; + } + + b.AppendLine( + $$""" + {{Summary("Initializes a new instance.")}} + {{Param("value", "The value to store.")}} + public UnmanagedVariantsContainer({{variant.Type.NullableName}} value) + { + {{variant.Name}} = value; + } + {{Summary(variant, static (v, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Gets the stored value typed as {Cref(v.Type.DocsId)}, to be used when representing the {C(v.Name)} variant."); + })}} + {{Create(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + if (m.TypeParameters is not []) + { + return; + } + + b.Append(Attribute(TypeName(), PositionalAttributeArgument("0"))); + })}} + public readonly {{variant.Type.NullableName}} {{variant.Name}}; + """ + ); + } + }); + + var type = Create((Model, body), static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (model, body) = t; + + b.Append($""" + {Summary(model, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Stores unmanaged variants of {Cref(m.DocsCommentId)}."); + })} + {Type( + "private readonly struct", + "UnmanagedVariantsContainer", + body, + attributes: + model.TypeParameters is [] + ? + [ + Attribute( + TypeName(), + AttributeArgumentComponent.CreatePositionalMemberAccess( + TypeName(), + nameof(LayoutKind.Explicit))) + ] + : [])} + """); + }); + + var region = Region("UnmanagedVariantsContainer", type); + + builder.Append(region); + } +} diff --git a/Janus.Analyzers/Components/ValidationComponent.cs b/Janus.Analyzers/Components/ValidationComponent.cs new file mode 100644 index 0000000..a6c389a --- /dev/null +++ b/Janus.Analyzers/Components/ValidationComponent.cs @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using Lyra; +using static Lyra.ComponentFactory; +using static Lyra.ComponentFactory.Docs; + +internal readonly record struct ValidationComponent(UnionModel Model) : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var methods = Create(Model, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $$""" + {{Summary("Creates an exception used when converting to a given variant.")}} + {{Param("variant", "The variant a conversion was attempted to.")}} + {{Returns("The created exception.")}} + private {{typeof(InvalidCastException)}} CreateInvalidCastException(VariantKind variant) + => new {{typeof(InvalidCastException)}}( + $"Unable to convert union to '{new VariantModel(variant).Name}', as it is currently representing " + + $"the '{Variant}' variant."); + + {{Summary("Creates an exception used when encountering an unknown variant state.")}} + {{Returns("The created exception.")}} + private {{typeof(InvalidOperationException)}} CreateUnknownVariantException() + => new {{typeof(InvalidOperationException)}}( + $"Unable to determine the variant of this union, as '{nameof(Variant)}' is not " + + $"representing a valid variant of this union: '{Variant}'. This could be either " + + "because the union itself was not initialized correctly, or due to a bug in the " + + "'JanusJanus' source generator that generated this union type. Please report an issue to the " + + "maintainer."); + + {{List(m.Variants, static (v, _, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $""" + {Summary("Validates a variant value for creating a new instance of the union.")} + {Remarks(static (b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $""" + If {ParamRef("throwIfInvalid")} is {Langword("true")}, the implementation should throw an + exception outlining why {ParamRef("value")} is invalid. + Otherwise, {ParamRef("isValid")} should be assigned {Langword("true")} if + {ParamRef("value")} is valid and {Langword("false")} if it is not. + """); + })} + {Param("value", "The value to validate.")} + {Param("throwIfInvalid", "Indicates whether to throw an exception if the value is invalid.")} + {Param("isValid", "Indicates whether the value is valid.")} + static partial void Validate({v.Type.NullableName} value, bool throwIfInvalid, ref bool isValid); + """ + ); + }, separator: "\n\n")}} + """ + ); + }); + + var region = Region("Validation", methods); + + builder.Append(region); + } +} diff --git a/Janus.Analyzers/Components/VariantAccessorComponent.cs b/Janus.Analyzers/Components/VariantAccessorComponent.cs new file mode 100644 index 0000000..a110ca6 --- /dev/null +++ b/Janus.Analyzers/Components/VariantAccessorComponent.cs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using Lyra; + +internal readonly record struct VariantAccessorComponent( + UnionTypeAttribute.Model Model, + String InstanceExpression = "this", + Boolean NullableCast = false) + : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + switch (Model.Type.Kind) + { + case VariantTypeKind.Value or VariantTypeKind.Unknown: + builder.Append($"{InstanceExpression}._{Model.Name}"); + break; + case VariantTypeKind.Unmanaged: + builder.Append($"{InstanceExpression}._unmanagedVariantsContainer.{Model.Name}"); + break; + case VariantTypeKind.Reference: + if (NullableCast && !Model.Type.IsNullable) + { + builder.Append($"({Model.Type.Name}?){InstanceExpression}._referenceVariantsContainer"); + } + else + { + builder.Append($"(({Model.Type.NullableName}){InstanceExpression}._referenceVariantsContainer!)"); + } + + break; + } + } +} diff --git a/Janus.Analyzers/Components/VariantGroupKindsComponent.cs b/Janus.Analyzers/Components/VariantGroupKindsComponent.cs new file mode 100644 index 0000000..8696f84 --- /dev/null +++ b/Janus.Analyzers/Components/VariantGroupKindsComponent.cs @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using System.Runtime.CompilerServices; +using Lyra; +using static Lyra.ComponentFactory; +using static Lyra.ComponentFactory.Docs; + +internal readonly record struct VariantGroupKindsComponent(UnionModel Model) : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var members = List( + Model.VariantGroups, + static (g, i, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.AppendLine(Summary(g, static (g, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Represents the {g} group."); + })); + + if (i is 0) + { + b.Append($"{g} = 0"); + } + else + { + b.Append($"{g} = 1 << {i - 1}"); + } + }, + separator: ",\n"); + var type = Create((Model, members), static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (model, members) = t; + + b.Append( + $""" + {Summary(model, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Enumerates the kinds of groups variants of the {Cref(m.DocsCommentId)} may be a part of."); + })} + {Type( + "public enum", + "VariantGroupKinds", + members, + attributes: + [ + Attribute(TypeName(), arguments: []) + ], + baseTypeList: [FlagsEnumBackingType(model.VariantGroups.Count - 1)])} + """ + ); + }); + var region = Region("VariantGroupKinds", type); + builder.Append(region); + } +} diff --git a/Janus.Analyzers/Components/VariantGroupModelComponent.cs b/Janus.Analyzers/Components/VariantGroupModelComponent.cs new file mode 100644 index 0000000..3c20942 --- /dev/null +++ b/Janus.Analyzers/Components/VariantGroupModelComponent.cs @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using Lyra; +using static Lyra.ComponentFactory; +using static Lyra.ComponentFactory.Docs; + +internal readonly record struct VariantGroupModelComponent(UnionModel Model) : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var members = Create(Model, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $$""" + {{Summary("Initializes a new instance.")}} + {{Param("kinds", "The kinds of variant groups to be included in the group set.")}} + public VariantGroupModel(VariantGroupKinds kinds) + { + Kinds = kinds; + } + + {{Summary("Gets the kinds of groups contained in the group.")}} + public VariantGroupKinds Kinds { get; } + + {{List(m.VariantGroups, static (g, _, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $$""" + {{Summary(g, static (g, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Gets the group model representing the {g} group."); + })}} + public static VariantGroupModel {{g}} { get; } = new(VariantGroupKinds.{{g}}); + """ + ); + }, separator: "\n")}} + + {{List(m.VariantGroups, static (g, _, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + if (g is "None") + { + return; + } + + b.AppendLine( + $""" + {Summary(g, static (g, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Gets a value indicating whether this group contains the {g} group."); + })} + public bool Contains{g} => Contains(VariantGroupModel.{g}); + """ + ); + })}} + {{Summary("Gets a value indicating whether this group contains the provided group.")}} + {{Param("group", "The group to check is contained in this group.")}} + {{Returns(static (b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"{Langword("true")} if this group contains {ParamRef("group")}; otherwise, {Langword("false")}."); + })}} + public bool Contains(VariantGroupModel group) => (Kinds & group.Kinds) == group.Kinds; + + {{Summary("Gets the set of all available groups.")}} + public static global::System.Collections.Immutable.ImmutableArray AllValues => + [ + {{List(m.VariantGroups, static (g, _, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append(g); + }, separator: ",\n")}} + ]; + + {{Summary("Gets the amount of individual groups contained in this group.")}} + public int IndividualGroupCount => PopCount((uint)Kinds); + + private void GetIndividualGroups(global::System.Span buffer) + { + var count = IndividualGroupCount; + + if (count is 0) + { + return; + } + + if (buffer.Length < count) + { + throw new global::System.ArgumentOutOfRangeException( + nameof(buffer), + $"{nameof(buffer)} did not have the required length of IndividualGroupCount. The required length was {count}, but the span provided had a length of {buffer.Length}."); + } + + if (count is 1) + { + buffer[0] = this; + return; + } + + var groupIndex = 0; + for (var i = LeadingZeroCount() + 1; i < 33 && groupIndex < count; i++) + { + var flagPosition = 32 - i; + var flag = 1 << flagPosition; + if (((int)Kinds & flag) == flag) + { + buffer[groupIndex++] = new VariantGroupModel((VariantGroupKinds)flag); + } + } + } + + {{Summary("Gets the individual groups contained in this group.")}} + {{Returns("The individual groups contained in this group.")}} + public global::System.Collections.Immutable.ImmutableArray GetIndividualGroups() + { + var groups = new VariantGroupModel[IndividualGroupCount]; + GetIndividualGroups(groups); + var result = global::System.Runtime.InteropServices.ImmutableCollectionsMarshal.AsImmutableArray(groups); + + return result; + } + + {{Summary("Gets the name of the group.")}} + public string Name + { + get + { + var count = IndividualGroupCount; + + if (count is 0 or 1) + { + return DegenerateName; + } + + global::System.Span + groups = stackalloc VariantGroupModel[count]; // Count never exceeds 32 + GetIndividualGroups(groups); + var builder = new global::System.Text.StringBuilder(); + for (var i = 0; i < count; i++) + { + var group = groups[i]; + var name = group.DegenerateName; + + if (i is not 0) + { + builder.Append(" | "); + } + + builder.Append(name); + } + + var result = builder.ToString(); + + return result; + } + } + + private string DegenerateName + { + get + { + switch (Kinds) + { + {{List(m.VariantGroups, static (v, _, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $$""" + case VariantGroupKinds.{{v}}: + { + return nameof(VariantGroupKinds.{{v}}); + } + """ + ); + }, separator: "\n") + }} + default: + { + throw CreateInvalidKindsException(); + } + } + } + } + + private global::System.InvalidOperationException CreateInvalidKindsException() + => new($"The {nameof(VariantGroupModel)} instance was not initialized correctly and is holding an invalid value: {Kinds}"); + + {{Inheritdoc()}} + public override string ToString() => Kinds.ToString(); + + private static global::System.ReadOnlySpan Log2DeBruijn => + [ + 00, 09, 01, 10, 13, 21, 02, 29, + 11, 14, 16, 18, 22, 25, 03, 30, + 08, 12, 20, 28, 15, 17, 24, 07, + 19, 27, 23, 06, 26, 05, 04, 31 + ]; + + private int LeadingZeroCount() + { + var value = (uint)Kinds; + + if (value == 0) + { + return 32; + } + + value |= value >> 01; + value |= value >> 02; + value |= value >> 04; + value |= value >> 08; + value |= value >> 16; + + var result = 31 ^ global::System.Runtime.CompilerServices.Unsafe.AddByteOffset( + ref global::System.Runtime.InteropServices.MemoryMarshal.GetReference(Log2DeBruijn), + ({{typeof(IntPtr)}})(int)((value * 0x07C4ACDDu) >> 27)); + + return result; + } + + private static int PopCount(uint value) + { + const uint c1 = 0x_55555555u; + const uint c2 = 0x_33333333u; + const uint c3 = 0x_0F0F0F0Fu; + const uint c4 = 0x_01010101u; + + value -= (value >> 1) & c1; + value = (value & c2) + ((value >> 2) & c2); + value = (((value + (value >> 4)) & c3) * c4) >> 24; + + return (int)value; + } + """ + ); + }); + + var type = Create((Model, members), static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (model, members) = t; + + b.Append( + $""" + {Summary(model, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Provides strongly typed access to the group of a variant of {Cref(m.DocsCommentId)}."); + })} + {Type( + "public readonly record struct", + "VariantGroupModel", + members)} + """ + ); + }); + + var region = Region("VariantGroupModel", type); + + builder.Append(region); + } +} diff --git a/Janus.Analyzers/Components/VariantKindComponent.cs b/Janus.Analyzers/Components/VariantKindComponent.cs new file mode 100644 index 0000000..6d3ebf7 --- /dev/null +++ b/Janus.Analyzers/Components/VariantKindComponent.cs @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using Lyra; +using static Lyra.ComponentFactory; +using static Lyra.ComponentFactory.Docs; + +internal readonly record struct VariantKindComponent(UnionModel Model) : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var members = List( + Model.Variants, + static (v, i, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + if (i is 0) + { + b.AppendLine( + $""" + {Summary("Represents a not yet or incorrectly initialized union.")} + Unknown = 0, + """); + } + + b.Append( + $""" + {Summary(v, static (v, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Represents the {C(v.Name)} variant."); + })} + {v.Name} = {i + 1} + """); + }, + separator: ",\n"); + + var type = Create((members, Model), static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (members, model) = t; + + b.Append($""" + {Summary(model, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Enumerates the variants of {Cref(m.DocsCommentId)}."); + })} + {Type( + "public enum", + "VariantKind", + members, + baseTypeList: [EnumBackingType(model.Variants.Count + 1)])} + """); + }); + + var region = Region("VariantKind", type); + + builder.Append(region); + } +} diff --git a/Janus.Analyzers/Components/VariantModelComponent.cs b/Janus.Analyzers/Components/VariantModelComponent.cs new file mode 100644 index 0000000..c2112c0 --- /dev/null +++ b/Janus.Analyzers/Components/VariantModelComponent.cs @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using Lyra; +using static Lyra.ComponentFactory; +using static Lyra.ComponentFactory.Docs; + +internal readonly record struct VariantModelComponent(UnionModel Model) : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var members = Create(Model, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $$""" + {{Summary("Initializes a new instance.")}} + {{Param("kind", "The kind of variant represented.")}} + public VariantModel(VariantKind kind) + { + Kind = kind; + } + + {{Summary("Gets the kind of variant represented.")}} + public VariantKind Kind { get; } + + {{Summary(static (b, ct) => + { + ct.ThrowIfCancellationRequested(); + b.Append($"Gets a variant model representing {Cref("VariantKind.Unknown")}."); + })}} + public static VariantModel Unknown { get; } = new(VariantKind.Unknown); + + {{List(m.Variants, static (v, _, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($$""" + {{Summary(v.Name, static (n, b, ct) => + { + ct.ThrowIfCancellationRequested(); + b.Append($"Gets a variant model representing {Cref(n, static (n, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"VariantKind.{n}"); + })}."); + })}} + public static VariantModel {{v.Name}} { get; } = new(VariantKind.{{v.Name}}); + """); + }, "\n")}} + + {{Summary("Gets the set of all variants.")}} + public static global::System.Collections.Immutable.ImmutableArray AllValues => + [ + {{List(m.Variants, static (v, _, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append(v.Name); + }, ",\n")}} + ]; + + {{Summary("Gets the name of the variant.")}} + public string Name + { + get + { + {{new VariantsSwitchComponent(m, static (v, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"return nameof(VariantKind.{v.Name});"); + }, + VariantExpression: "Kind", + Default: static (_, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append("throw CreateInvalidKindException();"); + })}} + } + } + + {{Summary("Gets the type of the variant.")}} + public global::System.Type Type + { + get + { + {{new VariantsSwitchComponent(m, static (v, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"return typeof({(v.Type.Kind is VariantTypeKind.Reference ? v.Type.Name : v.Type.NullableName)});"); + }, + VariantExpression: "Kind", + Default: static (_, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append("throw CreateInvalidKindException();"); + })}} + } + } + + {{Summary("Gets the groups the variant is a part of.")}} + public VariantGroupModel Group + { + get + { + {{new VariantsSwitchComponent(m, static (v, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"return new VariantGroupModel({List(v.Groups, static (g, i, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + if (i is not 0) + { + b.Append(" | "); + } + + b.Append($"VariantGroupKinds.{g}"); + })});"); + }, + VariantExpression: "Kind", + Default: static (_, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append("throw CreateInvalidKindException();"); + })}} + } + } + + private {{typeof(InvalidOperationException)}} CreateInvalidKindException() + => new($"The {nameof(VariantModel)} instance was not initialized correctly and is holding an invalid value: {Kind}"); + """ + ); + }); + + var type = Create((Model, members), static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (model, members) = t; + + b.Append($""" + {Summary(model, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Models and provides strongly typed access to the variants of {Cref(m.DocsCommentId)}."); + })} + {Type( + "public readonly record struct", + "VariantModel", + members)} + """); + }); + + var region = Region("VariantModel", type); + + builder.Append(region); + } +} diff --git a/Janus.Analyzers/Components/VariantsSwitchComponent.cs b/Janus.Analyzers/Components/VariantsSwitchComponent.cs new file mode 100644 index 0000000..95f0a84 --- /dev/null +++ b/Janus.Analyzers/Components/VariantsSwitchComponent.cs @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using Lyra; + +internal readonly record struct VariantsSwitchComponent( + UnionModel Model, + Action Append, + Action? Default = null, + String VariantExpression = "Variant.Kind") : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + builder.AppendLine($"switch({VariantExpression})") + .AppendLine('{') + .Indent(); + + foreach (var variant in Model.Variants) + { + builder.AppendLine($"case VariantKind.{variant.Name}:") + .AppendLine('{') + .Indent(); + + Append.Invoke(variant, Model, builder, cancellationToken); + + builder.AppendLine() + .Detent() + .AppendLine('}'); + } + + builder.AppendLine("default:") + .AppendLine('{') + .Indent(); + + if (Default is not null) + { + Default.Invoke(Model, builder, cancellationToken); + } + else + { + builder.Append("throw CreateUnknownVariantException();"); + } + + builder.AppendLine() + .Detent() + .AppendLine('}'); + + builder.Detent() + .Append('}'); + } + + public Boolean Equals(VariantsSwitchComponent other) => + throw new NotSupportedException("Equals is not supported on this type."); + + public override Int32 GetHashCode() => + throw new NotSupportedException("GetHashCode is not supported on this type."); +} diff --git a/Janus.Analyzers/DiagnosticDescriptors.cs b/Janus.Analyzers/DiagnosticDescriptors.cs new file mode 100644 index 0000000..daeed37 --- /dev/null +++ b/Janus.Analyzers/DiagnosticDescriptors.cs @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using Microsoft.CodeAnalysis; + +internal static class DiagnosticDescriptors +{ + public static DiagnosticDescriptor ToStringSettingIgnored { get; } = new( + id: "RMJ0001", + title: "`ToStringSetting` is ignored due to user defined `ToString` implementation", + messageFormat: "`{0}` is ignored due to user defined `ToString` implementation in `{1}`", + category: "Usage", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static DiagnosticDescriptor UnionMayNotBeRecordType { get; } = new( + id: "RMJ0002", + title: "Union may not be record type", + messageFormat: "`{0}` may not be a record type", + category: "Usage", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor GenericUnionsCannotBeJsonSerializable { get; } = new( + id: "RMJ0003", + title: "Generic unions cannot be json serializable", + messageFormat: "`{0}` is a generic type and may therefore not be json serializable", + category: "Usage", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor NoMoreThan31VariantGroupsMayBeDefined { get; } = new( + id: "RMJ0004", + title: "No more than 31 variant groups may be defined", + messageFormat: "`{0}` defines {1} groups, but no more than 31 groups may be defined", + category: "Usage", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor UnionMayNotBeStatic { get; } = new( + id: "RMJ0005", + title: "Union may not be static", + messageFormat: "`{0}` may not be static", + category: "Usage", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor VariantNamesMustBeUnique { get; } = new( + id: "RMJ0006", + title: "Variant names must be unique", + messageFormat: "`{0}` already defines a variant with the name `{1}`", + category: "Usage", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor EnsureValidStructUnionState { get; } = new( + id: "RMJ0007", + title: "Ensure that struct unions have at least one struct or nullable reference type variant", + messageFormat: + "`{0}` is a struct union but does not have a struct or nullable reference type variant. In order to ensure the union is always in a correct state, a struct variant or nullable reference type should be provided. If no such variant is provided, the union should be a class", + category: "Design", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static DiagnosticDescriptor InterfaceVariantIsExcludedFromConversionOperators { get; } = new( + id: "RMJ0008", + title: "Interface variants are excluded from conversion operator generation", + messageFormat: + "Variant `{0}` of union `{1}` is excluded from conversion operator generation because it is an interface", + category: "Design", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static DiagnosticDescriptor VariantTypesMustBeUnique { get; } = new( + id: "RMJ0009", + title: "Variant types must be unique", + messageFormat: "`{0}` already defines a variant with the type `{1}`", + category: "Usage", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor ObjectCannotBeUsedAsAVariant { get; } = new( + id: "RMJ0010", + title: "`object` cannot be used as a variant", + messageFormat: "`object` cannot be used as a variant of `{0}`", + category: "Usage", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor ValueTypeCannotBeUsedAsAVariantOfStructUnion { get; } = new( + id: "RMJ0019", + title: "`ValueType` cannot be used as a variant of struct union", + messageFormat: "`ValueType` cannot be used as a variant of `{0}`", + category: "Usage", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor UnionCannotBeUsedAsVariantOfItself { get; } = new( + id: "RMJ0012", + title: "Union cannot be used as variant of itself", + messageFormat: "`{0}` cannot be used as a variant of itself", + category: "Usage", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor UnionCannotExplicitlyDefineBaseType { get; } = new( + id: "RMJ0013", + title: "Union cannot explicitly define base type", + messageFormat: "`{0}` cannot explicitly define base type `{1}`", + category: "Usage", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); + + public static DiagnosticDescriptor PreferNullableStructOverIsNullable { get; } = new( + id: "RMJ0014", + title: "Prefer `Nullable` over `IsNullable = true`", + messageFormat: + "Prefer `Nullable<{0}>` over `{1}` for variant `{2}`, as the `IsNullable` option only affects nullable reference type variants", + category: "Usage", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static DiagnosticDescriptor NullableVariantNotAllowedAlongWithNonNullableVariant { get; } = new( + id: "RMJ0015", + title: "`Nullable` and `T` variants are not allowed for the same type `T`", + messageFormat: "`{0}` cannot have both `Nullable<{1}>` and `{1}` variants", + category: "Usage", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true + ); + + public static DiagnosticDescriptor UnionTypeSettingsAttributeIgnoredDueToMissingUnionTypeAttribute { get; } = new( + id: "RMJ0016", + title: "`UnionTypeSettingsAttribute` is ignored, as no `UnionTypeAttribute` has been applied", + messageFormat: "`UnionTypeSettingsAttribute` on `{0}` is ignored, as no `UnionTypeAttribute` has been applied", + category: "Usage", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static DiagnosticDescriptor DuplicateVariantGroupNamesAreIgnored { get; } = new( + id: "RMJ0017", + title: "Duplicate variant group names are ignored", + messageFormat: "The variant group `{0}` has been specified", + category: "Usage", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true + ); + + public static DiagnosticDescriptor ClassUnionsShouldBeSealed { get; } = new( + id: "RMJ0018", + title: "Class unions should be sealed", + messageFormat: "The union `{0}` should be sealed", + category: "Design", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true + ); + + public static DiagnosticDescriptor UnionCannotBeRefStruct { get; } = new( + id: "RMJ0020", + title: "Union cannot be ref struct", + messageFormat: "Union `{0}` cannot be a ref struct", + category: "Usage", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); +} diff --git a/Janus.Analyzers/DiagnosticIds.cs b/Janus.Analyzers/DiagnosticIds.cs new file mode 100644 index 0000000..b292a67 --- /dev/null +++ b/Janus.Analyzers/DiagnosticIds.cs @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +/// +/// Provides access to diagnostic ids. +/// +public static class DiagnosticIds +{ +#pragma warning disable CS1591 + public const String ToStringSettingIgnored = "RMJ0001"; + public const String UnionMayNotBeRecordType = "RMJ0002"; + public const String GenericUnionsCannotBeJsonSerializable = "RMJ0003"; + public const String NoMoreThan31VariantGroupsMayBeDefined = "RMJ0004"; + public const String UnionMayNotBeStatic = "RMJ0005"; + public const String VariantNamesMustBeUnique = "RMJ0006"; + public const String EnsureValidStructUnionState = "RMJ0007"; + public const String InterfaceVariantIsExcludedFromConversionOperators = "RMJ0008"; + public const String VariantTypesMustBeUnique = "RMJ0009"; + public const String ObjectCannotBeUsedAsAVariant = "RMJ0010"; + public const String ValueTypeCannotBeUsedAsAVariantOfStructUnion = "RMJ0019"; + public const String UnionCannotBeUsedAsVariantOfItself = "RMJ0012"; + public const String UnionCannotExplicitlyDefineBaseType = "RMJ0013"; + public const String PreferNullableStructOverIsNullable = "RMJ0014"; + public const String NullableVariantNotAllowedAlongWithNonNullableVariant = "RMJ0015"; + public const String UnionTypeSettingsAttributeIgnoredDueToMissingUnionTypeAttribute = "RMJ0016"; + public const String DuplicateVariantGroupNamesAreIgnored = "RMJ0017"; + public const String ClassUnionsShouldBeSealed = "RMJ0018"; + public const String UnionCannotBeRefStruct = "RMJ0020"; +#pragma warning restore CS1591 +} diff --git a/Janus.Analyzers/Janus.Analyzers.csproj b/Janus.Analyzers/Janus.Analyzers.csproj new file mode 100644 index 0000000..4786796 --- /dev/null +++ b/Janus.Analyzers/Janus.Analyzers.csproj @@ -0,0 +1,40 @@ + + + + netstandard2.0 + false + true + true + true + enable + + + + + $(DefineConstants);RHOMICRO_CODEANALYSIS_JANUS_ANALYZERS + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/Janus.Analyzers/JanusAnalyzer.AttributeContext.cs b/Janus.Analyzers/JanusAnalyzer.AttributeContext.cs new file mode 100644 index 0000000..1a8e9f0 --- /dev/null +++ b/Janus.Analyzers/JanusAnalyzer.AttributeContext.cs @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +partial class JanusAnalyzer +{ + readonly partial record struct AttributeAnalysisContext( + SemanticModel SemanticModel, + IObjectCreationOperation AttributeOperation, + AttributeSyntax AttributeSyntax, + INamedTypeSymbol AttributeSymbol, + ISymbol TargetSymbol) + { + public Boolean TryGetUnionTypeSymbol([NotNullWhen(true)] out INamedTypeSymbol? unionTypeSymbol) + { + switch (TargetSymbol) + { + case INamedTypeSymbol target: + unionTypeSymbol = target; + return true; + case ITypeParameterSymbol parameter: + unionTypeSymbol = parameter.ContainingType; + return true; + default: + unionTypeSymbol = null; + return false; + } + } + + [TypeSymbolPattern(typeof(UnionTypeSettingsAttribute))] + private static partial Boolean IsUnionTypeSettingsAttribute(ITypeSymbol? type); + + private static Boolean IsUnionTypeAttribute(ITypeSymbol? type) + { + var result = type is INamedTypeSymbol + { + Name: "UnionTypeAttribute", + TypeArguments.Length: < 9, + ContainingNamespace: + { + Name: "CodeAnalysis", + ContainingNamespace: + { + Name: "RhoMicro", + ContainingNamespace: + { + IsGlobalNamespace: true + } + } + } + }; + + return result; + } + + public static Boolean TryCreateForUnionTypeSettingsAttribute( + OperationAnalysisContext ctx, + out AttributeAnalysisContext attributeAnalysisContext) => + TryCreateForUnionTypeSettingsAttribute( + ctx.Operation, + GetContainingSymbol(ctx), + out attributeAnalysisContext, + ctx.CancellationToken); + + public static Boolean TryCreateForUnionTypeSettingsAttribute( + IOperation operation, + ISymbol containingSymbol, + out AttributeAnalysisContext attributeAnalysisContext, + CancellationToken ct) + { + ct.ThrowIfCancellationRequested(); + + attributeAnalysisContext = default; + + if (operation is not IAttributeOperation + { + Operation: IObjectCreationOperation + { + Type: INamedTypeSymbol attributeSymbol + } attributeOperation + }) + { + return false; + } + + if (!IsUnionTypeSettingsAttribute(attributeSymbol)) + { + return false; + } + + if (attributeOperation.Syntax is not AttributeSyntax attributeSyntax) + { + return false; + } + + if (attributeOperation.SemanticModel is not { } semanticModel) + { + return false; + } + + attributeAnalysisContext = new AttributeAnalysisContext( + semanticModel, + attributeOperation, + attributeSyntax, + AttributeSymbol: attributeSymbol, + TargetSymbol: containingSymbol); + + return true; + } + + public static Boolean TryCreateForUnionTypeAttribute( + OperationAnalysisContext ctx, + out AttributeAnalysisContext attributeAnalysisContext) => + TryCreateForUnionTypeAttribute( + ctx.Operation, + GetContainingSymbol(ctx), + out attributeAnalysisContext, + ctx.CancellationToken); + + private static ISymbol GetContainingSymbol(OperationAnalysisContext ctx) + { + var ct = ctx.CancellationToken; + + ct.ThrowIfCancellationRequested(); + + if (ctx is + not + { + Operation.Syntax.Parent.Parent: TypeParameterSyntax + { + Parent: TypeParameterListSyntax + { + Parameters: var typeParameterSyntaxes + } + } typeParameterSyntax, + ContainingSymbol: INamedTypeSymbol + { + TypeParameters: [_, ..] typeParameterSymbols + } + } || typeParameterSyntaxes.Count != typeParameterSymbols.Length) + { + return ctx.ContainingSymbol; + } + + var index = 0; + for (; index < typeParameterSyntaxes.Count; index++) + { + ct.ThrowIfCancellationRequested(); + + if (typeParameterSyntaxes[index].Equals(typeParameterSyntax)) + { + return typeParameterSymbols[index]; + } + } + + return ctx.ContainingSymbol; + } + + public static Boolean TryCreateForUnionTypeAttribute( + IOperation operation, + ISymbol containingSymbol, + out AttributeAnalysisContext attributeAnalysisContext, + CancellationToken ct) + { + ct.ThrowIfCancellationRequested(); + + attributeAnalysisContext = default; + + if (operation is not IAttributeOperation + { + Operation: IObjectCreationOperation + { + Type: INamedTypeSymbol attributeSymbol + } attributeOperation + }) + { + return false; + } + + if (!IsUnionTypeAttribute(attributeOperation.Type)) + { + return false; + } + + if (attributeOperation.Syntax is not AttributeSyntax attributeSyntax) + { + return false; + } + + if (attributeOperation.SemanticModel is not { } semanticModel) + { + return false; + } + + attributeAnalysisContext = new AttributeAnalysisContext( + semanticModel, + attributeOperation, + attributeSyntax, + AttributeSymbol: attributeSymbol, + TargetSymbol: containingSymbol); + + return true; + } + } +} diff --git a/Janus.Analyzers/JanusAnalyzer.cs b/Janus.Analyzers/JanusAnalyzer.cs new file mode 100644 index 0000000..584ec89 --- /dev/null +++ b/Janus.Analyzers/JanusAnalyzer.cs @@ -0,0 +1,1623 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +/// +/// Generates diagnostics for guiding usage of the . +/// +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed partial class JanusAnalyzer : DiagnosticAnalyzer +{ + /// + public override ImmutableArray SupportedDiagnostics { get; } = + [ + DiagnosticDescriptors.ToStringSettingIgnored, + DiagnosticDescriptors.UnionMayNotBeRecordType, + DiagnosticDescriptors.GenericUnionsCannotBeJsonSerializable, + DiagnosticDescriptors.NoMoreThan31VariantGroupsMayBeDefined, + DiagnosticDescriptors.UnionMayNotBeStatic, + DiagnosticDescriptors.VariantNamesMustBeUnique, + DiagnosticDescriptors.EnsureValidStructUnionState, + DiagnosticDescriptors.InterfaceVariantIsExcludedFromConversionOperators, + DiagnosticDescriptors.VariantTypesMustBeUnique, + DiagnosticDescriptors.ObjectCannotBeUsedAsAVariant, + DiagnosticDescriptors.ValueTypeCannotBeUsedAsAVariantOfStructUnion, + DiagnosticDescriptors.UnionCannotExplicitlyDefineBaseType, + DiagnosticDescriptors.UnionCannotBeUsedAsVariantOfItself, + DiagnosticDescriptors.PreferNullableStructOverIsNullable, + DiagnosticDescriptors.NullableVariantNotAllowedAlongWithNonNullableVariant, + DiagnosticDescriptors.UnionTypeSettingsAttributeIgnoredDueToMissingUnionTypeAttribute, + DiagnosticDescriptors.DuplicateVariantGroupNamesAreIgnored, + DiagnosticDescriptors.ClassUnionsShouldBeSealed, + DiagnosticDescriptors.UnionCannotBeRefStruct, + ]; + + /// + public override void Initialize(AnalysisContext context) + { + _ = context ?? throw new ArgumentNullException(nameof(context)); + + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + +#if !DEBUG + context.EnableConcurrentExecution(); +#endif + + context.RegisterOperationAction( + ReportToStringSettingIgnored, OperationKind.Attribute); + context.RegisterOperationAction( + ReportUnionMayNotBeRecordType, OperationKind.Attribute); + context.RegisterOperationAction( + ReportGenericUnionsCannotBeJsonSerializable, OperationKind.Attribute); + context.RegisterOperationAction( + ReportNoMoreThan31VariantGroupsMayBeDefined, OperationKind.Attribute); + context.RegisterOperationAction( + ReportUnionMayNotBeStatic, OperationKind.Attribute); + context.RegisterOperationAction( + ReportVariantNamesMustBeUnique, OperationKind.Attribute); + context.RegisterOperationAction( + ReportEnsureValidStructUnionState, OperationKind.Attribute); + context.RegisterOperationAction( + ReportInterfaceVariantIsExcludedFromConversionOperators, OperationKind.Attribute); + context.RegisterOperationAction( + ReportVariantTypesMustBeUnique, OperationKind.Attribute); + context.RegisterOperationAction( + ReportObjectCannotBeUsedAsAVariant, OperationKind.Attribute); + context.RegisterOperationAction( + ReportValueTypeCannotBeUsedAsAVariantOfStructUnion, OperationKind.Attribute); + context.RegisterSymbolAction( + ReportUnionCannotExplicitlyDefineBaseType, SymbolKind.NamedType); + context.RegisterOperationAction( + ReportUnionCannotBeUsedAsVariantOfItself, OperationKind.Attribute); + context.RegisterOperationAction( + ReportPreferNullableStructOverIsNullable, OperationKind.Attribute); + context.RegisterOperationAction( + ReportNullableVariantNotAllowedAlongWithNonNullableVariant, OperationKind.Attribute); + context.RegisterSymbolAction( + ReportUnionTypeSettingsAttributeIgnoredDueToMissingUnionTypeAttribute, SymbolKind.NamedType); + context.RegisterOperationAction( + ReportDuplicateVariantGroupNamesAreIgnored, OperationKind.Attribute); + context.RegisterSymbolAction( + ReportClassUnionsShouldBeSealed, SymbolKind.NamedType); + context.RegisterSymbolAction( + ReportUnionCannotBeRefStruct, SymbolKind.NamedType); + } + + private static void ReportUnionCannotBeRefStruct(SymbolAnalysisContext ctx) + { + var ct = ctx.CancellationToken; + + ct.ThrowIfCancellationRequested(); + + if (ctx.Symbol is not INamedTypeSymbol + { + IsRefLikeType: true + } target) + { + return; + } + + var isUnion = false; + var attributes = target.GetAttributes(); + + foreach (var attribute in attributes) + { + ct.ThrowIfCancellationRequested(); + + if (!attribute.IsUnionTypeAttribute()) + { + continue; + } + + isUnion = true; + break; + } + + if (!isUnion) + { + return; + } + + var unionName = target.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat); + + foreach (var reference in target.DeclaringSyntaxReferences) + { + ct.ThrowIfCancellationRequested(); + + if (reference.GetSyntax(ct) is not TypeDeclarationSyntax + { + Identifier: { } identifier + }) + { + continue; + } + + var location = identifier.GetLocation(); + var diagnostic = Diagnostic.Create( + DiagnosticDescriptors.UnionCannotBeRefStruct, + location, + messageArgs: + [ + unionName + ]); + ctx.ReportDiagnostic(diagnostic); + } + } + + private static void ReportClassUnionsShouldBeSealed(SymbolAnalysisContext ctx) + { + var ct = ctx.CancellationToken; + + ct.ThrowIfCancellationRequested(); + + if (ctx.Symbol is not INamedTypeSymbol + { + IsSealed: false, + IsReferenceType: true + } target) + { + return; + } + + var isUnion = false; + + var attributes = target.GetAttributes(); + foreach (var attribute in attributes) + { + ct.ThrowIfCancellationRequested(); + + if (attribute.IsUnionTypeAttribute()) + { + isUnion = true; + break; + } + } + + if (!isUnion) + { + return; + } + + var unionName = target.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat); + + foreach (var reference in target.DeclaringSyntaxReferences) + { + ct.ThrowIfCancellationRequested(); + + if (reference.GetSyntax(ct) is not TypeDeclarationSyntax + { + Identifier: { } identifier + }) + { + continue; + } + + var location = identifier.GetLocation(); + var diagnostic = Diagnostic.Create( + DiagnosticDescriptors.ClassUnionsShouldBeSealed, + location, + messageArgs: + [ + unionName + ]); + ctx.ReportDiagnostic(diagnostic); + } + } + + private static void ReportDuplicateVariantGroupNamesAreIgnored(OperationAnalysisContext ctx) + { + var ct = ctx.CancellationToken; + + ct.ThrowIfCancellationRequested(); + + if (!AttributeAnalysisContext.TryCreateForUnionTypeAttribute(ctx, out var attributeContext)) + { + return; + } + + if (attributeContext is not + { + AttributeOperation.Initializer.Initializers: [_, ..] initializers, + AttributeSymbol: + { + TypeArguments: [_, ..] + } + }) + { + return; + } + + foreach (var initializer in initializers) + { + ct.ThrowIfCancellationRequested(); + + if (initializer is not ISimpleAssignmentOperation + { + Target: IPropertyReferenceOperation + { + Member.Name: nameof(UnionTypeAttribute.Groups) + }, + Value: { } value + }) + { + continue; + } + + var elements = value switch + { + IConversionOperation + { + Operand: ICollectionExpressionOperation + { + Elements: { } e + } + } => e, + IArrayCreationOperation + { + Initializer.ElementValues: { } e + } => e, + _ => [] + }; + + if (elements is [] or [_]) + { + continue; + } + + var groups = new HashSet(); + + foreach (var element in elements) + { + ct.ThrowIfCancellationRequested(); + + if (element.ConstantValue is not { HasValue: true, Value: String group }) + { + continue; + } + + if (groups.Add(group)) + { + continue; + } + + var location = element.Syntax.GetLocation(); + var diagnostic = Diagnostic.Create( + DiagnosticDescriptors.DuplicateVariantGroupNamesAreIgnored, + location, + messageArgs: + [ + group + ]); + ctx.ReportDiagnostic(diagnostic); + } + } + } + + private static void ReportUnionTypeSettingsAttributeIgnoredDueToMissingUnionTypeAttribute(SymbolAnalysisContext ctx) + { + var ct = ctx.CancellationToken; + + ct.ThrowIfCancellationRequested(); + + if (ctx.Symbol is not INamedTypeSymbol target) + { + return; + } + + var attributes = target.GetAttributes(); + var settingsLocation = Location.None; + + foreach (var attribute in attributes) + { + ct.ThrowIfCancellationRequested(); + + if (attribute.IsUnionTypeAttribute()) + { + return; + } + + if (attribute.IsUnionTypeSettingsAttribute()) + { + settingsLocation = attribute.ApplicationSyntaxReference? + .GetSyntax(ct) + .GetLocation() ?? Location.None; + } + } + + foreach (var typeParameter in target.TypeParameters) + { + ct.ThrowIfCancellationRequested(); + + var typeParameterAttributes = typeParameter.GetAttributes(); + + foreach (var attribute in typeParameterAttributes) + { + ct.ThrowIfCancellationRequested(); + + if (attribute.IsUnionTypeAttribute()) + { + return; + } + } + } + + if (settingsLocation == Location.None) + { + return; + } + + var unionName = target.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat); + var diagnostic = Diagnostic.Create( + DiagnosticDescriptors.UnionTypeSettingsAttributeIgnoredDueToMissingUnionTypeAttribute, + settingsLocation, + messageArgs: + [ + unionName + ]); + ctx.ReportDiagnostic(diagnostic); + } + + private static void ReportNullableVariantNotAllowedAlongWithNonNullableVariant(OperationAnalysisContext ctx) + { + var ct = ctx.CancellationToken; + + ct.ThrowIfCancellationRequested(); + + if (!AttributeAnalysisContext.TryCreateForUnionTypeAttribute(ctx, out var attributeContext)) + { + return; + } + + if (!attributeContext.TryGetUnionTypeSymbol(out var target) + || attributeContext is not + { + AttributeSymbol.TypeArguments: [_, ..] localTypeArgumentSymbols, + AttributeSyntax.Name: GenericNameSyntax + { + TypeArgumentList.Arguments: [_, ..] localTypeArgumentSyntaxes + } + }) + { + return; + } + + if (localTypeArgumentSyntaxes.Count != localTypeArgumentSymbols.Length) + { + return; + } + + var typeLocations = new Dictionary>(SymbolEqualityComparer.Default); + + for (var i = 0; i < localTypeArgumentSymbols.Length; i++) + { + var typeArgumentSymbol = localTypeArgumentSymbols[i]; + var location = localTypeArgumentSyntaxes[i].GetLocation(); + + if (typeArgumentSymbol is INamedTypeSymbol + { + OriginalDefinition: + { + SpecialType: SpecialType.System_Nullable_T + }, + TypeArguments: [{ } actualTypeArgumentSymbol] + }) + { + typeArgumentSymbol = actualTypeArgumentSymbol; + } + + if (!typeLocations.TryGetValue(typeArgumentSymbol, out var locations)) + { + typeLocations.Add(typeArgumentSymbol, locations = []); + } + + locations.Add(location); + } + + var attributes = target.GetAttributes(); + var nullableTypeArguments = new HashSet(SymbolEqualityComparer.Default); + var nonNullableTypeArguments = new HashSet(SymbolEqualityComparer.Default); + + foreach (var attribute in attributes) + { + ct.ThrowIfCancellationRequested(); + + if (!attribute.IsUnionTypeAttribute()) + { + continue; + } + + if (attribute.AttributeClass is not + { + TypeArguments: [_, ..] globalTypeArgumentSymbols + }) + { + continue; + } + + foreach (var typeArgumentSymbol in globalTypeArgumentSymbols) + { + ct.ThrowIfCancellationRequested(); + + var nonNullableTypeArgumentSymbol = typeArgumentSymbol; + var isNullable = false; + + if (typeArgumentSymbol is INamedTypeSymbol + { + OriginalDefinition: + { + SpecialType: SpecialType.System_Nullable_T + }, + TypeArguments: [{ } actualTypeArgumentSymbol] + }) + { + nonNullableTypeArgumentSymbol = actualTypeArgumentSymbol; + isNullable = true; + } + + if (!typeLocations.ContainsKey(nonNullableTypeArgumentSymbol)) + { + continue; + } + + if (isNullable) + { + nullableTypeArguments.Add(nonNullableTypeArgumentSymbol); + } + else + { + nonNullableTypeArguments.Add(nonNullableTypeArgumentSymbol); + } + } + } + + var typeName = target.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat); + + foreach (var nullableTypeArgument in nullableTypeArguments) + { + ct.ThrowIfCancellationRequested(); + + if (!nonNullableTypeArguments.Contains(nullableTypeArgument) || + !typeLocations.TryGetValue(nullableTypeArgument, out var locations)) + { + continue; + } + + var variantName = nullableTypeArgument.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat); + + foreach (var location in locations) + { + ct.ThrowIfCancellationRequested(); + + var diagnostic = Diagnostic.Create( + DiagnosticDescriptors.NullableVariantNotAllowedAlongWithNonNullableVariant, + location, + messageArgs: + [ + typeName, + variantName + ]); + ctx.ReportDiagnostic(diagnostic); + } + } + } + + private static void ReportPreferNullableStructOverIsNullable(OperationAnalysisContext ctx) + { + var ct = ctx.CancellationToken; + + ct.ThrowIfCancellationRequested(); + + if (!AttributeAnalysisContext.TryCreateForUnionTypeAttribute(ctx, out var attributeContext)) + { + return; + } + + if (attributeContext.AttributeSyntax.Name is not GenericNameSyntax + { + TypeArgumentList.Arguments: [{ } variantSyntax] + }) + { + return; + } + + if (attributeContext.SemanticModel.GetTypeInfo(variantSyntax).Type is not + { + IsValueType: true, + OriginalDefinition: + { + SpecialType: not SpecialType.System_Nullable_T + } + } variantSymbol) + { + return; + } + + if (attributeContext.AttributeOperation.Initializer?.Initializers is not [_, ..] initializers) + { + return; + } + + var variantTypeName = variantSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat); + var isNullableText = String.Empty; + var location = Location.None; + var variantName = variantSymbol.Name; + + foreach (var initializer in initializers) + { + ct.ThrowIfCancellationRequested(); + + if (initializer is not ISimpleAssignmentOperation + { + Target: IPropertyReferenceOperation + { + Member.Name: { } assignedMember + }, + Value.ConstantValue: { HasValue: true, Value: { } assignedValue } + } assignmentOperation) + { + continue; + } + + if (assignedMember is nameof(UnionTypeAttribute.Name)) + { + if (assignedValue is String explicitName) + { + variantName = explicitName; + } + + continue; + } + + if (assignedMember is nameof(UnionTypeAttribute.IsNullable)) + { + if (assignedValue is false) + { + return; + } + + isNullableText = assignmentOperation.Syntax.ToString(); + location = assignmentOperation.Syntax.GetLocation(); + } + } + + if (isNullableText is []) + { + return; + } + + var diagnostic = Diagnostic.Create( + DiagnosticDescriptors.PreferNullableStructOverIsNullable, + location, + messageArgs: + [ + variantTypeName, + isNullableText, + variantName + ]); + ctx.ReportDiagnostic(diagnostic); + } + + private static void ReportUnionCannotBeUsedAsVariantOfItself(OperationAnalysisContext ctx) + { + var ct = ctx.CancellationToken; + + ct.ThrowIfCancellationRequested(); + + if (!AttributeAnalysisContext.TryCreateForUnionTypeAttribute(ctx, out var attributeContext)) + { + return; + } + + if (attributeContext is not + { + AttributeSyntax.Name: GenericNameSyntax + { + TypeArgumentList.Arguments: [_, ..] typeArgumentSyntaxes + } + }) + { + return; + } + + for (var i = 0; i < typeArgumentSyntaxes.Count; i++) + { + ct.ThrowIfCancellationRequested(); + + var typeArgumentSyntax = typeArgumentSyntaxes[i]; + + if (attributeContext.SemanticModel.GetTypeInfo(typeArgumentSyntax, ct).Type is not { } variant) + { + continue; + } + + if (!SymbolEqualityComparer.Default.Equals(variant, attributeContext.TargetSymbol)) + { + continue; + } + + var location = typeArgumentSyntaxes[i].GetLocation(); + var diagnostic = Diagnostic.Create( + DiagnosticDescriptors.UnionCannotBeUsedAsVariantOfItself, + location, + messageArgs: + [ + attributeContext.TargetSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat) + ]); + ctx.ReportDiagnostic(diagnostic); + } + } + + private static void ReportUnionCannotExplicitlyDefineBaseType(SymbolAnalysisContext ctx) + { + var ct = ctx.CancellationToken; + + ct.ThrowIfCancellationRequested(); + + if (ctx.Symbol is not INamedTypeSymbol + { + IsReferenceType: true, + BaseType: + { + SpecialType: not SpecialType.System_Object + } baseTypeSymbol + } target) + { + return; + } + + var attributes = target.GetAttributes(); + var baseTypeName = baseTypeSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat); + var isUnion = false; + + foreach (var attribute in attributes) + { + ct.ThrowIfCancellationRequested(); + + if (attribute.IsUnionTypeAttribute()) + { + isUnion = true; + } + } + + if (!isUnion) + { + return; + } + + foreach (var reference in target.DeclaringSyntaxReferences) + { + ct.ThrowIfCancellationRequested(); + + if (reference.GetSyntax(ct) is not TypeDeclarationSyntax + { + Identifier: { } identifier + }) + { + continue; + } + + var location = identifier.GetLocation(); + var unionName = target.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat); + var diagnostic = Diagnostic.Create( + DiagnosticDescriptors.UnionCannotExplicitlyDefineBaseType, + location, + messageArgs: + [ + unionName, + baseTypeName + ]); + ctx.ReportDiagnostic(diagnostic); + } + } + + private static void ReportValueTypeCannotBeUsedAsAVariantOfStructUnion(OperationAnalysisContext ctx) + { + var ct = ctx.CancellationToken; + + ct.ThrowIfCancellationRequested(); + + if (!AttributeAnalysisContext.TryCreateForUnionTypeAttribute(ctx, out var attributeContext)) + { + return; + } + + if (!attributeContext.TryGetUnionTypeSymbol(out var target) + || !target.IsValueType + || attributeContext is not + { + AttributeSyntax.Name: GenericNameSyntax + { + TypeArgumentList.Arguments: [_, ..] typeArgumentSyntaxes + } + }) + { + return; + } + + for (var i = 0; i < typeArgumentSyntaxes.Count; i++) + { + ct.ThrowIfCancellationRequested(); + + var typeArgumentSyntax = typeArgumentSyntaxes[i]; + + if (attributeContext.SemanticModel.GetTypeInfo(typeArgumentSyntax, ct).Type is not + { + SpecialType: SpecialType.System_ValueType + }) + { + continue; + } + + var location = typeArgumentSyntaxes[i].GetLocation(); + var diagnostic = Diagnostic.Create( + DiagnosticDescriptors.ValueTypeCannotBeUsedAsAVariantOfStructUnion, + location, + messageArgs: + [ + attributeContext.TargetSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat) + ]); + ctx.ReportDiagnostic(diagnostic); + } + } + + private static void ReportObjectCannotBeUsedAsAVariant(OperationAnalysisContext ctx) + { + var ct = ctx.CancellationToken; + + ct.ThrowIfCancellationRequested(); + + if (!AttributeAnalysisContext.TryCreateForUnionTypeAttribute(ctx, out var attributeContext)) + { + return; + } + + if (attributeContext is not + { + AttributeSyntax.Name: GenericNameSyntax + { + TypeArgumentList.Arguments: [_, ..] typeArgumentSyntaxes + } + }) + { + return; + } + + for (var i = 0; i < typeArgumentSyntaxes.Count; i++) + { + ct.ThrowIfCancellationRequested(); + + var typeArgumentSyntax = typeArgumentSyntaxes[i]; + + if (attributeContext.SemanticModel.GetTypeInfo(typeArgumentSyntax, ct).Type is not + { + SpecialType: SpecialType.System_Object + }) + { + continue; + } + + var location = typeArgumentSyntaxes[i].GetLocation(); + var diagnostic = Diagnostic.Create( + DiagnosticDescriptors.ObjectCannotBeUsedAsAVariant, + location, + messageArgs: + [ + attributeContext.TargetSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat) + ]); + ctx.ReportDiagnostic(diagnostic); + } + } + + private static void ReportVariantTypesMustBeUnique(OperationAnalysisContext ctx) + { + var ct = ctx.CancellationToken; + + ct.ThrowIfCancellationRequested(); + + if (!AttributeAnalysisContext.TryCreateForUnionTypeAttribute(ctx, out var attributeContext)) + { + return; + } + + if (!attributeContext.TryGetUnionTypeSymbol(out var target) + || attributeContext is not + { + AttributeSymbol.TypeArguments: [_, ..] localTypeArgumentSymbols, + AttributeSyntax.Name: GenericNameSyntax + { + TypeArgumentList.Arguments: [_, ..] localTypeArgumentSyntaxes + } + }) + { + return; + } + + if (localTypeArgumentSyntaxes.Count != localTypeArgumentSymbols.Length) + { + return; + } + + var typeLocations = new Dictionary>(SymbolEqualityComparer.Default); + + for (var i = 0; i < localTypeArgumentSymbols.Length; i++) + { + var typeArgumentSymbol = localTypeArgumentSymbols[i]; + var location = localTypeArgumentSyntaxes[i].GetLocation(); + + if (typeArgumentSymbol is INamedTypeSymbol + { + OriginalDefinition: + { + SpecialType: SpecialType.System_Nullable_T + }, + TypeArguments: [{ } actualTypeArgumentSymbol] + }) + { + typeArgumentSymbol = actualTypeArgumentSymbol; + } + + if (!typeLocations.TryGetValue(typeArgumentSymbol, out var locations)) + { + typeLocations.Add(typeArgumentSymbol, locations = []); + } + + locations.Add(location); + } + + var attributes = target.GetAttributes(); + var duplicates = new Dictionary(SymbolEqualityComparer.Default); + foreach (var attribute in attributes) + { + ct.ThrowIfCancellationRequested(); + + if (!attribute.IsUnionTypeAttribute()) + { + continue; + } + + if (attribute is not + { + AttributeClass.TypeArguments: [_, ..] typeArgumentSymbols + }) + { + continue; + } + + foreach (var typeArgumentSymbol in typeArgumentSymbols) + { + ct.ThrowIfCancellationRequested(); + + if (!typeLocations.ContainsKey(typeArgumentSymbol)) + { + continue; + } + + if (duplicates.TryGetValue(typeArgumentSymbol, out var isDuplicate)) + { + if (!isDuplicate) + { + duplicates[typeArgumentSymbol] = true; + } + } + else + { + duplicates[typeArgumentSymbol] = false; + } + } + } + + if (duplicates.Count is 0) + { + return; + } + + var unionName = target.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat); + + foreach (var (variantType, isDuplicate) in duplicates) + { + ct.ThrowIfCancellationRequested(); + + if (!isDuplicate) + { + continue; + } + + if (!typeLocations.TryGetValue(variantType, out var locations)) + { + continue; + } + + foreach (var location in locations) + { + ct.ThrowIfCancellationRequested(); + + var variantTypeName = variantType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat); + var diagnostic = Diagnostic.Create( + DiagnosticDescriptors.VariantTypesMustBeUnique, + location, + messageArgs: + [ + unionName, + variantTypeName + ]); + ctx.ReportDiagnostic(diagnostic); + } + } + } + + private static void ReportInterfaceVariantIsExcludedFromConversionOperators(OperationAnalysisContext ctx) + { + var ct = ctx.CancellationToken; + + ct.ThrowIfCancellationRequested(); + + if (!AttributeAnalysisContext.TryCreateForUnionTypeAttribute(ctx, out var attributeContext)) + { + return; + } + + if (attributeContext.AttributeSyntax.Name is not GenericNameSyntax + { + TypeArgumentList.Arguments: { } argumentSyntaxes + }) + { + return; + } + + var interfaceVariants = new Dictionary>(); + for (var i = 0; i < argumentSyntaxes.Count; i++) + { + var typeArgumentSyntax = argumentSyntaxes[i]; + + if (attributeContext.SemanticModel.GetTypeInfo(typeArgumentSyntax, ct).Type is not INamedTypeSymbol + { + TypeKind: TypeKind.Interface + } typeArgumentSymbol) + { + continue; + } + + var name = typeArgumentSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat); + var location = typeArgumentSyntax.GetLocation(); + + if (!interfaceVariants.TryGetValue(name, out var locations)) + { + interfaceVariants.Add(name, locations = []); + } + + locations.Add(location); + } + + if (interfaceVariants.Count is 0) + { + return; + } + + foreach (var (name, locations) in interfaceVariants) + { + ct.ThrowIfCancellationRequested(); + + foreach (var location in locations) + { + ct.ThrowIfCancellationRequested(); + + var diagnostic = Diagnostic.Create( + DiagnosticDescriptors.InterfaceVariantIsExcludedFromConversionOperators, + location, + messageArgs: + [ + name, + attributeContext.TargetSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat) + ]); + ctx.ReportDiagnostic(diagnostic); + } + } + } + + private static void ReportEnsureValidStructUnionState(OperationAnalysisContext ctx) + { + var ct = ctx.CancellationToken; + + ct.ThrowIfCancellationRequested(); + + if (!AttributeAnalysisContext.TryCreateForUnionTypeAttribute(ctx, out var attributeContext)) + { + return; + } + + if (!attributeContext.TryGetUnionTypeSymbol(out var target) + || target.TypeKind is TypeKind.Class) + { + return; + } + + var model = UnionModel.Create(target, ct); + + if (model.Variants.Any(v => v.Type is not { Kind: VariantTypeKind.Reference, IsNullable: false })) + { + return; + } + + var locations = attributeContext + .TargetSymbol + .DeclaringSyntaxReferences + .Select(r => r.GetSyntax(ct)) + .OfType() + .Select(d => d.Identifier.GetLocation()); + + foreach (var location in locations) + { + ct.ThrowIfCancellationRequested(); + + var diagnostic = Diagnostic.Create( + DiagnosticDescriptors.EnsureValidStructUnionState, + location, + messageArgs: + [ + attributeContext.TargetSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat) + ]); + ctx.ReportDiagnostic(diagnostic); + } + } + + private static void ReportVariantNamesMustBeUnique(OperationAnalysisContext ctx) + { + var ct = ctx.CancellationToken; + + ct.ThrowIfCancellationRequested(); + + if (!AttributeAnalysisContext.TryCreateForUnionTypeAttribute(ctx, out var attributeContext)) + { + return; + } + + var localNameLocations = new Dictionary>(StringComparer.Ordinal); + var duplicates = new Dictionary(); + + if (!tryGetNamedTypeTargetNames(out var unionType) && !tryGetTypeParameterTargetNames(out unionType)) + { + return; + } + + getGlobalNames(unionType); + + var unionTypeName = unionType.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat); + + foreach (var (variantName, isDuplicate) in duplicates) + { + ct.ThrowIfCancellationRequested(); + + if (!isDuplicate) + { + continue; + } + + if (!localNameLocations.TryGetValue(variantName, out var locations)) + { + continue; + } + + foreach (var location in locations) + { + ct.ThrowIfCancellationRequested(); + + var diagnostic = Diagnostic.Create( + DiagnosticDescriptors.VariantNamesMustBeUnique, + location, + messageArgs: + [ + unionTypeName, + variantName + ]); + ctx.ReportDiagnostic(diagnostic); + } + } + + Boolean tryGetTypeParameterTargetNames([NotNullWhen(true)] out INamedTypeSymbol? containingUnionType) + { + if (attributeContext is not + { + TargetSymbol: ITypeParameterSymbol + { + ContainingType: var containingType + } target + }) + { + containingUnionType = null; + return false; + } + + var hasExplicitName = false; + var initializers = attributeContext.AttributeOperation.Initializer?.Initializers ?? []; + foreach (var initializer in initializers) + { + ct.ThrowIfCancellationRequested(); + + if (initializer is not ISimpleAssignmentOperation + { + Target: IMemberReferenceOperation + { + Member.Name: nameof(UnionTypeAttribute.Name) + }, + Value.ConstantValue: { HasValue: true, Value: String explicitName } + } nameAssignmentOperation) + { + continue; + } + + var location = nameAssignmentOperation.Syntax.GetLocation(); + + registerLocation(explicitName, location); + } + + if (hasExplicitName) + { + } + else + { + var name = target.Name; + foreach (var reference in target.DeclaringSyntaxReferences) + { + ct.ThrowIfCancellationRequested(); + + if (reference.GetSyntax(ct) is not TypeParameterSyntax + { + Identifier: var targetSyntaxIdentifier + }) + { + continue; + } + + var location = targetSyntaxIdentifier.GetLocation(); + registerLocation(name, location); + } + } + + containingUnionType = containingType; + return true; + } + + Boolean tryGetNamedTypeTargetNames([NotNullWhen(true)] out INamedTypeSymbol? targetUnionType) + { + if (!attributeContext.TryGetUnionTypeSymbol(out var target) + || attributeContext is not + { + AttributeSymbol.TypeArguments: [_, ..] localTypeArgumentSymbols, + AttributeSyntax.Name: GenericNameSyntax + { + TypeArgumentList.Arguments: [_, ..] localTypeArgumentSyntaxes + } + } || localTypeArgumentSyntaxes.Count != localTypeArgumentSymbols.Length) + { + targetUnionType = null; + return false; + } + + var hasExplicitName = false; + var initializers = attributeContext.AttributeOperation.Initializer?.Initializers ?? []; + foreach (var initializer in initializers) + { + ct.ThrowIfCancellationRequested(); + + if (initializer is not ISimpleAssignmentOperation + { + Target: IMemberReferenceOperation + { + Member.Name: nameof(UnionTypeAttribute.Name) + }, + Value.ConstantValue: { HasValue: true, Value: String explicitName } + } nameAssignmentOperation) + { + continue; + } + + var location = nameAssignmentOperation.Syntax.GetLocation(); + + registerLocation(explicitName, location); + } + + if (!hasExplicitName) + { + for (var i = 0; i < localTypeArgumentSymbols.Length; i++) + { + ct.ThrowIfCancellationRequested(); + + var typeArgumentSymbol = localTypeArgumentSymbols[i]; + var variantName = typeArgumentSymbol.Name; + var location = localTypeArgumentSyntaxes[i].GetLocation(); + registerLocation(variantName, location); + } + } + + targetUnionType = target; + return true; + } + + void registerName(String name) + { + if (!localNameLocations.ContainsKey(name)) + { + return; + } + + if (duplicates.TryGetValue(name, out var isDuplicate)) + { + if (!isDuplicate) + { + duplicates[name] = true; + } + } + else + { + duplicates[name] = false; + } + } + + void getGlobalNames(INamedTypeSymbol target) + { + var attributes = target.GetAttributes(); + + foreach (var attribute in attributes) + { + ct.ThrowIfCancellationRequested(); + + var typeArguments = attribute.AttributeClass?.TypeArguments ?? []; + + foreach (var typeArgument in typeArguments) + { + ct.ThrowIfCancellationRequested(); + + if (!attribute.TryGetUnionTypeAttributeModel( + new UnionTypeAttribute.Model.TypeArgumentState(typeArgument), + out var model)) + { + continue; + } + + if (model.Name is [_, ..] name) + { + registerName(name); + } + } + } + + foreach (var typeParameter in target.TypeParameters) + { + ct.ThrowIfCancellationRequested(); + + var typeParameterAttributes = typeParameter.GetAttributes(); + + foreach (var attribute in typeParameterAttributes) + { + ct.ThrowIfCancellationRequested(); + + if (!attribute.TryGetUnionTypeAttributeModel( + new UnionTypeAttribute.Model.TypeParameterState(typeParameter), + out var model)) + { + continue; + } + + var name = model.Name is [_, ..] explicitName + ? explicitName + : typeParameter.Name; + + registerName(name); + } + } + } + + void registerLocation(String name, Location location) + { + if (!localNameLocations.TryGetValue(name, out var locations)) + { + localNameLocations.Add(name, locations = []); + } + + locations.Add(location); + } + } + + private static void ReportUnionMayNotBeStatic(OperationAnalysisContext ctx) + { + var ct = ctx.CancellationToken; + + ct.ThrowIfCancellationRequested(); + + if (!AttributeAnalysisContext.TryCreateForUnionTypeAttribute(ctx, out var attributeContext)) + { + return; + } + + var locations = attributeContext + .TargetSymbol + .DeclaringSyntaxReferences + .Select(r => r.GetSyntax(ct)) + .OfType() + .Select(d => d + .Modifiers + .FirstOrDefault(m => m.IsKind(SyntaxKind.StaticKeyword))) + .Where(m => m.IsKind(SyntaxKind.StaticKeyword)) + .Select(m => m.GetLocation()); + + foreach (var location in locations) + { + ct.ThrowIfCancellationRequested(); + + var diagnostic = Diagnostic.Create( + DiagnosticDescriptors.UnionMayNotBeStatic, + location, + messageArgs: + [ + attributeContext.TargetSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat) + ]); + ctx.ReportDiagnostic(diagnostic); + } + } + + private static void ReportNoMoreThan31VariantGroupsMayBeDefined(OperationAnalysisContext ctx) + { + var ct = ctx.CancellationToken; + + ct.ThrowIfCancellationRequested(); + + if (!AttributeAnalysisContext.TryCreateForUnionTypeAttribute(ctx, out var attributeContext)) + { + return; + } + + var groupDatum = attributeContext + .AttributeOperation + .Initializer? + .Initializers + .OfType() + .Select(i => + { + if (i is not { Target: IPropertyReferenceOperation { Member.Name: nameof(UnionTypeAttribute.Groups) } }) + { + return (groupsOperation: i, groupsCount: -1); + } + + if (i.Value is + IConversionOperation + { + Operand: + ICollectionExpressionOperation + { + Elements.Length: > 31 and var collectionExpressionElementCount + } + }) + { + return (groupsOperation: i, groupsCount: collectionExpressionElementCount); + } + + if (i.Value is + IArrayCreationOperation + { + Initializer.ElementValues.Length: > 31 and var arrayInitializerElementCount + }) + { + return (groupsOperation: i, groupsCount: arrayInitializerElementCount); + } + + return (groupsOperation: i, groupsCount: -1); + }) + .FirstOrDefault(t => t.groupsCount is not -1); + + var (groupsOperation, groupsCount) = groupDatum.GetValueOrDefault(); + + if (groupsOperation is null) + { + return; + } + + var location = groupsOperation.Syntax.GetLocation(); + + var diagnostic = Diagnostic.Create( + DiagnosticDescriptors.NoMoreThan31VariantGroupsMayBeDefined, + location, + messageArgs: + [ + attributeContext.TargetSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat), + groupsCount + ]); + + ctx.ReportDiagnostic(diagnostic); + } + + private static void ReportGenericUnionsCannotBeJsonSerializable(OperationAnalysisContext ctx) + { + var ct = ctx.CancellationToken; + + ct.ThrowIfCancellationRequested(); + + if (!AttributeAnalysisContext.TryCreateForUnionTypeSettingsAttribute(ctx, out var attributeContext)) + { + return; + } + + if (!attributeContext.TryGetUnionTypeSymbol(out var taget) || !taget.IsGenericType) + { + return; + } + + var isJsonSerializableOperation = attributeContext + .AttributeOperation + .Initializer? + .Initializers + .OfType() + .FirstOrDefault(i => i is + { + Target: IPropertyReferenceOperation + { + Member.Name: nameof(UnionTypeSettingsAttribute.JsonConverterSetting) + }, + Value.ConstantValue: + { + HasValue: true, + Value: (Int32)JsonConverterSetting.EmitJsonConverter + } + }); + + if (isJsonSerializableOperation is null) + { + return; + } + + var location = isJsonSerializableOperation.Syntax.GetLocation(); + var diagnostic = Diagnostic.Create( + DiagnosticDescriptors.GenericUnionsCannotBeJsonSerializable, + location, + messageArgs: + [ + attributeContext.TargetSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat) + ]); + ctx.ReportDiagnostic(diagnostic); + } + + [TypeSymbolPattern(typeof(ToStringSetting))] + private static partial Boolean IsToStringSetting(ITypeSymbol? type); + + private static void ReportToStringSettingIgnored(OperationAnalysisContext ctx) + { + var ct = ctx.CancellationToken; + + ct.ThrowIfCancellationRequested(); + + if (!AttributeAnalysisContext.TryCreateForUnionTypeSettingsAttribute(ctx, out var attributeContext)) + { + return; + } + + var toStringSettingOperation = attributeContext + .AttributeOperation + .Initializer? + .Initializers + .FirstOrDefault(i => IsToStringSetting(i.Type) && i is not ISimpleAssignmentOperation + { + Value.ConstantValue: { HasValue: true, Value: ToStringSetting.None } + }); + + if (toStringSettingOperation is null) + { + return; + } + + if (!attributeContext.TryGetUnionTypeSymbol(out var target)) + { + return; + } + + var hasNonGeneratedToString = false; + + foreach (var member in target.GetMembers(nameof(ToString))) + { + ct.ThrowIfCancellationRequested(); + + if (member is not IMethodSymbol + { + Parameters: [], + IsOverride: true, + } method) + { + return; + } + + foreach (var reference in method.DeclaringSyntaxReferences) + { + ct.ThrowIfCancellationRequested(); + + var declaration = reference.GetSyntax(ct); + + if (declaration.SyntaxTree.FilePath.EndsWith(".g.cs")) + { + continue; + } + + var text = declaration.SyntaxTree.GetText(ct); + + if (text.Lines is not [{ } firstLine, ..]) + { + continue; + } + + if (text.GetSubText(firstLine.Span).ToString().StartsWith("// ")) + { + continue; + } + + hasNonGeneratedToString = true; + break; + } + + if (hasNonGeneratedToString) + { + break; + } + } + + if (!hasNonGeneratedToString) + { + return; + } + + var location = toStringSettingOperation.Syntax.GetLocation(); + var diagnostic = Diagnostic.Create( + DiagnosticDescriptors.ToStringSettingIgnored, + location, + messageArgs: + [ + toStringSettingOperation.Syntax.ToString(), + attributeContext.TargetSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat) + ]); + ctx.ReportDiagnostic(diagnostic); + } + + private static void ReportUnionMayNotBeRecordType(OperationAnalysisContext ctx) + { + var ct = ctx.CancellationToken; + + ct.ThrowIfCancellationRequested(); + + if (!AttributeAnalysisContext.TryCreateForUnionTypeAttribute(ctx, out var attributeContext)) + { + return; + } + + if (!attributeContext.TryGetUnionTypeSymbol(out var target) || !target.IsRecord) + { + return; + } + + var locations = target + .DeclaringSyntaxReferences + .Select(r => r.GetSyntax(ct)) + .OfType() + .Select(d => d.Keyword.GetLocation()); + + foreach (var location in locations) + { + ct.ThrowIfCancellationRequested(); + + var diagnostic = Diagnostic.Create( + DiagnosticDescriptors.UnionMayNotBeRecordType, + location, + messageArgs: + [ + attributeContext.TargetSymbol.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat) + ]); + ctx.ReportDiagnostic(diagnostic); + } + } +} diff --git a/Janus.Analyzers/JanusGenerator.cs b/Janus.Analyzers/JanusGenerator.cs new file mode 100644 index 0000000..c75b8a4 --- /dev/null +++ b/Janus.Analyzers/JanusGenerator.cs @@ -0,0 +1,212 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Runtime.InteropServices; +using Generated; +using Library.Models; +using Library.Models.Collections; +using Lyra; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +/// +/// Generates union type implementations. +/// +[Generator(LanguageNames.CSharp)] +public sealed class JanusGenerator : IIncrementalGenerator +{ + private static readonly ImmutableArray _genericUnionTypeAttributeMetadataNames = + [ + typeof(UnionTypeAttribute<>).FullName, + typeof(UnionTypeAttribute<,>).FullName, + typeof(UnionTypeAttribute<,,>).FullName, + typeof(UnionTypeAttribute<,,,>).FullName, + typeof(UnionTypeAttribute<,,,,>).FullName, + typeof(UnionTypeAttribute<,,,,,>).FullName, + typeof(UnionTypeAttribute<,,,,,,>).FullName, + typeof(UnionTypeAttribute<,,,,,,,>).FullName + ]; + + private static readonly CSharpSourceBuilderOptions _sourceBuilderOptions = new() + { + Prelude = static (b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.AppendLine( + $""" + // + // This file was generated using the Janus source generator. + // SPDX-License-Identifier: MPL-2.0 + // + #nullable enable + """); + } + }; + + /// + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var defaultSettingsProvider = context.SyntaxProvider + .ForAttributeWithMetadataName( + typeof(UnionTypeSettingsAttribute).FullName!, + static (n, ct) => + { + ct.ThrowIfCancellationRequested(); + + var result = n is CompilationUnitSyntax; + + return result; + }, static (ctx, ct) => + { + ct.ThrowIfCancellationRequested(); + + if (!ctx.Attributes[0] + .TryGetUnionTypeSettingsAttributeModel(out var settings, cancellationToken: ct)) + { + return null; + } + + return settings; + }) + .Collect() + .Select(static (s, ct) => + { + ct.ThrowIfCancellationRequested(); + + if (s is not [{ } settings, ..]) + { + return UnionTypeSettingsAttribute.Model.InheritanceRoot; + } + + // settings are copied in collection expression above, + // so we don't need to do that before mutating to + // avoid cache corruption + settings.Inherit(UnionTypeSettingsAttribute.Model.InheritanceRoot); + return settings; + }); + + var aggregateUnionTypesProvider = context.SyntaxProvider.ForAttributeWithMetadataName( + typeof(UnionTypeAttribute).FullName!, + static (n, ct) => + { + ct.ThrowIfCancellationRequested(); + + var result = n is TypeParameterSyntax; + + return result; + }, static (ctx, ct) => + { + ct.ThrowIfCancellationRequested(); + + var result = ctx.TargetSymbol is ITypeParameterSymbol { ContainingType: { } target } && + UnionModel.TryCreate(target, out var r, ct) + ? r + : null; + + return result; + }) + .Where(static m => m is not null)! + .Collect(); + + for (var i = 0; i < 8; i++) + { + var provider = context.SyntaxProvider.ForAttributeWithMetadataName( + _genericUnionTypeAttributeMetadataNames[i], + static (n, ct) => + { + ct.ThrowIfCancellationRequested(); + + var result = n is TypeDeclarationSyntax; + + return result; + }, static (ctx, ct) => + { + ct.ThrowIfCancellationRequested(); + + var result = ctx.TargetSymbol is INamedTypeSymbol target && + UnionModel.TryCreate(target, out var r, ct) + ? r + : null; + + return result; + }) + .Where(static m => m is not null)! + .Collect(); + + aggregateUnionTypesProvider = aggregateUnionTypesProvider + .Combine(provider) + .Select(static (t, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (left, right) = t; + + ImmutableArray result = [..left, ..right]; + + return result; + }) + .WithComparer(ImmutableArrayEqualityComparer.Default); + } + + var sourceProvider = aggregateUnionTypesProvider + .SelectMany(static (a, ct) => + { + ct.ThrowIfCancellationRequested(); + + var included = new HashSet(); + + foreach (var model in a) + { + ct.ThrowIfCancellationRequested(); + + _ = included.Add(model); + } + + return included; + }) + .Combine(defaultSettingsProvider) + .Select(static (t, ct) => + { + var (union, defaultSettings) = t; + + // copy settings before mutating to avoid cache corruption + var unionSettings = union.Settings; + unionSettings.Inherit(defaultSettings); + union = union with { Settings = unionSettings }; + + var result = union.GetValidation(ct).HasUnsupportedSettingsAwareState + ? null + : union; + + return result; + })! + .Where(m => m is not null) + .Select(static (m, ct) => + { + ct.ThrowIfCancellationRequested(); + + var sourceBuilder = new CSharpSourceBuilder(_sourceBuilderOptions); + var source = sourceBuilder + .SetCancellationToken(ct) + .Append(new UnionComponent(m)) + .ToString(); + + var hintName = sourceBuilder + .Clear() + .SkipPreludeChecks() + .Append(new UnionTypeMetadataNameComponent(m)) + .Append(".g.cs") + .ToString(); + + return (hintName, source); + }); + + context.RegisterSourceOutput(sourceProvider, static (ctx, t) => ctx.AddSource(t.hintName, t.source)); + IncludedFileSources.RegisterToContext(context); + } +} diff --git a/Janus.Analyzers/Models/ContainingTypeModel.cs b/Janus.Analyzers/Models/ContainingTypeModel.cs new file mode 100644 index 0000000..cb18008 --- /dev/null +++ b/Janus.Analyzers/Models/ContainingTypeModel.cs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using System.Collections.Immutable; +using Library.Models.Collections; + +internal readonly record struct ContainingTypeModel( + String Modifier, + String Name, + EquatableList TypeParameters); diff --git a/Janus.Analyzers/Models/TypeNames.cs b/Janus.Analyzers/Models/TypeNames.cs new file mode 100644 index 0000000..9032145 --- /dev/null +++ b/Janus.Analyzers/Models/TypeNames.cs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using Lyra; + +internal record TypeNames +{ + public TypeNames(UnionModel model) + { + IUnionFactory = ComponentFactory.TypeName($"global::RhoMicro.CodeAnalysis.IUnionFactory<{new UnionTypeNameComponent(model)}>"); + } + + public TypeNameComponent IUnion { get; } = ComponentFactory.TypeName("global::RhoMicro.CodeAnalysis.IUnion"); + public TypeNameComponent IUnionFactory { get; } +} diff --git a/Janus.Analyzers/Models/UnionModel.cs b/Janus.Analyzers/Models/UnionModel.cs new file mode 100644 index 0000000..1a86e2c --- /dev/null +++ b/Janus.Analyzers/Models/UnionModel.cs @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Text; +using Library.Models; +using Library.Models.Collections; +using Microsoft.CodeAnalysis; + +internal sealed partial record UnionModel( + UnionTypeKind TypeKind, + String Name, + String Namespace, + EquatableList ContainingTypes, + EquatableList Variants, + EquatableList VariantGroups, + EquatableList TypeParameters, + UnionTypeSettingsAttribute.Model Settings, + Boolean IsToStringUserProvided, + String DocsCommentId, + Boolean EmitDocsComment) +{ + [field: MaybeNull] public TypeNames TypeNames => field ??= new TypeNames(this); + + private static readonly SymbolDisplayFormat _namespaceFormat = new( + globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, + typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces); + + private static Boolean HasUnsupportedStateSettingsUnaware(INamedTypeSymbol target, CancellationToken ct) + { + ct.ThrowIfCancellationRequested(); + + // type keywords would be incorrectly emitted + if (target.IsRecord) + { + return true; + } + + // partial class would not be emitted as static + if (target.IsStatic) + { + return true; + } + + // ref structs introduce (for now) unsupported complexities + if (target.IsRefLikeType) + { + return true; + } + + return false; + } + + public UnionModelValidation GetValidation(CancellationToken ct) => new(this, ct); + + public static Boolean TryCreate( + INamedTypeSymbol target, + [NotNullWhen(true)] out UnionModel? model, + CancellationToken ct) + { + ct.ThrowIfCancellationRequested(); + + if (HasUnsupportedStateSettingsUnaware(target, ct)) + { + model = null; + return false; + } + + model = Create(target, ct); + + if (model.GetValidation(ct).HasUnsupportedSettingsUnawareStatePostCondition(target)) + { + model = null; + return false; + } + + return true; + } + + public static UnionModel Create( + INamedTypeSymbol target, + CancellationToken ct) + { + using var ctx = ModelCreationContext.CreateDefault(ct); + + var docsCommentId = target.GetDocumentationCommentId() ?? String.Empty; + + var variantsList = new List(); + var variantGroupsList = new List(); + var typeParameters = ctx.CollectionFactory.CreateList(); + ParseTargetAttributes( + target, + variantsList, + variantGroupsList, + out var settings, + ct); + ParseTargetTypeParametersAttributes( + target: target, + variants: variantsList, + variantGroups: variantGroupsList, + typeParameters: typeParameters, ct: ct); + + var variants = SortVariants(variantsList, ctx); + var variantGroups = SortVariantGroups(variantGroupsList, ctx); + + var typeKind = target.TypeKind is Microsoft.CodeAnalysis.TypeKind.Struct + ? UnionTypeKind.Struct + : UnionTypeKind.Class; + var containingTypes = ctx.CollectionFactory.CreateList(); + if (target.ContainingType is { } t) + { + AppendContainingType(t, containingTypes, in ctx); + } + + var isToStringUserProvided = + target.GetMembers(nameof(ToString)).Any(m => m is IMethodSymbol { Parameters: [] }); + + var emitDocsComment = target.GetDocumentationCommentXml(cancellationToken: ct) is null or []; + + var result = new UnionModel( + TypeKind: typeKind, + Name: target.Name, + Namespace: target.ContainingNamespace.ToDisplayString(_namespaceFormat), + ContainingTypes: containingTypes, + Variants: variants, + VariantGroups: variantGroups, + TypeParameters: typeParameters, + Settings: settings, + IsToStringUserProvided: isToStringUserProvided, + DocsCommentId: docsCommentId, + EmitDocsComment: emitDocsComment + ); + + return result; + } + + private static EquatableList SortVariantGroups( + List variantGroupsList, + in ModelCreationContext ctx) + { + ctx.ThrowIfCancellationRequested(); + + variantGroupsList.Sort((x, y) => x.CompareTo(y, StringComparison.Ordinal)); + variantGroupsList.Insert(0, "None"); + var variantGroups = ctx.CollectionFactory.CreateList(); + foreach (var variantGroup in variantGroupsList) + { + ctx.ThrowIfCancellationRequested(); + + variantGroups.Add(variantGroup); + } + + return variantGroups; + } + + private static EquatableList SortVariants( + List variantsList, + in ModelCreationContext ctx) + { + ctx.ThrowIfCancellationRequested(); + + variantsList.Sort((x, y) => + { + var xTypePrecedence = getTypePrecedence(x.Type); + var yTypePrecedence = getTypePrecedence(y.Type); + // negative order means x precedes y in sort order, x has higher precedence than y + // positive order means y precedes x in sort order, y has higher precedence than x + var typeOrder = yTypePrecedence - xTypePrecedence; + + var result = typeOrder == 0 + ? x.Name.CompareTo(y.Name, StringComparison.Ordinal) + : typeOrder; + + return result; + + static Int32 getTypePrecedence(VariantTypeModel type) + { + var result = type switch + { + { Kind: VariantTypeKind.Unmanaged } => 3, + { Kind: VariantTypeKind.Value } => 2, + { Kind: VariantTypeKind.Reference or VariantTypeKind.Unknown, IsNullable: true } => 1, + _ => 0 + }; + + return result; + } + }); + var variants = ctx.CollectionFactory.CreateList(); + foreach (var variant in variantsList) + { + ctx.ThrowIfCancellationRequested(); + + variants.Add(variant); + } + + return variants; + } + + private static void AppendContainingType( + INamedTypeSymbol type, + EquatableList containingTypes, + in ModelCreationContext ctx) + { + ctx.ThrowIfCancellationRequested(); + + if (type.ContainingType is { } t) + { + AppendContainingType(t, containingTypes, in ctx); + } + + var typeParameters = ctx.CollectionFactory.CreateList(); + + for (var i = 0; i < type.TypeParameters.Length; i++) + { + ctx.ThrowIfCancellationRequested(); + + typeParameters.Add(type.TypeParameters[i].Name); + } + + var containingType = new ContainingTypeModel( + $"{(type.IsRecord ? "record " : String.Empty)}{(type.TypeKind is Microsoft.CodeAnalysis.TypeKind.Struct ? "struct" : "class")}", + type.Name, + typeParameters); + + containingTypes.Add(containingType); + } + + private static void ParseTargetTypeParametersAttributes( + INamedTypeSymbol target, + List variants, + List variantGroups, + EquatableList typeParameters, + CancellationToken ct) + { + foreach (var typeParameter in target.TypeParameters) + { + ct.ThrowIfCancellationRequested(); + + typeParameters.Add(typeParameter.Name); + + var attributes = typeParameter.GetAttributes(); + foreach (var attributeData in attributes) + { + ct.ThrowIfCancellationRequested(); + + if (!attributeData.TryGetUnionTypeAttributeModel( + new UnionTypeAttribute.Model.TypeParameterState(typeParameter), + out var variant, + cancellationToken: ct)) + { + continue; + } + + variants.Add(variant); + + AddDistinctVariantGroups(variant, variantGroups, ct); + } + } + } + + private static void ParseTargetAttributes( + INamedTypeSymbol target, + List variants, + List variantGroups, + out UnionTypeSettingsAttribute.Model settings, + CancellationToken ct) + { + settings = UnionTypeSettingsAttribute.Model.Default; + + foreach (var attributeData in target.GetAttributes()) + { + ct.ThrowIfCancellationRequested(); + + if (attributeData.TryGetUnionTypeSettingsAttributeModel(out var s, cancellationToken: ct)) + { + settings = s; + continue; + } + + if (attributeData.AttributeClass?.TypeArguments is not [_, ..] typeArguments) + { + continue; + } + + foreach (var typeArgument in typeArguments) + { + ct.ThrowIfCancellationRequested(); + + if (!attributeData.TryGetUnionTypeAttributeModel( + new UnionTypeAttribute.Model.TypeArgumentState(typeArgument), + out var variant, + cancellationToken: ct)) + { + continue; + } + + variants.Add(variant); + + AddDistinctVariantGroups(variant, variantGroups, ct); + } + } + } + + private static void AddDistinctVariantGroups( + UnionTypeAttribute.Model variant, List variantGroups, + CancellationToken ct) + { + ct.ThrowIfCancellationRequested(); + + foreach (var group in variant.Groups) + { + ct.ThrowIfCancellationRequested(); + + if (!variantGroups.Contains(group)) + { + variantGroups.Add(group); + } + } + } +} diff --git a/Janus.Analyzers/Models/UnionModelValidation.cs b/Janus.Analyzers/Models/UnionModelValidation.cs new file mode 100644 index 0000000..cb04f0a --- /dev/null +++ b/Janus.Analyzers/Models/UnionModelValidation.cs @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +using Microsoft.CodeAnalysis; + +internal readonly struct UnionModelValidation(UnionModel model, CancellationToken ct) +{ + public Boolean HasUnsupportedSettingsUnawareStatePostCondition(INamedTypeSymbol target) + { + ct.ThrowIfCancellationRequested(); + + if (HasUnsupportedSettingsUnawareState) + { + return true; + } + + var unionName = target.ToDisplayString(UnionTypeAttribute.Model.TypeDisplayFormat); + foreach (var variant in model.Variants) + { + ct.ThrowIfCancellationRequested(); + + if (variant.Type.Name == unionName) + { + return true; + } + } + + return false; + } + + public bool HasUnsupportedSettingsUnawareState + { + get + { + ct.ThrowIfCancellationRequested(); + + if (ExceedsVariantGroupsLimit) + { + return true; + } + + if (ContainsDuplicateVariantNames) + { + return true; + } + + if (ContainsObjectVariant) + { + return true; + } + + if (ContainsValueTypeVariantAsStructUnion) + { + return true; + } + + if (ContainsDuplicateNullableValueTypeVariants) + { + return true; + } + + return false; + } + } + + public Boolean ContainsDuplicateNullableValueTypeVariants + { + get + { + ct.ThrowIfCancellationRequested(); + + var handledTypes = new HashSet(); + + foreach (var variant in model.Variants) + { + ct.ThrowIfCancellationRequested(); + + if (!handledTypes.Add(variant.Type.Name)) + { + return true; + } + } + + return false; + } + } + + public Boolean ContainsValueTypeVariantAsStructUnion + { + get + { + ct.ThrowIfCancellationRequested(); + + if (model.TypeKind is UnionTypeKind.Class) + { + return false; + } + + foreach (var variant in model.Variants) + { + ct.ThrowIfCancellationRequested(); + + if (variant.Type.Name is "global::System.ValueType") + { + return true; + } + } + + return false; + } + } + + public Boolean ContainsObjectVariant + { + get + { + ct.ThrowIfCancellationRequested(); + + foreach (var variant in model.Variants) + { + ct.ThrowIfCancellationRequested(); + + if (variant.Type.Name is "object") + { + return true; + } + } + + return false; + } + } + + public bool HasUnsupportedSettingsAwareState + { + get + { + ct.ThrowIfCancellationRequested(); + + if (IsGenericJsonSerializable) + { + return true; + } + + return false; + } + } + + public bool ExceedsVariantGroupsLimit + { + get + { + ct.ThrowIfCancellationRequested(); + + if (model.VariantGroups.Count > 31) + { + return true; + } + + return false; + } + } + + private bool IsGenericJsonSerializable + { + get + { + ct.ThrowIfCancellationRequested(); + + if (model.Settings.JsonConverterSetting is JsonConverterSetting.OmitJsonConverter) + { + return false; + } + + var isExplicitlyGeneric = model.TypeParameters is not []; + if (isExplicitlyGeneric) + { + return true; + } + + foreach (var containingType in model.ContainingTypes) + { + ct.ThrowIfCancellationRequested(); + + if (containingType.TypeParameters is not []) + { + return true; + } + } + + return false; + } + } + + public bool ContainsDuplicateVariantNames + { + get + { + ct.ThrowIfCancellationRequested(); + + var variantNames = new HashSet(); + foreach (var variant in model.Variants) + { + ct.ThrowIfCancellationRequested(); + + if (!variantNames.Add(variant.Name)) + { + return true; + } + } + + return false; + } + } +} diff --git a/Janus.Analyzers/Models/UnionTypeAttribute.Model.cs b/Janus.Analyzers/Models/UnionTypeAttribute.Model.cs new file mode 100644 index 0000000..46e03a6 --- /dev/null +++ b/Janus.Analyzers/Models/UnionTypeAttribute.Model.cs @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis; + +using System.Runtime.InteropServices; +using Janus; +using Microsoft.CodeAnalysis; + +partial class UnionTypeAttribute +{ + [StructLayout(LayoutKind.Auto)] + partial record struct Model + { + internal static readonly SymbolDisplayFormat TypeDisplayFormat = new SymbolDisplayFormat( + globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Included, + typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, + genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, + miscellaneousOptions: + SymbolDisplayMiscellaneousOptions.ExpandNullable | + SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers | + SymbolDisplayMiscellaneousOptions.UseSpecialTypes); + + [InitializationMethod(StateTypeName = "TypeParameterState")] + private void Initialize(ITypeParameterSymbol target, CancellationToken ct) + { + Name ??= target.Name; + var name = target.Name; + var docsId = target.GetDocumentationCommentId() ?? String.Empty; + Type = target switch + { + { HasUnmanagedTypeConstraint: true } => + new(VariantTypeKind.Unmanaged, + IsNullable: false, + IsInterface: false, + Name: name, + DocsId: docsId), + { HasValueTypeConstraint: true } => + new(VariantTypeKind.Value, + IsNullable: false, + IsInterface: false, + Name: name, + DocsId: docsId), + { HasReferenceTypeConstraint: true, HasNotNullConstraint: true } => + new(VariantTypeKind.Reference, + IsNullable: false, + IsInterface: false, + Name: name, + DocsId: docsId), + { HasReferenceTypeConstraint: true, HasNotNullConstraint: false } => + new(VariantTypeKind.Reference, + IsNullable: true, + IsInterface: false, + Name: name, + DocsId: docsId), + _ => + new(VariantTypeKind.Unknown, + IsNullable: false, + IsInterface: false, + Name: name, + DocsId: docsId), + }; + } + + [InitializationMethod(StateTypeName = "TypeArgumentState")] + private void Initialize(ITypeSymbol variant, CancellationToken ct) + { + var isInterface = variant.TypeKind is TypeKind.Interface; + var docsId = variant.GetDocumentationCommentId() ?? String.Empty; + + if (variant is INamedTypeSymbol + { + OriginalDefinition: + { + SpecialType: SpecialType.System_Nullable_T + }, + TypeArguments: [{ } actualVariant] + }) + { + Name ??= actualVariant.Name; + var name = actualVariant.ToDisplayString(TypeDisplayFormat); + Type = new( + actualVariant.IsUnmanagedType ? VariantTypeKind.Unmanaged : VariantTypeKind.Value, + IsNullable: true, + IsInterface: isInterface, + Name: name, + DocsId: docsId); + } + else + { + Name ??= variant.Name; + var name = variant.ToDisplayString(TypeDisplayFormat); + Type = variant switch + { + { IsUnmanagedType: true } => + new(VariantTypeKind.Unmanaged, + IsNullable: false, + IsInterface: isInterface, + Name: name, + DocsId: docsId), + { IsValueType: true } => + new(VariantTypeKind.Value, + IsNullable: false, + IsInterface: isInterface, + Name: name, + DocsId: docsId), + { IsReferenceType: true } => + new(VariantTypeKind.Reference, + IsNullable: IsNullable, + IsInterface: isInterface, + Name: name, + DocsId: docsId), + _ => + new(VariantTypeKind.Unknown, + IsNullable: false, + IsInterface: false, + Name: name, + DocsId: docsId), + }; + } + } + + public VariantTypeModel Type { get; private set; } + } + + [DefaultValue((String[])[])] + public override String[] Groups + { + get => base.Groups; + set => base.Groups = value; + } + + public override String? Name + { + get => base.Name; + set => base.Name = value; + } + + public override String? Description + { + get => base.Description; + set => base.Description = value; + } + + public override Boolean IsNullable + { + get => base.IsNullable; + set => base.IsNullable = value; + } +} diff --git a/Janus.Analyzers/Models/UnionTypeKind.cs b/Janus.Analyzers/Models/UnionTypeKind.cs new file mode 100644 index 0000000..2b3767c --- /dev/null +++ b/Janus.Analyzers/Models/UnionTypeKind.cs @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +internal enum UnionTypeKind : byte +{ + Class, + Struct +} diff --git a/Janus.Analyzers/Models/UnionTypeSettingsAttribute.Model.cs b/Janus.Analyzers/Models/UnionTypeSettingsAttribute.Model.cs new file mode 100644 index 0000000..4c80586 --- /dev/null +++ b/Janus.Analyzers/Models/UnionTypeSettingsAttribute.Model.cs @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis; + +partial class UnionTypeSettingsAttribute +{ + partial record struct Model + { + public static Model InheritanceRoot { get; } = new() + { + EqualityOperatorsSetting = EqualityOperatorsSetting.EmitOperatorsIfValueType, + ToStringSetting = ToStringSetting.Detailed, + JsonConverterSetting = JsonConverterSetting.OmitJsonConverter + }; + + public void Inherit(Model source) + { + if (EqualityOperatorsSetting is EqualityOperatorsSetting.Inherit) + { + EqualityOperatorsSetting = source.EqualityOperatorsSetting; + } + + if (ToStringSetting is ToStringSetting.Inherit) + { + ToStringSetting = source.ToStringSetting; + } + + if (JsonConverterSetting is JsonConverterSetting.Inherit) + { + JsonConverterSetting = source.JsonConverterSetting; + } + } + } +} diff --git a/Janus.Analyzers/Models/VariantTypeKind.cs b/Janus.Analyzers/Models/VariantTypeKind.cs new file mode 100644 index 0000000..08a68af --- /dev/null +++ b/Janus.Analyzers/Models/VariantTypeKind.cs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +internal enum VariantTypeKind : byte +{ + Unmanaged, + Value, + Reference, + Unknown +} diff --git a/Janus.Analyzers/Models/VariantTypeModel.cs b/Janus.Analyzers/Models/VariantTypeModel.cs new file mode 100644 index 0000000..11af356 --- /dev/null +++ b/Janus.Analyzers/Models/VariantTypeModel.cs @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus; + +internal readonly record struct VariantTypeModel( + VariantTypeKind Kind, + Boolean IsNullable, + Boolean IsInterface, + String Name, + String DocsId) +{ + public String NullableName { get; } = IsNullable || Kind is VariantTypeKind.Unknown ? Name + "?" : Name; + + public String NullableValueTypeName => + Kind is VariantTypeKind.Reference or VariantTypeKind.Unknown ? Name : NullableName; + + public Boolean Equals(VariantTypeModel other) => + other.Kind == Kind + && other.IsNullable == IsNullable + && other.Name == Name; + + public override Int32 GetHashCode() => + HashCode.Combine(Kind, IsNullable, Name); + + public override String ToString() => throw new NotSupportedException(); +} diff --git a/Janus.Analyzers/Properties/launchSettings.json b/Janus.Analyzers/Properties/launchSettings.json new file mode 100644 index 0000000..fe65332 --- /dev/null +++ b/Janus.Analyzers/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "EndToEnd.Tests": { + "commandName": "DebugRoslynComponent", + "targetProject": "../Janus.EndToEnd.Tests/Janus.EndToEnd.Tests.csproj" + } + } +} diff --git a/Janus.Analyzers/UnionTypeAttribute.cs b/Janus.Analyzers/UnionTypeAttribute.cs new file mode 100644 index 0000000..a4dff0c --- /dev/null +++ b/Janus.Analyzers/UnionTypeAttribute.cs @@ -0,0 +1,146 @@ +// +// SPDX-License-Identifier: MPL-2.0 +// +#nullable enable +#pragma warning disable + +namespace RhoMicro.CodeAnalysis +{ + /// + /// Marks the target type as a union type. + /// + abstract class UnionTypeBaseAttribute : global::System.Attribute + { + /// + /// Gets or sets the groups that the variant is to be a part of. + /// Variants that share a group may be checked for using e.g.: + /// myUnion.Variant.Groups.ContainsMyGroup where MyGroup + /// is the name of the group that the variant is a part of. + /// + public virtual string[] Groups { get; set; } = global::System.Array.Empty(); + } + + /// + /// Marks the target type as a union type. + /// + abstract class NamedUnionTypeBaseAttribute : UnionTypeBaseAttribute + { + /// + /// Gets or sets the name to use for members representing the variant of the union. + /// For example, the variant would be represented using names like + /// List_of_T. Setting this property to MyName will instruct the generator to use + /// member names like MyName instead of List_of_T. Use this property to avoid + /// name collisions in generated code. Since the name will be used for member names, it is + /// required to also be a valid C# identifier. + /// + public virtual string? Name { get; set; } + + /// + /// Gets or sets the description of the variant. + /// This value will be used in documentation comments, so escaping special characters might be necessary. + /// + public virtual string? Description { get; set; } + + /// + /// Gets or sets a value indicating whether to generate the reference type variant as nullable. + /// This setting is only relevant for reference types or generic type parameters not constrained + /// to . In order to use nullable value types, use a + /// variant type. + /// + public virtual bool IsNullable { get; set; } + } + + /// + /// Marks the target type as a union type with the variant . + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Struct | global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + sealed partial class UnionTypeAttribute : NamedUnionTypeBaseAttribute + { } + /// + /// Marks the target type as a union type with the variants + /// + /// and . + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Struct | global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + sealed partial class UnionTypeAttribute : UnionTypeBaseAttribute + { } + /// + /// Marks the target type as a union type with the variants + /// , + /// + /// and . + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Struct | global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + sealed partial class UnionTypeAttribute : UnionTypeBaseAttribute + { } + /// + /// Marks the target type as a union type with the variants + /// , + /// , + /// + /// and . + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Struct | global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + sealed partial class UnionTypeAttribute : UnionTypeBaseAttribute + { } + /// + /// Marks the target type as a union type with the variants + /// , + /// , + /// , + /// + /// and . + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Struct | global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + sealed partial class UnionTypeAttribute : UnionTypeBaseAttribute + { } + /// + /// Marks the target type as a union type with the variants + /// , + /// , + /// , + /// , + /// + /// and . + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Struct | global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + sealed partial class UnionTypeAttribute : UnionTypeBaseAttribute + { } + /// + /// Marks the target type as a union type with the variants + /// , + /// , + /// , + /// , + /// , + /// + /// and . + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Struct | global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + sealed partial class UnionTypeAttribute : UnionTypeBaseAttribute + { } + /// + /// Marks the target type as a union type with the variants + /// , + /// , + /// , + /// , + /// , + /// , + /// + /// and . + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Struct | global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + sealed partial class UnionTypeAttribute : UnionTypeBaseAttribute + { } + /// + /// Marks the target type as a union type with one of the variants being the annotated type parameter. + /// + [global::System.AttributeUsage(global::System.AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] +#if RHOMICRO_CODEANALYSIS_JANUS_ANALYZERS + [global::RhoMicro.CodeAnalysis.IncludeFile] + [RhoMicro.CodeAnalysis.GenerateFactory(GenerateModelTypeAsStruct = true)] +#endif + sealed partial class UnionTypeAttribute : NamedUnionTypeBaseAttribute + { } +} diff --git a/Janus.Analyzers/UnionTypeSettingsAttribute.cs b/Janus.Analyzers/UnionTypeSettingsAttribute.cs new file mode 100644 index 0000000..ae3019e --- /dev/null +++ b/Janus.Analyzers/UnionTypeSettingsAttribute.cs @@ -0,0 +1,126 @@ +// +// SPDX-License-Identifier: MPL-2.0 +// +#nullable enable +#pragma warning disable + +namespace RhoMicro.CodeAnalysis +{ + /// + /// Defines settings for generating an implementation of . + /// + enum ToStringSetting + { + /// + /// Inherits the setting. This is the default value. + /// + /// If the target is a type, it will inherit the setting from its containing assembly. + /// If the target is an assembly, the setting will be used. + /// + /// + Inherit, + /// + /// The generator will emit an implementation that returns detailed information, including: + /// + /// the name of the union type + /// the set of variants + /// an indication of which variant is being represented by the instance + /// the value currently being represented by the instance + /// + /// + Detailed, + /// + /// The generator will not generate an implementation of . + /// + None, + /// + /// The generator will generate an implementation that returns the result of calling on the currently represented value. + /// + Simple + } + + /// + /// Defines settings pertaining to equality operator implementations. + /// + enum EqualityOperatorsSetting + { + /// + /// Inherits the setting. This is the default value. + /// + /// If the target is a type, it will inherit the setting from its containing assembly. + /// If the target is an assembly, the setting will be used. + /// + /// + Inherit, + /// + /// Equality operators will be emitted only if the target union type is a value type. + /// + EmitOperatorsIfValueType, + /// + /// Equality operators will be emitted. + /// + EmitOperators, + /// + /// Equality operators will be omitted. + /// + OmitOperators + } + + /// + /// Defiles settings pertaining to JSON converter implementations. + /// + enum JsonConverterSetting + { + /// + /// Inherits the setting. This is the default value. + /// + /// If the target is a type, it will inherit the setting from its containing assembly. + /// If the target is an assembly, the setting will be used. + /// + /// + Inherit, + /// + /// No JSON converter implementation is emitted. + /// + OmitJsonConverter, + /// + /// A JSON converter implementation is emitted. + /// + EmitJsonConverter + } + + /// + /// Supplies the generator with additional settings on how to generate a targeted union type. + /// If the target member is an assembly, the attribute supplies default values for any union + /// type setting not defined. + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Struct | global::System.AttributeTargets.Class | global::System.AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)] +#if RHOMICRO_CODEANALYSIS_JANUS_ANALYZERS + [global::RhoMicro.CodeAnalysis.IncludeFile] + [RhoMicro.CodeAnalysis.GenerateFactory(GenerateModelTypeAsStruct = true)] +#endif + sealed partial class UnionTypeSettingsAttribute : global::System.Attribute + { + /// + /// Defines how to generate an implementation for . + /// + public ToStringSetting ToStringSetting { get; set; } + /// + /// Indicates how to generate equality operators. + /// By default, equality operators will only be emitted for value types, to preserve + /// reference equality for comparing reference union types via == or !=. + /// + public EqualityOperatorsSetting EqualityOperatorsSetting { get; set; } + + + /// + /// Gets or sets a value indicating whether to make the union type JSON serializable. + /// + /// + /// The generated JSON serialization support is not AOT-compatible. + /// If you require this as a feature, please open an issue with the maintainer. + /// + public JsonConverterSetting JsonConverterSetting { get; set; } + + } +} diff --git a/Janus.CodeFixes/Janus.CodeFixes.csproj b/Janus.CodeFixes/Janus.CodeFixes.csproj new file mode 100644 index 0000000..0fe721c --- /dev/null +++ b/Janus.CodeFixes/Janus.CodeFixes.csproj @@ -0,0 +1,19 @@ + + + + netstandard2.0 + preview + enable + true + true + + + + + + + + + + + diff --git a/Janus.CodeFixes/JanusCodeFixProvider.cs b/Janus.CodeFixes/JanusCodeFixProvider.cs new file mode 100644 index 0000000..80162d3 --- /dev/null +++ b/Janus.CodeFixes/JanusCodeFixProvider.cs @@ -0,0 +1,124 @@ +using System; + +namespace RhoMicro.CodeAnalysis.Janus; + +using System.Collections.Immutable; +using System.Composition; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +/// +/// Provides code fixes for the Janus analyzer. +/// +[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(JanusCodeFixProvider)), Shared] +public class JanusCodeFixProvider : CodeFixProvider +{ + /// + public sealed override ImmutableArray FixableDiagnosticIds { get; } = + [ + DiagnosticIds.ClassUnionsShouldBeSealed + ]; + + /// + public override FixAllProvider GetFixAllProvider() + => FixAllProvider.Create(async (context, document, diagnostics) => + { + var ct = context.CancellationToken; + + ct.ThrowIfCancellationRequested(); + + if (diagnostics.IsEmpty) + { + return null; + } + + return await GetTransformedDocumentAsync( + document, + diagnostics, + context.CancellationToken) + .ConfigureAwait(false); + }); + + /// + public override Task RegisterCodeFixesAsync(CodeFixContext context) + { + var ct = context.CancellationToken; + + ct.ThrowIfCancellationRequested(); + + foreach (var diagnostic in context.Diagnostics) + { + ct.ThrowIfCancellationRequested(); + + context.RegisterCodeFix( + CodeAction.Create( + "Seal union type", + cancellationToken => GetTransformedDocumentAsync(context.Document, [diagnostic], cancellationToken), + equivalenceKey: nameof(JanusCodeFixProvider)), + diagnostic); + } + + return Task.CompletedTask; + } + + private static async Task GetTransformedDocumentAsync( + Document document, + ImmutableArray diagnosticsToFix, + CancellationToken ct) + { + ct.ThrowIfCancellationRequested(); + + var syntaxRoot = await document.GetSyntaxRootAsync(ct); + + if (syntaxRoot is null) + { + return document; + } + + var nodesToReplace = diagnosticsToFix + .Select(d => + { + var result = syntaxRoot.FindNode(d.Location.SourceSpan) is TypeDeclarationSyntax s + ? s + : null; + + return result; + }) + .OfType(); + + var transformedSyntaxRoot = syntaxRoot.ReplaceNodes( + nodesToReplace, + (_, t) => + { + if (t.Modifiers.Any(SyntaxKind.SealedKeyword)) + { + return t; + } + + SyntaxTokenList modifiedModifiers; + var sealedToken = SyntaxFactory.Token(SyntaxKind.SealedKeyword); + if (t.Modifiers.FirstOrDefault(st => st.IsKind(SyntaxKind.PartialKeyword)) is + { + RawKind: (Int32)SyntaxKind.PartialKeyword + } partialToken) + { + modifiedModifiers = t.Modifiers.ReplaceRange(partialToken, [sealedToken, partialToken]); + } + else + { + modifiedModifiers = t.Modifiers.Add(sealedToken); + } + + var modifiedNode = t.WithModifiers(modifiedModifiers); + + return modifiedNode; + }); + + var transformedDocument = document.WithSyntaxRoot(transformedSyntaxRoot); + + return transformedDocument; + } +} diff --git a/Janus.Library/IUnion.cs b/Janus.Library/IUnion.cs new file mode 100644 index 0000000..ffc4ffb --- /dev/null +++ b/Janus.Library/IUnion.cs @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis; + +using System.Diagnostics.CodeAnalysis; + +/// +/// Represents a union type. +/// +public interface IUnion +{ + /// + /// Maps the variant value represented by this instance to a union of type . + /// + /// + /// The factory to use when creating an instance of . + /// + /// + /// The type of union to map to. + /// + /// + /// The type of factory to use when creating an instance of . + /// + /// + /// A new instance of representing the value represented by this instance. + /// + TUnion MapTo(TFactory factory) + where TFactory : IUnionFactory; + + + /// + /// Attempts to map the variant value represented by this instance to a union of type . + /// + /// + /// The factory to use when attempting to create an instance of . + /// + /// + /// A new instance of representing the value represented by this instance, + /// if the factory indicates success; otherwise, . + /// + /// + /// The type of union to attempt to map to. + /// + /// + /// The type of factory to use when attempting to create an instance of . + /// + /// + /// if the factory indicates success; otherwise, . + /// + bool TryMapTo(TFactory factory, [NotNullWhen(true)] out TUnion? union) + where TFactory : IUnionFactory; +} diff --git a/Janus.Library/IUnionFactory.cs b/Janus.Library/IUnionFactory.cs new file mode 100644 index 0000000..f1fa0d3 --- /dev/null +++ b/Janus.Library/IUnionFactory.cs @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis; + +using System.Diagnostics.CodeAnalysis; + +/// +/// Creates instances of . +/// +/// +/// The type of union to create. +/// +public interface IUnionFactory +{ + /// + /// Creates a new instance of from a variant value. + /// + /// + /// The value to create an instance of from. + /// + /// + /// The type of variant to create an instance of from. + /// + /// + /// A new instance of , containing . + /// + TUnion Create(TVariant value); + + /// + /// Attempts to create a new instance of from a variant value. + /// + /// + /// The value to attempt to create an instance of from. + /// + /// + /// The created instance of if + /// can represent ; otherwise, . + /// + /// + /// The type of the variant to create an instance of from. + /// + /// + /// if an instance of could be created; + /// otherwise, . + /// + bool TryCreate(TVariant value, [NotNullWhen(true)] out TUnion? union); +} diff --git a/Janus.Library/Janus.Library.csproj b/Janus.Library/Janus.Library.csproj new file mode 100644 index 0000000..83a6cd8 --- /dev/null +++ b/Janus.Library/Janus.Library.csproj @@ -0,0 +1,16 @@ + + + + netstandard2.0 + preview + enable + + + + + + + + + + diff --git a/Janus.Library/NotNullWhenAttribute.cs b/Janus.Library/NotNullWhenAttribute.cs new file mode 100644 index 0000000..b46b7f9 --- /dev/null +++ b/Janus.Library/NotNullWhenAttribute.cs @@ -0,0 +1,28 @@ +#pragma warning disable +#nullable enable annotations + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Diagnostics.CodeAnalysis +{ + /// + /// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. + /// + [global::System.AttributeUsage(global::System.AttributeTargets.Parameter, Inherited = false)] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + internal sealed class NotNullWhenAttribute : global::System.Attribute + { + /// + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + public NotNullWhenAttribute(bool returnValue) + { + ReturnValue = returnValue; + } + + /// Gets the return value condition. + public bool ReturnValue { get; } + } +} diff --git a/Janus.Library/README.md b/Janus.Library/README.md new file mode 100644 index 0000000..199a7e8 --- /dev/null +++ b/Janus.Library/README.md @@ -0,0 +1 @@ +# RhoMicro.CodeAnalysis.Janus.Library diff --git a/UnionsGenerator.EndToEnd.Tests/AsPropertyTests.cs b/Janus.Tests.EndToEnd/AsPropertyTests.cs similarity index 83% rename from UnionsGenerator.EndToEnd.Tests/AsPropertyTests.cs rename to Janus.Tests.EndToEnd/AsPropertyTests.cs index c3eebc8..72c20e7 100644 --- a/UnionsGenerator.EndToEnd.Tests/AsPropertyTests.cs +++ b/Janus.Tests.EndToEnd/AsPropertyTests.cs @@ -2,15 +2,15 @@ #pragma warning disable IDE0059 // Unnecessary assignment of a value #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member -namespace RhoMicro.CodeAnalysis.UnionsGenerator.EndToEnd.Tests; +namespace RhoMicro.CodeAnalysis.Janus.EndToEnd.Tests; using System; public partial class AsPropertyTests { - [UnionType(Alias = "Int")] + [UnionType(Name = "Int")] [UnionType>] - private partial class Union<[UnionType(Alias = "ValueT")] T> + private sealed partial class Union<[UnionType(Name = "ValueT")] T> where T : struct; [Fact] @@ -37,20 +37,20 @@ public void NotAsIntWhenRepresentingByte() public void NotAsListWhenRepresentingInt32() { Union u = 32; - Assert.Equal(default, u.AsList_of_String); + Assert.Equal(default, u.AsList); } [Fact] public void AsListWhenRepresentingList() { var expected = new List(); Union u = expected; - Assert.Equal(expected, u.AsList_of_String); + Assert.Equal(expected, u.AsList); } [Fact] public void NotAsListWhenRepresentingByte() { Union u = (Byte)32; - Assert.Equal(default, u.AsList_of_String); + Assert.Equal(default, u.AsList); } [Fact] diff --git a/Janus.Tests.EndToEnd/EqualityTests.cs b/Janus.Tests.EndToEnd/EqualityTests.cs new file mode 100644 index 0000000..834c383 --- /dev/null +++ b/Janus.Tests.EndToEnd/EqualityTests.cs @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus.EndToEnd.Tests; + +using RhoMicro.CodeAnalysis; +using System; + +public partial class EqualityTests +{ + [UnionType] + private sealed partial class Foo; + + [Fact] + public void EqualisIsValueEquality() + { + Foo expected = 32; + Foo actual = 32; + Assert.Equal(expected, actual); + } +} diff --git a/Janus.Tests.EndToEnd/FactoryTests.cs b/Janus.Tests.EndToEnd/FactoryTests.cs new file mode 100644 index 0000000..efd9044 --- /dev/null +++ b/Janus.Tests.EndToEnd/FactoryTests.cs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus.EndToEnd.Tests; +using System; + +public partial class FactoryTests +{ + [UnionType] + private sealed partial class UnnamedFactoryUnion; + + [Fact] + public void UsesDefaultFactoryName() + { + _ = UnnamedFactoryUnion.Create(0); + } +} diff --git a/UnionsGenerator.Tests/GlobalUsings.cs b/Janus.Tests.EndToEnd/GlobalUsings.cs similarity index 56% rename from UnionsGenerator.Tests/GlobalUsings.cs rename to Janus.Tests.EndToEnd/GlobalUsings.cs index 0b52112..1b42ec1 100644 --- a/UnionsGenerator.Tests/GlobalUsings.cs +++ b/Janus.Tests.EndToEnd/GlobalUsings.cs @@ -1,4 +1,3 @@ // SPDX-License-Identifier: MPL-2.0 global using Xunit; -global using RhoMicro.CodeAnalysis.Library; diff --git a/UnionsGenerator.EndToEnd.Tests/IsPropertyTests.cs b/Janus.Tests.EndToEnd/IsPropertyTests.cs similarity index 83% rename from UnionsGenerator.EndToEnd.Tests/IsPropertyTests.cs rename to Janus.Tests.EndToEnd/IsPropertyTests.cs index ca4cfcb..874b8b2 100644 --- a/UnionsGenerator.EndToEnd.Tests/IsPropertyTests.cs +++ b/Janus.Tests.EndToEnd/IsPropertyTests.cs @@ -2,15 +2,15 @@ #pragma warning disable IDE0059 // Unnecessary assignment of a value #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member -namespace RhoMicro.CodeAnalysis.UnionsGenerator.EndToEnd.Tests; +namespace RhoMicro.CodeAnalysis.Janus.EndToEnd.Tests; using System; public partial class IsPropertyTests { - [UnionType(Alias = "Int")] + [UnionType(Name = "Int")] [UnionType>] - private partial class Union<[UnionType] T>; + private sealed partial class Union<[UnionType] T>; [Fact] public void IsIntWhenRepresentingInt32() @@ -35,19 +35,19 @@ public void IsNotIntWhenRepresentingByte() public void IsNotListWhenRepresentingInt32() { Union u = 32; - Assert.False(u.IsList_of_String); + Assert.False(u.IsList); } [Fact] public void IsListWhenRepresentingList() { Union u = new List(); - Assert.True(u.IsList_of_String); + Assert.True(u.IsList); } [Fact] public void IsNotListWhenRepresentingByte() { Union u = (Byte)32; - Assert.False(u.IsList_of_String); + Assert.False(u.IsList); } [Fact] diff --git a/Janus.Tests.EndToEnd/Janus.Tests.EndToEnd.csproj b/Janus.Tests.EndToEnd/Janus.Tests.EndToEnd.csproj new file mode 100644 index 0000000..2001c2e --- /dev/null +++ b/Janus.Tests.EndToEnd/Janus.Tests.EndToEnd.csproj @@ -0,0 +1,31 @@ + + + + net10.0 + enable + enable + true + true + $(NoWarn);CS1591 + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + diff --git a/UnionsGenerator.EndToEnd.Tests/JsonConverterTests.cs b/Janus.Tests.EndToEnd/JsonConverterTests.cs similarity index 64% rename from UnionsGenerator.EndToEnd.Tests/JsonConverterTests.cs rename to Janus.Tests.EndToEnd/JsonConverterTests.cs index cbdad3b..164868e 100644 --- a/UnionsGenerator.EndToEnd.Tests/JsonConverterTests.cs +++ b/Janus.Tests.EndToEnd/JsonConverterTests.cs @@ -1,28 +1,30 @@ // SPDX-License-Identifier: MPL-2.0 -namespace RhoMicro.CodeAnalysis.UnionsGenerator.EndToEnd.Tests; +namespace RhoMicro.CodeAnalysis.Janus.EndToEnd.Tests; + using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; -using System.Text; using System.Text.Json; -using System.Threading.Tasks; public partial class JsonConverterTests { [UnionType>] + [UnionTypeSettings(JsonConverterSetting = JsonConverterSetting.EmitJsonConverter)] private readonly partial struct Union; [Fact] public void SerializesDateTimeUnion() { - var expected = DateTime.Parse("01/10/2009 7:34"); + var expected = DateTime.Parse("01/10/2009 7:34", CultureInfo.InvariantCulture); Union u = expected; var serialized = JsonSerializer.Serialize(u); var deserialized = JsonSerializer.Deserialize(serialized); - Assert.True(deserialized.IsDateTime); - Assert.Equal(expected, deserialized.AsDateTime); + Assert.True(deserialized.TryCastToDateTime(out var actual)); + Assert.Equal(expected, actual); } + [Fact] public void SerializesDoubleUnion() { @@ -30,9 +32,10 @@ public void SerializesDoubleUnion() Union u = expected; var serialized = JsonSerializer.Serialize(u); var deserialized = JsonSerializer.Deserialize(serialized); - Assert.True(deserialized.IsDouble); - Assert.Equal(expected, deserialized.AsDouble); + Assert.True(deserialized.TryCastToDouble(out var actual)); + Assert.Equal(expected, actual); } + [Fact] public void SerializesStringUnion() { @@ -40,9 +43,10 @@ public void SerializesStringUnion() Union u = expected; var serialized = JsonSerializer.Serialize(u); var deserialized = JsonSerializer.Deserialize(serialized); - Assert.True(deserialized.IsString); - Assert.Equal(expected, deserialized.AsString); + Assert.True(deserialized.TryCastToString(out var actual)); + Assert.Equal(expected, actual); } + [Fact] public void SerializesListUnion() { @@ -50,7 +54,7 @@ public void SerializesListUnion() Union u = expected; var serialized = JsonSerializer.Serialize(u); var deserialized = JsonSerializer.Deserialize(serialized); - Assert.True(deserialized.IsList_of_String); - Assert.True(expected.SequenceEqual(deserialized.AsList_of_String!)); + Assert.True(deserialized.TryCastToList(out var actual)); + Assert.True(expected.SequenceEqual(actual)); } } diff --git a/Janus.Tests.EndToEnd/NullableTests.cs b/Janus.Tests.EndToEnd/NullableTests.cs new file mode 100644 index 0000000..25ae2e9 --- /dev/null +++ b/Janus.Tests.EndToEnd/NullableTests.cs @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus.EndToEnd.Tests; + +using System; + +public partial class NullableTests +{ + [UnionType] + private readonly partial struct NullableBoolUnion; + + [Fact] + public void NullableBoolTrueFactoryCall() + { + var u = NullableBoolUnion.Create((Boolean?)true); + Assert.True(u.IsBoolean); + Assert.True(u.AsBoolean.HasValue); + Assert.True(u.AsBoolean.Value); + } + + [Fact] + public void NullableBoolFalseFactoryCall() + { + var u = NullableBoolUnion.Create((Boolean?)false); + Assert.True(u.IsBoolean); + Assert.True(u.AsBoolean.HasValue); + Assert.False(u.AsBoolean.Value); + } + + [Fact] + public void NullableBoolNullFactoryCall() + { + var u = NullableBoolUnion.Create((Boolean?)null); + Assert.True(u.IsBoolean); + Assert.False(u.AsBoolean.HasValue); + } +} diff --git a/UnionsGenerator.EndToEnd.Tests/ReadMeAssertions.cs b/Janus.Tests.EndToEnd/ReadMeAssertions.cs similarity index 78% rename from UnionsGenerator.EndToEnd.Tests/ReadMeAssertions.cs rename to Janus.Tests.EndToEnd/ReadMeAssertions.cs index 3c43d63..9a1b755 100644 --- a/UnionsGenerator.EndToEnd.Tests/ReadMeAssertions.cs +++ b/Janus.Tests.EndToEnd/ReadMeAssertions.cs @@ -3,21 +3,17 @@ #pragma warning disable IDE0250 #pragma warning disable IDE0059 #pragma warning disable CS1591 -namespace RhoMicro.CodeAnalysis.UnionsGenerator.EndToEnd.Tests; +namespace RhoMicro.CodeAnalysis.Janus.EndToEnd.Tests; + using System; using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; - -using Microsoft.VisualBasic; public partial class ReadMeAssertions { [UnionType] [UnionType] private partial struct IntOrString; + [Fact] public void TypeDeclarationTarget() { @@ -26,6 +22,7 @@ public void TypeDeclarationTarget() } private partial struct GenericUnion<[UnionType] T0, [UnionType] T1>; + [Fact] public void TypeParameterTarget() { @@ -33,24 +30,27 @@ public void TypeParameterTarget() u = GenericUnion.Create(32); } - [UnionType>(Alias = "MultipleNames")] - [UnionType(Alias = "SingleName")] - private partial struct Names; + [UnionType>(Name = "MultipleNames")] + [UnionType(Name = "SingleName")] + private sealed partial class Names; + [Fact] public void AliasExample() { Names n = "John"; - if(n.IsSingleName) + if (n.IsSingleName) { var singleName = n.AsSingleName; - } else if(n.IsMultipleNames) + } + else if (n.IsMultipleNames) { var multipleNames = n.AsMultipleNames; } } - [UnionType(Options = UnionTypeOptions.ImplicitConversionIfSolitary)] + [UnionType] private partial struct Int32Alias; + [Fact] public void Solitary() { @@ -60,6 +60,7 @@ public void Solitary() } private partial struct GenericConvertableUnion<[UnionType] T>; + [Fact] public void SupersetOfParameter() { @@ -68,9 +69,10 @@ public void SupersetOfParameter() } #pragma warning disable CS8604 // Possible null reference argument. - [UnionType(Options = UnionTypeOptions.Nullable)] + [UnionType(IsNullable = true)] [UnionType>] private partial struct NullableStringUnion; + [Fact] public void NullableUnion() { @@ -84,27 +86,28 @@ public void NullableUnion() [UnionType(Groups = ["Number"])] [UnionType(Groups = ["Text"])] private partial struct GroupedUnion; + [Fact] public void GroupedUnions() { GroupedUnion u = "Hello, World!"; - if(u.IsNumberGroup) + if (u.Variant.Group.ContainsNumber) { Assert.Fail("Expected union to be text."); } - if(!u.IsTextGroup) + if (!u.Variant.Group.ContainsText) { Assert.Fail("Expected union to be text."); } u = 32f; - if(!u.IsNumberGroup) + if (!u.Variant.Group.ContainsNumber) { Assert.Fail("Expected union to be number."); } - if(u.IsTextGroup) + if (u.Variant.Group.ContainsText) { Assert.Fail("Expected union to be number."); } diff --git a/UnionsGenerator.EndToEnd.Tests/RelatedTypeConversionTests.cs b/Janus.Tests.EndToEnd/RelatedTypeConversionTests.cs similarity index 76% rename from UnionsGenerator.EndToEnd.Tests/RelatedTypeConversionTests.cs rename to Janus.Tests.EndToEnd/RelatedTypeConversionTests.cs index 341dc0c..f0b8bdf 100644 --- a/UnionsGenerator.EndToEnd.Tests/RelatedTypeConversionTests.cs +++ b/Janus.Tests.EndToEnd/RelatedTypeConversionTests.cs @@ -3,7 +3,7 @@ #pragma warning disable CA1305 // Specify IFormatProvider #pragma warning disable IDE0059 // Unnecessary assignment of a value #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member -namespace RhoMicro.CodeAnalysis.UnionsGenerator.EndToEnd.Tests; +namespace RhoMicro.CodeAnalysis.Janus.EndToEnd.Tests; using System; @@ -12,10 +12,6 @@ public partial class RelatedTypeConversionTests [UnionType] [UnionType] [UnionType] - [Relation] - [Relation] - [Relation] - [Relation] private readonly partial struct Union; [UnionType] @@ -27,15 +23,16 @@ private sealed partial class CongruentUnion; public void StringUnionToCongruent() { Union u = "Hello, World!"; - CongruentUnion cu = u; + var cu = CongruentUnion.Create(u); Assert.Equal(u.AsString, cu.AsString); } + [Fact] public void StringCongruentToUnion() { CongruentUnion cu = "Hello, World!"; - Union u = cu; + var u = Union.Create(cu); Assert.Equal(cu.AsString, u.AsString); } @@ -44,15 +41,16 @@ public void StringCongruentToUnion() public void DoubleUnionToCongruent() { Union u = 32d; - CongruentUnion cu = u; + var cu = CongruentUnion.Create(u); Assert.Equal(u.AsDouble, cu.AsDouble); } + [Fact] public void DoubleCongruentToUnion() { CongruentUnion cu = 32d; - Union u = cu; + var u = Union.Create(cu); Assert.Equal(cu.AsDouble, u.AsDouble); } @@ -61,36 +59,38 @@ public void DoubleCongruentToUnion() public void DateTimeUnionToCongruent() { Union u = DateTime.Parse("01/10/2009 7:34"); - CongruentUnion cu = u; + var cu = CongruentUnion.Create(u); Assert.Equal(u.AsDateTime, cu.AsDateTime); } + [Fact] public void DateTimeCongruentToUnion() { CongruentUnion cu = DateTime.Parse("01/10/2009 7:34"); - Union u = cu; + var u = Union.Create(cu); Assert.Equal(cu.AsDateTime, u.AsDateTime); } [UnionType] [UnionType] - private partial class SubsetUnion; + private sealed partial class SubsetUnion; [Fact] public void StringUnionToSubset() { Union u = "Hello, World!"; - var su = (SubsetUnion)u; + var su = SubsetUnion.Create(u); Assert.Equal(u.AsString, su.AsString); } + [Fact] public void StringSubsetToUnion() { SubsetUnion su = "Hello, World!"; - Union u = su; + var u = Union.Create(su); Assert.Equal(su.AsString, u.AsString); } @@ -99,22 +99,23 @@ public void StringSubsetToUnion() public void DoubleUnionToSubset() { Union u = 32d; - _ = Assert.Throws(() => (SubsetUnion)u); + _ = Assert.Throws(() => SubsetUnion.Create(u)); } [Fact] public void DateTimeUnionToSubset() { Union u = DateTime.Parse("01/10/2009 7:34"); - var su = (SubsetUnion)u; + var su = SubsetUnion.Create(u); Assert.Equal(u.AsDateTime, su.AsDateTime); } + [Fact] public void DateTimeSubsetToUnion() { SubsetUnion su = DateTime.Parse("01/10/2009 7:34"); - Union u = su; + var u = Union.Create(su); Assert.Equal(su.AsDateTime, u.AsDateTime); } @@ -129,15 +130,16 @@ public void DateTimeSubsetToUnion() public void StringUnionToSuperset() { Union u = "Hello, World!"; - SupersetUnion su = u; + var su = SupersetUnion.Create(u); Assert.Equal(u.AsString, su.AsString); } + [Fact] public void StringSupersetToUnion() { SupersetUnion su = "Hello, World!"; - var u = (Union)su; + var u = Union.Create(su); Assert.Equal(su.AsString, u.AsString); } @@ -146,22 +148,23 @@ public void StringSupersetToUnion() public void Int32SupersetToUnion() { SupersetUnion su = 32; - _ = Assert.Throws(() => (Union)su); + _ = Assert.Throws(() => Union.Create(su)); } [Fact] public void DateTimeUnionToSuperset() { Union u = DateTime.Parse("01/10/2009 7:34"); - SupersetUnion su = u; + var su = SupersetUnion.Create(u); Assert.Equal(u.AsDateTime, su.AsDateTime); } + [Fact] public void DateTimeSupersetToUnion() { SupersetUnion su = DateTime.Parse("01/10/2009 7:34"); - var u = (Union)su; + var u = Union.Create(su); Assert.Equal(su.AsDateTime, u.AsDateTime); } @@ -170,15 +173,16 @@ public void DateTimeSupersetToUnion() public void DoubleUnionToSuperset() { Union u = 32d; - SupersetUnion su = u; + var su = SupersetUnion.Create(u); Assert.Equal(u.AsDouble, su.AsDouble); } + [Fact] public void DoubleSupersetToUnion() { SupersetUnion su = 32d; - var u = (Union)su; + var u = Union.Create(su); Assert.Equal(su.AsDouble, u.AsDouble); } @@ -187,41 +191,43 @@ public void DoubleSupersetToUnion() [UnionType] [UnionType] [UnionType>] - private partial class IntersectionUnion; + private sealed partial class IntersectionUnion; [Fact] public void Int16IntersectionToUnion() { IntersectionUnion iu = 32; - _ = Assert.Throws(() => (Union)iu); + _ = Assert.Throws(() => Union.Create(iu)); } + [Fact] public void ListIntersectionToUnion() { IntersectionUnion iu = new List(); - _ = Assert.Throws(() => (Union)iu); + _ = Assert.Throws(() => Union.Create(iu)); } [Fact] public void DateTimeUnionToIntersection() { Union iu = DateTime.Parse("01/10/2009 7:34"); - _ = Assert.Throws(() => (IntersectionUnion)iu); + _ = Assert.Throws(() => IntersectionUnion.Create(iu)); } [Fact] public void StringUnionToIntersection() { Union u = "Hello, World!"; - var iu = (IntersectionUnion)u; + var iu = IntersectionUnion.Create(u); Assert.Equal(u.AsString, iu.AsString); } + [Fact] public void StringIntersectionToUnion() { IntersectionUnion iu = "Hello, World!"; - var u = (Union)iu; + var u = Union.Create(iu); Assert.Equal(iu.AsString, u.AsString); } @@ -230,15 +236,16 @@ public void StringIntersectionToUnion() public void DoubleUnionToIntersection() { Union u = 32d; - var iu = (IntersectionUnion)u; + var iu = IntersectionUnion.Create(u); Assert.Equal(u.AsDouble, iu.AsDouble); } + [Fact] public void DoubleIntersectionToUnion() { IntersectionUnion iu = 32d; - var u = (Union)iu; + var u = Union.Create(iu); Assert.Equal(iu.AsDouble, u.AsDouble); } diff --git a/UnionsGenerator.EndToEnd.Tests/RepresentableTypeConversionTests.cs b/Janus.Tests.EndToEnd/RepresentableTypeConversionTests.cs similarity index 80% rename from UnionsGenerator.EndToEnd.Tests/RepresentableTypeConversionTests.cs rename to Janus.Tests.EndToEnd/RepresentableTypeConversionTests.cs index 334fbc4..70a80d7 100644 --- a/UnionsGenerator.EndToEnd.Tests/RepresentableTypeConversionTests.cs +++ b/Janus.Tests.EndToEnd/RepresentableTypeConversionTests.cs @@ -2,15 +2,15 @@ #pragma warning disable IDE0059 // Unnecessary assignment of a value #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member -namespace RhoMicro.CodeAnalysis.UnionsGenerator.EndToEnd.Tests; +namespace RhoMicro.CodeAnalysis.Janus.EndToEnd.Tests; using System; using System.Numerics; public partial class RepresentableTypeConversionTests { - [UnionType(Alias = "ErrorMessage")] - private readonly partial struct Result<[UnionType(Alias = "Result")] T>; + [UnionType(Name = "ErrorMessage")] + private readonly partial struct Result<[UnionType(Name = "Result")] T>; [Fact] public void IsImplicitlyConvertibleFromString() @@ -40,19 +40,19 @@ public void IsExplicitlyConvertibleToString() public void IsNotExplicitlyConvertibleToString() { Result r = 32; - _ = Assert.Throws(() => (String)r); + _ = Assert.Throws(() => (String)r); } [Fact] public void IsNotExplicitlyConvertibleToInt32() { Result r = "Hello, World!"; - _ = Assert.Throws(() => (Int32)r); + _ = Assert.Throws(() => (Int32)r); } [Fact] public void IsNotExplicitlyConvertibleToChar() { Result r = "Hello, World!"; - _ = Assert.Throws(() => (Char)r); + _ = Assert.Throws(() => (Char)r); } [Fact] public void IsExplicitlyConvertibleToInt32() diff --git a/Janus.Tests.EndToEnd/ToStringTests.cs b/Janus.Tests.EndToEnd/ToStringTests.cs new file mode 100644 index 0000000..b6e03a2 --- /dev/null +++ b/Janus.Tests.EndToEnd/ToStringTests.cs @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Janus.EndToEnd.Tests; +using System; +using System.Collections.Immutable; + +public partial class ToStringTests +{ + [UnionType] + [UnionTypeSettings(ToStringSetting = ToStringSetting.None)] + private sealed partial class NoToStringUnionType; + + [Fact] + public void UsesDefaultToString() + { + NoToStringUnionType u = "Foo"; + var expected = typeof(NoToStringUnionType).FullName; + var actual = u.ToString(); + + Assert.Equal(expected, actual); + } + + [UnionType] + [UnionTypeSettings(ToStringSetting = ToStringSetting.Simple)] + private sealed partial class SimpleToStringUnionType; + [Fact] + public void UsesSimpleToString() + { + const String expected = "Foo"; + SimpleToStringUnionType u = expected; + var actual = u.ToString(); + + Assert.Equal(expected, actual); + } + + [UnionType, string>] + [UnionTypeSettings(JsonConverterSetting = JsonConverterSetting.EmitJsonConverter)] + sealed partial class IntDoubleString; + + [Fact] + public void UsesDetailedToString2() + { + const String expected = "IntDoubleString { Variants: [Double, , ImmutableArray, String], Value: 42 }"; + IntDoubleString u = 42; + var actual = u.ToString(); + + Assert.Equal(expected, actual); + } + + [UnionType] + [UnionTypeSettings(ToStringSetting = ToStringSetting.Detailed)] + private sealed partial class DetailedToStringUnionType; + + [Fact] + public void UsesDetailedToString1() + { + const String expected = "DetailedToStringUnionType { Variants: [Int32, ], Value: Foo }"; + DetailedToStringUnionType u = "Foo"; + var actual = u.ToString(); + + Assert.Equal(expected, actual); + } + + [UnionType] + private sealed partial class CustomToStringUnionType + { + public override String ToString() => "Foo"; + } + + [Fact] + public void UsesCustomToString() + { + CustomToStringUnionType u = "Bar"; + var expected = "Foo"; + var actual = u.ToString(); + + Assert.Equal(expected, actual); + } +} diff --git a/UnionsGenerator.EndToEnd.Tests/TryAsFunctionsTests.cs b/Janus.Tests.EndToEnd/TryAsFunctionsTests.cs similarity index 75% rename from UnionsGenerator.EndToEnd.Tests/TryAsFunctionsTests.cs rename to Janus.Tests.EndToEnd/TryAsFunctionsTests.cs index 432dbbb..a05e142 100644 --- a/UnionsGenerator.EndToEnd.Tests/TryAsFunctionsTests.cs +++ b/Janus.Tests.EndToEnd/TryAsFunctionsTests.cs @@ -2,22 +2,22 @@ #pragma warning disable IDE0059 // Unnecessary assignment of a value #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member -namespace RhoMicro.CodeAnalysis.UnionsGenerator.EndToEnd.Tests; +namespace RhoMicro.CodeAnalysis.Janus.EndToEnd.Tests; using System; public partial class TryAsFunctionsTests { - [UnionType(Alias = "Int")] + [UnionType(Name = "Int")] [UnionType>] - private partial class Union<[UnionType] T>; + private sealed partial class Union<[UnionType] T>; [Fact] public void IsIntWhenRepresentingInt32() { const Int32 expected = 32; Union u = expected; - Assert.True(u.TryAsInt(out var actual)); + Assert.True(u.TryCastToInt(out var actual)); Assert.Equal(expected, actual); } [Fact] @@ -25,7 +25,7 @@ public void IsNotIntWhenRepresentingList() { const Int32 expected = 0; Union u = new List(); - Assert.False(u.TryAsInt(out var actual)); + Assert.False(u.TryCastToInt(out var actual)); Assert.Equal(expected, actual); } [Fact] @@ -33,7 +33,7 @@ public void IsNotIntWhenRepresentingByte() { const Int32 expected = 0; Union u = (Byte)32; - Assert.False(u.TryAsInt(out var actual)); + Assert.False(u.TryCastToInt(out var actual)); Assert.Equal(expected, actual); } @@ -42,7 +42,7 @@ public void IsNotListWhenRepresentingInt32() { List? expected = null; Union u = 32; - Assert.False(u.TryAsList_of_String(out var actual)); + Assert.False(u.TryCastToList(out var actual)); Assert.Equal(expected, actual); } [Fact] @@ -50,7 +50,7 @@ public void IsListWhenRepresentingList() { var expected = new List(); Union u = expected; - Assert.True(u.TryAsList_of_String(out var actual)); + Assert.True(u.TryCastToList(out var actual)); Assert.Equal(expected, actual); } [Fact] @@ -58,7 +58,7 @@ public void IsNotListWhenRepresentingByte() { List? expected = null; Union u = (Byte)32; - Assert.False(u.TryAsList_of_String(out var actual)); + Assert.False(u.TryCastToList(out var actual)); Assert.Equal(expected, actual); } @@ -67,7 +67,7 @@ public void IsNotByteWhenRepresentingInt32() { Byte expected = 0; Union u = 32; - Assert.False(u.TryAsT(out var actual)); + Assert.False(u.TryCastToT(out var actual)); Assert.Equal(expected, actual); } [Fact] @@ -75,7 +75,7 @@ public void IsNotByteWhenRepresentingList() { Byte expected = 0; Union u = new List(); - Assert.False(u.TryAsT(out var actual)); + Assert.False(u.TryCastToT(out var actual)); Assert.Equal(expected, actual); } [Fact] @@ -83,7 +83,7 @@ public void IsByteWhenRepresentingByte() { Byte expected = 32; Union u = expected; - Assert.True(u.TryAsT(out var actual)); + Assert.True(u.TryCastToT(out var actual)); Assert.Equal(expected, actual); } } diff --git a/Janus.Tests/CodeFixTests.cs b/Janus.Tests/CodeFixTests.cs new file mode 100644 index 0000000..483edb6 --- /dev/null +++ b/Janus.Tests/CodeFixTests.cs @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace Janus.Tests; + +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Testing; +using RhoMicro.CodeAnalysis.Janus; + +public class CodeFixTests +{ + [Fact] + public Task ClassUnionsShouldBeSealed() => + JanusTest.TestCodeFix( + source: + """ + using RhoMicro.CodeAnalysis; + + [UnionType] + partial class {|RMJ0018:Union|}; + """, + fixedSource: + """ + using RhoMicro.CodeAnalysis; + + [UnionType] + sealed partial class Union; + """); +} diff --git a/Janus.Tests/Janus.Tests.csproj b/Janus.Tests/Janus.Tests.csproj new file mode 100644 index 0000000..36eae04 --- /dev/null +++ b/Janus.Tests/Janus.Tests.csproj @@ -0,0 +1,41 @@ + + + + enable + enable + Exe + Janus.Tests + net10.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Janus.Tests/JanusAnalyzerTests.cs b/Janus.Tests/JanusAnalyzerTests.cs new file mode 100644 index 0000000..89fc60e --- /dev/null +++ b/Janus.Tests/JanusAnalyzerTests.cs @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace Janus.Tests; + +public class JanusAnalyzerTests +{ + [Theory] + [InlineData("None")] + [InlineData("Simple")] + [InlineData("Detailed")] + public Task ToStringSettingIgnored(String setting) => JanusTest.TestAnalyzer( + $$""" + using RhoMicro.CodeAnalysis; + + [UnionType, UnionTypeSettings( {|RMJ0001:ToStringSetting = ToStringSetting.{{setting}}|} )] + partial struct Union + { + public override string ToString() => throw null; + } + """); + + [Theory] + [InlineData("class ")] + [InlineData("struct ")] + [InlineData("")] + public Task RecordUnionsAreDisallowed(String modifier) => JanusTest.TestAnalyzer( + $$""" + using RhoMicro.CodeAnalysis; + + #pragma warning disable RMJ0018 + + [UnionType] + partial {|RMJ0002:record|} {{modifier}}Union; + """); + + [Fact] + public Task ExplicitlyGenericUnionsCannotBeJsonSerializable() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + [UnionTypeSettings( {|RMJ0003:JsonConverterSetting = JsonConverterSetting.EmitJsonConverter|} )] + partial struct Union<[UnionType] T>; + """); + + [Fact] + public Task TransientlyGenericUnionsCannotBeJsonSerializable() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + + partial class Outer + { + [UnionType] + [UnionTypeSettings( {|RMJ0003:JsonConverterSetting = JsonConverterSetting.EmitJsonConverter|} )] + partial struct Union; + } + """); + + [Fact] + public Task ExplicitlyGenericConstValueUnionsCannotBeJsonSerializable() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + + static class Constants + { + public const JsonConverterSetting Value = JsonConverterSetting.EmitJsonConverter; + } + + [UnionTypeSettings( {|RMJ0003:JsonConverterSetting = Constants.Value|} )] + partial struct Union<[UnionType] T>; + """); + + [Fact] + public Task TransientlyGenericConstValueUnionsCannotBeJsonSerializable() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + + static class Constants + { + public const JsonConverterSetting Value = JsonConverterSetting.EmitJsonConverter; + } + + partial class Outer + { + [UnionType] + [UnionTypeSettings( {|RMJ0003:JsonConverterSetting = Constants.Value|} )] + partial struct Union; + } + """); + + [Fact] + public Task NoMoreThan31VariantGroupsMayBeDefined() => JanusTest.TestAnalyzer( + $$""" + using RhoMicro.CodeAnalysis; + + [UnionType({|RMJ0004:Groups = [{{String.Join(", ", Enumerable.Range(1, 32).Select(i => $"\"Group{i}\""))}}]|})] + partial struct Union; + """); + + [Fact] + public Task StaticUnionsAreDisallowed() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + + #pragma warning disable RMJ0018 + + [UnionType] + {|RMJ0005:static|} partial class Union; + """); + + [Fact] + public Task VariantNamesMustBeUnique_SingleExplicitDuplicate() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + + [UnionType({|RMJ0006:Name = "Foo"|})] + [UnionType({|RMJ0006:Name = "Foo"|})] + partial struct Union; + """); + + [Fact] + public Task VariantNamesMustBeUnique_MultipleExplicitDuplicate() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + + [UnionType({|RMJ0006:Name = "Foo"|})] + [UnionType({|RMJ0006:Name = "Foo"|})] + [UnionType({|RMJ0006:Name = "Foo"|})] + [UnionType({|RMJ0006:Name = "Foo"|})] + partial struct Union; + """); + + [Fact] + public Task VariantNamesMustBeUnique_SingleImplicitDuplicate() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + using System.Collections.Generic; + + [UnionType<{|RMJ0006:List|}>] + [UnionType<{|RMJ0006:List|}>] + [UnionType] + partial struct Union; + """); + + [Fact] + public Task VariantNamesMustBeUnique_SingleImplicitInlineDuplicate() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + using System.Collections.Generic; + + [UnionType<{|RMJ0006:List|}, int, {|RMJ0006:List|}>] + partial struct Union; + """); + + [Fact] + public Task VariantNamesMustBeUnique_MultipleImplicitDuplicate() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + using System.Collections.Generic; + + [UnionType] + [UnionType<{|RMJ0006:List|}>] + [UnionType<{|RMJ0006:List|}>] + [UnionType<{|RMJ0006:List|}>] + partial struct Union; + """); + + [Fact] + public Task VariantNamesMustBeUnique_TypeParameter() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + using System.Collections.Generic; + + [UnionType<{|RMJ0006:System.String|}>] + partial struct Union<[UnionType]{|RMJ0006:String|}>; + """); + + [Fact] + public Task EnsureValidStructUnionState() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + + [UnionType] + partial struct {|RMJ0007:Union|}; + """); + + [Fact] + public Task InterfaceVariantIsExcludedFromConversionOperators() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + using System.Collections.Generic; + + #pragma warning disable RMJ0018 + + [UnionType<{|RMJ0008:IEnumerable|}>] + partial class Union; + """ + ); + + [Fact] + public Task VariantTypesMustBeUnique() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + + #pragma warning disable RMJ0018 + #pragma warning disable RMJ0006 + + [UnionType<{|RMJ0009:int|}>] + [UnionType<{|RMJ0009:int|}>] + partial class Union; + """ + ); + + [Fact] + public Task VariantTypesMustBeUniqueInline() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + + #pragma warning disable RMJ0018 + #pragma warning disable RMJ0006 + + [UnionType<{|RMJ0009:int|}, {|RMJ0009:int|}>] + partial class Union; + """ + ); + + [Fact] + public Task ObjectCannotBeUsedAsAVariant_Class() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + + #pragma warning disable RMJ0018 + + [UnionType<{|RMJ0010:object|}>] + partial class Union; + """ + ); + + [Fact] + public Task ObjectCannotBeUsedAsAVariant_Struct() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + + #pragma warning disable RMJ0007 + + [UnionType<{|RMJ0010:object|}>] + partial struct Union; + """ + ); + + [Fact] + public Task ValueTypeCannotBeUsedAsAVariantOfStructUnions() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + + #pragma warning disable RMJ0007 + + [UnionType<{|RMJ0019:System.ValueType|}>] + partial struct Union; + """ + ); + + [Fact] + public Task UnionCannotBeUsedAsVariantOfItself() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + + #pragma warning disable RMJ0018 + + [UnionType<{|RMJ0012:Union|}>] + partial class Union; + """ + ); + + [Theory] + [InlineData("System.Collections.Generic.HashSet")] + [InlineData("System.Collections.Generic.List")] + public Task UnionCannotExplicitlyDefineBaseType(String baseType) => JanusTest.TestAnalyzer( + $$""" + using RhoMicro.CodeAnalysis; + + #pragma warning disable RMJ0018 + + [UnionType] + partial class {|RMJ0013:Union|} : {{baseType}}; + """ + ); + + [Theory] + [InlineData( + "System.Collections.Generic.IEnumerable", + """ + public System.Collections.Generic.IEnumerator GetEnumerator() => throw null; + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => throw null; + """ + )] + [InlineData( + "System.IComparable", + """ + public int CompareTo(Union other) => throw null; + """ + )] + public Task UnionCanExplicitlyDefineInterfaces(String baseType, String implementation) => JanusTest.TestAnalyzer( + $$""" + using RhoMicro.CodeAnalysis; + + #pragma warning disable RMJ0018 + + [UnionType] + partial class Union : {{baseType}} + { + #pragma warning disable + {{implementation}} + #pragma warning restore + } + """ + ); + + [Fact] + public Task PreferNullableStructOverIsNullable() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + + #pragma warning disable RMJ0018 + + [UnionType({|RMJ0014:IsNullable = true|})] + [UnionType(IsNullable = true)] + partial class Union; + """ + ); + + [Fact] + public Task NullableVariantNotAllowedAlongWithNonNullableVariant() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + + #pragma warning disable RMJ0018 + + [UnionType<{|RMJ0015:int|}>] + [UnionType<{|RMJ0015:System.Nullable|}>(Name = "NullableInt")] + partial class Union; + """ + ); + + [Fact] + public Task UnionTypeSettingsAttributeIgnoredDueToMissingUnionTypeAttribute() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + + [{|RMJ0016:UnionTypeSettings|}] + partial class Union; + """ + ); + + [Fact] + public Task DuplicateVariantGroupNamesAreIgnored() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + + #pragma warning disable RMJ0018 + + [UnionType(Groups = [ "Group", {|RMJ0017:"Group"|} ])] + partial class Union; + """ + ); + + [Fact] + public Task ClassUnionsShouldBeSealed() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + + [UnionType] + partial class {|RMJ0018:Union|}; + """); + + [Fact] + public Task UnionCannotBeRefStruct() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + + [UnionType] + ref partial struct {|RMJ0020:Union|}; + """); + + [Theory] + [InlineData(""" + using RhoMicro.CodeAnalysis; + using System.Collections.Generic; + + [UnionType(Name = "Int")] + [UnionType>] + sealed partial class Union<[UnionType(Name = "ValueT")] T> where T : struct + { + public void Foo() + { + Switch( + onInt: _ => { }, + onList: _ => { }, + onValueT: _ => { }); + } + } + """ + )] + [InlineData( + """ + using RhoMicro.CodeAnalysis; + + [UnionType] + [UnionTypeSettings(ToStringSetting = ToStringSetting.Simple)] + sealed partial class Union; + """ + )] + [InlineData( + """ + using RhoMicro.CodeAnalysis; + + [UnionType] + [UnionTypeSettings(ToStringSetting = ToStringSetting.None)] + sealed partial class Union; + """ + )] + public Task ProducesNoDiagnostics(String source) => JanusTest.TestAnalyzer(source); +} diff --git a/Janus.Tests/JanusTest.cs b/Janus.Tests/JanusTest.cs new file mode 100644 index 0000000..a9cbde9 --- /dev/null +++ b/Janus.Tests/JanusTest.cs @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace Janus.Tests; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Testing; +using Microsoft.CodeAnalysis.Testing; +using RhoMicro.CodeAnalysis; +using RhoMicro.CodeAnalysis.Janus; + +class JanusTest : CSharpCodeFixTest +{ + public JanusTest() + { + TestBehaviors = TestBehaviors.SkipGeneratedSourcesCheck; + ReferenceAssemblies = ReferenceAssemblies.Net.Net90; + TestState.AdditionalReferences.Add(MetadataReference.CreateFromFile(typeof(IUnion).Assembly.Location)); + } + + public static Task TestCodeFix(String source, String fixedSource) + { + return CodeFixVerifier + .VerifyCodeFixAsync(source, fixedSource) + .WaitAsync(TestContext.Current.CancellationToken); + } + + public static Task TestAnalyzer(String source) + { + return CodeFixVerifier + .VerifyAnalyzerAsync(source) + .WaitAsync(TestContext.Current.CancellationToken); + } + + protected override IEnumerable GetSourceGenerators() => + [ + typeof(JanusGenerator), + typeof(OverloadResolutionPriorityAttributeGenerator) // TODO: remove when net10 is supported by MS.CA.Testing.ReferenceAssemblies + ]; +} diff --git a/Janus.Tests/OverloadResolutionPriorityAttributeGenerator.cs b/Janus.Tests/OverloadResolutionPriorityAttributeGenerator.cs new file mode 100644 index 0000000..8ca461c --- /dev/null +++ b/Janus.Tests/OverloadResolutionPriorityAttributeGenerator.cs @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace Janus.Tests; + +using Microsoft.CodeAnalysis; + +sealed class OverloadResolutionPriorityAttributeGenerator : IIncrementalGenerator +{ + public void Initialize(IncrementalGeneratorInitializationContext context) => + context.RegisterPostInitializationOutput(ctx => + ctx.AddSource("System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute", + """ + #pragma warning disable + #nullable enable annotations + + // Licensed to the .NET Foundation under one or more agreements. + // The .NET Foundation licenses this file to you under the MIT license. + + namespace System.Runtime.CompilerServices + { + /// + /// Specifies the priority of a member in overload resolution. When unspecified, the default priority is 0. + /// + [global::System.AttributeUsage( + global::System.AttributeTargets.Method | + global::System.AttributeTargets.Constructor | + global::System.AttributeTargets.Property, + AllowMultiple = false, + Inherited = false)] + [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + internal sealed class OverloadResolutionPriorityAttribute : global::System.Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// The priority of the attributed member. Higher numbers are prioritized, lower numbers are deprioritized. 0 is the default if no attribute is present. + public OverloadResolutionPriorityAttribute(int priority) + { + Priority = priority; + } + + /// + /// The priority of the member. + /// + public int Priority { get; } + } + } + + """ + )); +} diff --git a/Janus.Tests/xunit.runner.json b/Janus.Tests/xunit.runner.json new file mode 100644 index 0000000..86c7ea0 --- /dev/null +++ b/Janus.Tests/xunit.runner.json @@ -0,0 +1,3 @@ +{ + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json" +} diff --git a/Janus/Janus.csproj b/Janus/Janus.csproj new file mode 100644 index 0000000..4deece3 --- /dev/null +++ b/Janus/Janus.csproj @@ -0,0 +1,49 @@ + + + + + netstandard2.0 + false + true + true + true + enable + + + + + true + true + + Generate hybrid (tagged/type) union types. + + Source Generator; Union Types; Unions;Discriminated Unions;Tagged Unions;Variant Types + https://raw.githubusercontent.com/PaulBraetz/RhoMicro.CodeAnalysis/master/Janus/PackageLogo.svg + + + + 1.0.9 + ..\dist\dev + + + + + + + + + + + + + + + + + + + + + + + diff --git a/UnionsGenerator/Logo_Explorations.odg b/Janus/Logo_Explorations.odg similarity index 100% rename from UnionsGenerator/Logo_Explorations.odg rename to Janus/Logo_Explorations.odg diff --git a/UnionsGenerator/PackageLogo.svg b/Janus/PackageLogo.svg similarity index 100% rename from UnionsGenerator/PackageLogo.svg rename to Janus/PackageLogo.svg diff --git a/UnionsGenerator/README.md b/Janus/README.md similarity index 100% rename from UnionsGenerator/README.md rename to Janus/README.md diff --git a/UnionsGenerator/ReadmeLogo.svg b/Janus/ReadmeLogo.svg similarity index 100% rename from UnionsGenerator/ReadmeLogo.svg rename to Janus/ReadmeLogo.svg diff --git a/Janus/requirements.md b/Janus/requirements.md new file mode 100644 index 0000000..5b87b72 --- /dev/null +++ b/Janus/requirements.md @@ -0,0 +1,123 @@ +# New UnionsGenerator Design + +## Terminology + +The term `union` refers to a tagged union or sum type, or an instance thereof. +It is able to represent an instance of a type present in its set of representable types. + +The term `Union` refers to a nonspecific generated type implementing the defined union. + +The term `variant` refers to a type representable by a union. +Unless improperly initialized, a union is always representing an instance of one of its variants. + +The term `value` refers to the variant instance being represented by a union. + +The term `adapter` refers to the type adapting the union onto interfaces implemented by all its variants. + +## Requirements + +| id | description | issues | met (×/✓) | +|----|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|-----------| +| 1 | An error is issued if `Union` is not partial. | #151 | × | +| 2 | Interfaces implemented by all variants are implemented by an adapter property on the union. | #40 | × | +| 3 | Conflicting interface member implementations on the adapter are implemented explicitly. | #40 | × | +| 4 | An error is issued for `ref` like variants. | | × | +| 4 | An error is issued for `record` unions. | | × | +| 5 | A warning is issued for non-nullable reference variant on struct union. | | × | +| 6 | An error is issued if more than 31 variant groups are defined. This is to allow groups to be modelled using a [Flags] enum. | | × | +| 7 | Variant group names have diagnostics mapped onto their definition in the attribute usage. | | × | +| 8 | Variant names have diagnostics mapped onto their definition in the attribute usage. | | × | +| 9 | The backing type of the `VariantKind` is chosen as the smallest integral datatype able to accommodate all variants. | | × | +| 10 | An option is provided to box managed struct variants. | | × | +| 11 | Managed struct variants are stored in dedicated fields by default. | | × | +| 12 | An error is issued for static variants. | | × | +| 13 | An error is issued for conflicting variant names. | | × | +| 14 | An error is issued if no unmanaged, managed struct or nullable reference variant is defined for a struct union. This is to prevent struct unions to be left in an invalid state when uninitialized. | | × | +| 15 | Variants are ordered by unmanaged, managed struct, nullable reference, reference, fully qualified type name. This is to force the default variant to be valid when the struct union is uninitialized. | | × | +| 16 | If exactly one variant is defined, an implicit conversion to that variant is defined. | | × | +| 17 | If multiple variants are defined, an explicit conversion to each variant is defined. | | × | +| 18 | If a validation method is implemented, then an explicit conversion from the validated variant is defined. | | × | +| 19 | If a validation method is not implemented, then an implicit conversion from the variant is defined. | | × | +| 20 | Documentation comments are emitted for every generated member. | | × | +| 21 | Parameter lists are wrapped and indented. | | × | +| 22 | The backing type of the `VariantGroupKinds` is chosen as the smallest integral datatype able to accommodate all variants groups. | | × | +| 23 | `Union` methods are grouped by the variant they are specific to. | | × | +| 24 | Variant group names are ordered alphabetically, except for the default group `None`, which must always be the first element. | | × | +| 25 | An error is issued for json serializable unions that are generic (transitively). | | × | +| 26 | An error is issued for interface variants. | | × | +| 27 | An error is issued for duplicate variant types. | | × | +| 28 | An error is issued for static unions. | | × | +| 29 | An error is issued if the union implements the variant. | | × | +| 30 | An error is issued for `allows ref` variants. | | × | +| 31 | /* A warning is issued for transitively generic unions. */ | | × | +| 32 | An error is issued for variants that are the union itself. | | × | +| 33 | An error is issued for any non-interface base type (excluding `System.Object`, `System.ValueType`) implemented by the union. | | × | +| 34 | A warning is issued for `IsNullable = true` set on value type variants. | | × | +| 35 | A error is issued if a variants of `Nullable` and `T` are defined for the same union. | | × | +| 36 | A warning is issued for an explicitly set `ToStringSetting` if a `ToString` implementation is user provided. | | × | +| 37 | If a `ToString` implementation is user provided, no conflicting implementation is emitted. | | × | +| 38 | An error is issued for duplicate variant names. | | × | +| 39 | | | × | +| 40 | | | × | +| 41 | | | × | +| 42 | | | × | +| 43 | | | × | +| 44 | | | × | +| 45 | | | × | +| 46 | | | × | +| 47 | | | × | +| 48 | | | × | +| 49 | | | × | +| 50 | | | × | + +## Breaking Changes + +This is a non-exhaustive list of breaking changes to the previous version: + +| id | change | remediation / replacement | +|----|----------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------| +| 1 | `RelationAttribute` has been removed. | Use explicit conversion through `TTo.Create(TFrom)`, where `TTo` and `TFrom` are unions. | +| 2 | `UnionTypeFactoryAttribute` has been removed. | Custom validation logic should be implemented through the partial `static partial void Validate(TVariant, bool, ref bool)` method. | +| 3 | `LayoutSetting` and `UnionTypeSettingsAttribute.Layout` have been removed. | None - layouts are no longer customizable. | +| 4 | `ConstructorAccessibilitySetting` and `UnionTypeSettingsAttribute.ConstructorAccessibility` have been removed. | None - constructor accessibility is no longer customizable. | +| 5 | `InterfaceMatchSetting` and `UnionTypeSettingsAttribute.InterfaceMatchSetting` have been removed. | Use the `Switch` overloads to match interface implementations. | +| 6 | `DiagnosticsLevelSettings` and `UnionTypeSettingsAttribute.DiagnosticsLevel` have been removed. | Diagnostics should be configured through builtin mechanisms like `.editorconfig` or `#pragma warning disable`. | +| 7 | `MiscellaneousSettings` and `UnionTypeSettingsAttribute.Miscellaneous` have been removed. | Enable json converter generation through the `UnionTypeSettingsAttribute.IsJsonSerializable` property. | +| 8 | `UnionTypeSettingsAttribute.TypeDeclarationPreface` has been removed. | Apply any comment or attribute annotations to your partial declaration of the union. | +| 9 | `Match` methods have been removed. | Use the new `Switch` method overloads. | +| 10 | All naming options from `UnionTypeSettings` have been removed. | None, open an issue on the github repository outlining your custom naming needs. | +| 11 | `StorageOption` and `UnionTypeAttribute.Storage` have been removed. | None, storage is no longer customizable. | +| 12 | `UnionTypeAttribute.FactoryName` has been removed. | None, factory names are no longer customizable. | +| 13 | `UnionTypeOptions` has been removed. | | +| 14 | | | +| 15 | | | +| 16 | | | +| 17 | | | +| 18 | | | +| 19 | | | +| 20 | | | + +- Relations have been removed +- Factories have been removed + +## Notes + +### TODO + +- relations + +### Table Template + +| id | signature | description | conditions | issues | met (×/✓) | +|----|-----------|-------------|------------|--------|-----------| +| 1 | | | | | × | +| 2 | | | | | × | +| 3 | | | | | × | +| 4 | | | | | × | +| 5 | | | | | × | +| 6 | | | | | × | +| 7 | | | | | × | +| 8 | | | | | × | +| 9 | | | | | × | +| 10 | | | | | × | + From f1acb48c5bb3d88d30b996e8edd1d0a5e07d7cb2 Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Tue, 9 Dec 2025 02:53:37 +0100 Subject: [PATCH 13/36] implement lyra --- Lyra/AttributeArgumentComponent.cs | 100 +- ...SourceBuilder.InterpolatedStringHandler.cs | 8 +- Lyra/CSharpSourceBuilder.cs | 308 +++++-- Lyra/CSharpSourceBuilderOptions.cs | 5 + Lyra/ComponentFactory.Docs.cs | 870 ++++++++++++++++++ Lyra/ComponentFactory.cs | 308 +++++-- Lyra/DocsCommentElementComponent.cs | 195 ++++ Lyra/DocsCommentElementComponentOptions.cs | 163 ++++ Lyra/Generator.cs | 2 +- Lyra/ListComponent.cs | 40 +- Lyra/Lyra.csproj | 4 +- Lyra/NamespaceComponent.cs | 4 +- Lyra/README.md | 45 +- Lyra/RegionComponent.cs | 70 ++ Lyra/StrategyComponent.cs | 49 +- Lyra/TypeComponent.cs | 4 +- Lyra/TypeNameComponent.cs | 17 +- Lyra/TypeNameOptions.cs | 26 + 18 files changed, 2013 insertions(+), 205 deletions(-) create mode 100644 Lyra/ComponentFactory.Docs.cs create mode 100644 Lyra/DocsCommentElementComponent.cs create mode 100644 Lyra/DocsCommentElementComponentOptions.cs create mode 100644 Lyra/RegionComponent.cs create mode 100644 Lyra/TypeNameOptions.cs diff --git a/Lyra/AttributeArgumentComponent.cs b/Lyra/AttributeArgumentComponent.cs index 7b3e596..c5d29fc 100644 --- a/Lyra/AttributeArgumentComponent.cs +++ b/Lyra/AttributeArgumentComponent.cs @@ -19,26 +19,29 @@ namespace RhoMicro.CodeAnalysis.Lyra; { private AttributeArgumentComponent( Char assignmentOperator, - TypeNameComponent? type = null, - ICSharpSourceComponent? expressionComponent = null, - String? expression = null, - String? name = null) + TypeNameComponent? type, + ICSharpSourceComponent? expressionComponent, + String? expression, + String? name, + String? member) { _type = type; _expressionComponent = expressionComponent; _expression = expression; _name = name; _assignmentOperator = assignmentOperator; + _member = member; } private readonly TypeNameComponent? _type; private readonly ICSharpSourceComponent? _expressionComponent; + private readonly String? _member; private readonly String? _expression; private readonly String? _name; private readonly Char _assignmentOperator; /// - /// Creates a positional (constructor) argument of type . + /// Creates a positional (constructor) argument of type using a typeof() expression. /// /// /// The type to append into the typeof() expression assigned to the parameter. @@ -49,12 +52,41 @@ private AttributeArgumentComponent( /// /// A new positional argument component. /// - public static AttributeArgumentComponent CreatePositional(TypeNameComponent type, String? parameterName = null) + public static AttributeArgumentComponent CreatePositional( + TypeNameComponent type, + String? parameterName = null) => new(assignmentOperator: ':', type: type, expressionComponent: null, expression: null, - name: parameterName); + name: parameterName, + member: null); + + /// + /// Creates a positional (constructor) argument using a type.member expression. + /// + /// + /// The type whose member to use. + /// + /// + /// The member to use. + /// + /// + /// The name of the parameter. + /// + /// + /// A new positional argument component. + /// + public static AttributeArgumentComponent CreatePositionalMemberAccess( + TypeNameComponent type, + String member, + String? parameterName = null) + => new(assignmentOperator: ':', + type: type, + expressionComponent: null, + expression: null, + name: parameterName, + member: member); /// /// Creates a positional (constructor) argument. @@ -68,13 +100,15 @@ public static AttributeArgumentComponent CreatePositional(TypeNameComponent type /// /// A new positional argument component. /// - public static AttributeArgumentComponent CreatePositional(ICSharpSourceComponent expressionComponent, - String? parameterName = null) + public static AttributeArgumentComponent CreatePositional( + ICSharpSourceComponent expressionComponent, + String? parameterName = null) => new(assignmentOperator: ':', type: null, expressionComponent: expressionComponent, expression: null, - name: parameterName); + name: parameterName, + member: null); /// /// Creates a positional (constructor) argument. @@ -93,7 +127,8 @@ public static AttributeArgumentComponent CreatePositional(String expression, Str type: null, expressionComponent: null, expression: expression, - name: parameterName); + name: parameterName, + member: null); /// /// Creates a named (property) argument of type . @@ -112,7 +147,34 @@ public static AttributeArgumentComponent CreateNamed(TypeNameComponent type, Str type: type, expressionComponent: null, expression: null, - name: propertyName); + name: propertyName, + member: null); + + /// + /// Creates a named (property) argument using a type.member expression. + /// + /// + /// The type whose member to use. + /// + /// + /// The member to use. + /// + /// + /// The name of the property. + /// + /// + /// A new positional argument component. + /// + public static AttributeArgumentComponent CreateNamedMemberAccess( + TypeNameComponent type, + String member, + String propertyName) + => new(assignmentOperator: '=', + type: type, + expressionComponent: null, + expression: null, + name: propertyName, + member: member); /// /// Creates a named (property) argument. @@ -132,7 +194,8 @@ public static AttributeArgumentComponent CreateNamed(ICSharpSourceComponent expr type: null, expressionComponent: expressionComponent, expression: null, - name: propertyName); + name: propertyName, + member: null); /// /// Creates a named (property) argument. @@ -151,7 +214,8 @@ public static AttributeArgumentComponent CreateNamed(String expression, String p type: null, expressionComponent: null, expression: expression, - name: propertyName); + name: propertyName, + member: null); /// public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) @@ -175,9 +239,13 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation builder.Append($"{_assignmentOperator} "); } - if (_type is { } type) + if (_member is { } member && _type is { } type) + { + builder.Append($"{type}.{member}"); + } + else if (_type is { } typeOfArgument) { - builder.Append($"typeof({type})"); + builder.Append($"typeof({typeOfArgument})"); } else if (_expressionComponent is not null) { diff --git a/Lyra/CSharpSourceBuilder.InterpolatedStringHandler.cs b/Lyra/CSharpSourceBuilder.InterpolatedStringHandler.cs index cc5d929..a20b790 100644 --- a/Lyra/CSharpSourceBuilder.InterpolatedStringHandler.cs +++ b/Lyra/CSharpSourceBuilder.InterpolatedStringHandler.cs @@ -36,13 +36,13 @@ public partial struct InterpolatedStringHandler( /// public void AppendLiteral(String literal) { - if (!writer._appendCondition) + if (!writer.IsAppendConditionMet) { return; } TryDetent(); - writer.AppendCore(literal, out var lastLineStartText); + writer.AppendCore(literal.AsSpan(), out var lastLineStartText); if (!writer._detector.TryDetectIndentation( lastLineStartText, @@ -100,7 +100,7 @@ public void AppendFormatted(ICSharpSourceComponent placeholder) /// public void AppendFormatted(T placeholder) { - if (!writer._appendCondition) + if (!writer.IsAppendConditionMet) { return; } @@ -123,7 +123,7 @@ public void AppendFormatted(T placeholder) break; case ICSharpSourceComponent component: // invert call in order to allow jit to remove boxing conversion - component.AppendTo(writer, writer._cancellationToken); + component.AppendTo(writer, writer.CancellationToken); break; case null: break; diff --git a/Lyra/CSharpSourceBuilder.cs b/Lyra/CSharpSourceBuilder.cs index 6b5a106..fe90d9b 100644 --- a/Lyra/CSharpSourceBuilder.cs +++ b/Lyra/CSharpSourceBuilder.cs @@ -4,7 +4,9 @@ namespace RhoMicro.CodeAnalysis.Lyra; using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Threading; /// @@ -39,11 +41,12 @@ public CSharpSourceBuilder() : this(CSharpSourceBuilderOptions.Default) { } - private const Int32 AppendMethodHighOverloadPriority = 3; - private const Int32 AppendMethodMediumOverloadPriority = 2; - private const Int32 AppendMethodLowOverloadPriority = 1; - private const Int32 AppendMethodNoOverloadPriority = 0; + private const Int32 _appendMethodHighOverloadPriority = 3; + private const Int32 _appendMethodMediumOverloadPriority = 2; + private const Int32 _appendMethodLowOverloadPriority = 1; + private const Int32 _appendMethodNoOverloadPriority = 0; + private readonly Stack _appendConditions = []; private readonly List> _indentations; private readonly RentedArrayLifetime _rentedArrayLifetime; private readonly InterpolationIndentationDetectorContext _indentationDetectorContext; @@ -77,13 +80,14 @@ public CSharpSourceBuilder() : this(CSharpSourceBuilderOptions.Default) /// public Int32 Lines { get; private set; } + private Boolean IsAppendConditionMet => _appendConditions.Count == 0 || _appendConditions.Peek(); + private IInterpolationIndentationDetector _detector; private Boolean _lastWasNewLine = true; private Boolean _lastWasEmptyLine = false; private Boolean _preludeWritten = false; private Char[] _buffer; - private Boolean _appendCondition = true; - private CancellationToken _cancellationToken; + public CancellationToken CancellationToken { get; private set; } private static Int32 RoundUpToPowerOf2(Int32 value) { @@ -113,8 +117,8 @@ private Memory GetMemory(Int32 lengthHint) if (_buffer.Length < requiredLength) { var newLength = RoundUpToPowerOf2(requiredLength); - var newBuffer = Options.CharBufferOwner.Rent((Int32)newLength); - _buffer.CopyTo(newBuffer); + var newBuffer = Options.CharBufferOwner.Rent(newLength); + _buffer.CopyTo(newBuffer.AsSpan()); Options.CharBufferOwner.Return(_buffer); _buffer = newBuffer; } @@ -126,7 +130,7 @@ private Memory GetMemory(Int32 lengthHint) private void AppendCore(ReadOnlySpan text, out ReadOnlyMemory lastLineStartText) { - _cancellationToken.ThrowIfCancellationRequested(); + CancellationToken.ThrowIfCancellationRequested(); lastLineStartText = default; @@ -140,7 +144,7 @@ private void AppendCore(ReadOnlySpan text, out ReadOnlyMemory lastLi var tail = text; while (true) { - _cancellationToken.ThrowIfCancellationRequested(); + CancellationToken.ThrowIfCancellationRequested(); var nextNewline = tail.IndexOfAny('\n', '\r'); if (nextNewline is -1) @@ -195,12 +199,14 @@ private void TryWritePrelude() } _preludeWritten = true; - Options.Prelude.Invoke(this, _cancellationToken); + DetentAll(out var indentations); + Options.Prelude.Invoke(this, CancellationToken); + Indent(indentations); } private void TryWriteIndentation() { - _cancellationToken.ThrowIfCancellationRequested(); + CancellationToken.ThrowIfCancellationRequested(); if (!_lastWasNewLine) { @@ -242,7 +248,7 @@ public CSharpSourceBuilder Clear() /// public CSharpSourceBuilder SetCondition(Boolean condition) { - _appendCondition = condition; + _appendConditions.Push(condition); return this; } @@ -256,12 +262,87 @@ public CSharpSourceBuilder SetCondition(Boolean condition) /// public CSharpSourceBuilder UnsetCondition() { - _appendCondition = true; + _appendConditions.Pop(); return this; } #region Indentation + /// + /// Detents the builder fully. + /// + /// + /// The indentations removed from the builder. + /// + /// + /// A reference to the builder, for chaining of further method calls. + /// + public CSharpSourceBuilder DetentAll(out ImmutableArray> indentations) + { + if (!IsAppendConditionMet) + { + indentations = []; + return this; + } + + var result = new ReadOnlyMemory[_indentations.Count]; + for (var i = 0; i < _indentations.Count; i++) + { + result[i] = _indentations[i]; + } + + _indentations.Clear(); + + indentations = ImmutableCollectionsMarshal.AsImmutableArray(result); + return this; + } + + /// + /// Detents the builder fully. + /// + /// + /// A reference to the builder, for chaining of further method calls. + /// + public CSharpSourceBuilder DetentAll() + { + if (!IsAppendConditionMet) + { + return this; + } + + _indentations.Clear(); + + return this; + } + + /// + /// Indents the builder. + /// + /// + /// The indentations to add to the builder. + /// + /// + /// The type of enumerable containing the indentations. + /// + /// + /// A reference to the builder, for chaining of further method calls. + /// + public CSharpSourceBuilder Indent(TEnumerable indentations) + where TEnumerable : IEnumerable> + { + if (!IsAppendConditionMet) + { + return this; + } + + foreach (var indentation in indentations) + { + _indentations.Add(indentation); + } + + return this; + } + /// /// Indents the builder. /// @@ -273,7 +354,7 @@ public CSharpSourceBuilder UnsetCondition() /// public CSharpSourceBuilder Indent(ReadOnlyMemory indentation) { - if (!_appendCondition) + if (!IsAppendConditionMet) { return this; } @@ -309,7 +390,7 @@ public CSharpSourceBuilder Indent(ReadOnlyMemory indentation) /// public CSharpSourceBuilder Detent() { - if (!_appendCondition) + if (!IsAppendConditionMet) { return this; } @@ -335,17 +416,17 @@ public CSharpSourceBuilder Detent() /// /// A reference to the builder, for chaining of further method calls. /// - [OverloadResolutionPriority(AppendMethodLowOverloadPriority)] + [OverloadResolutionPriority(_appendMethodLowOverloadPriority)] public CSharpSourceBuilder Append(String text) => - Append(text.AsSpan()); + Append(text.AsSpan()); /// - [OverloadResolutionPriority(AppendMethodLowOverloadPriority)] + [OverloadResolutionPriority(_appendMethodLowOverloadPriority)] public CSharpSourceBuilder Append(ReadOnlySpan text) { - _cancellationToken.ThrowIfCancellationRequested(); + CancellationToken.ThrowIfCancellationRequested(); - if (!_appendCondition) + if (!IsAppendConditionMet) { return this; } @@ -355,19 +436,19 @@ public CSharpSourceBuilder Append(ReadOnlySpan text) } /// - [OverloadResolutionPriority(AppendMethodLowOverloadPriority)] + [OverloadResolutionPriority(_appendMethodLowOverloadPriority)] public CSharpSourceBuilder Append(Char text) => - Append([text]); + Append([text]); /// - [OverloadResolutionPriority(AppendMethodMediumOverloadPriority)] + [OverloadResolutionPriority(_appendMethodMediumOverloadPriority)] public CSharpSourceBuilder Append( - [InterpolatedStringHandlerArgument("")] - InterpolatedStringHandler text) + [InterpolatedStringHandlerArgument("")] + InterpolatedStringHandler text) { - _cancellationToken.ThrowIfCancellationRequested(); + CancellationToken.ThrowIfCancellationRequested(); - if (!_appendCondition) + if (!IsAppendConditionMet) { return this; } @@ -381,13 +462,13 @@ public CSharpSourceBuilder Append( /// /// The type of component to append, /// - [OverloadResolutionPriority(AppendMethodLowOverloadPriority)] + [OverloadResolutionPriority(_appendMethodLowOverloadPriority)] public CSharpSourceBuilder Append(T component) - where T : ICSharpSourceComponent + where T : ICSharpSourceComponent { - _cancellationToken.ThrowIfCancellationRequested(); + CancellationToken.ThrowIfCancellationRequested(); - component.AppendTo(this, _cancellationToken); + component.AppendTo(this, CancellationToken); return this; } @@ -401,12 +482,12 @@ public CSharpSourceBuilder Append(T component) /// /// A reference to the builder, for chaining of further method calls. /// - [OverloadResolutionPriority(AppendMethodNoOverloadPriority)] + [OverloadResolutionPriority(_appendMethodNoOverloadPriority)] public CSharpSourceBuilder Append(ICSharpSourceComponent component) { - _cancellationToken.ThrowIfCancellationRequested(); + CancellationToken.ThrowIfCancellationRequested(); - component.AppendTo(this, _cancellationToken); + component.AppendTo(this, CancellationToken); return this; } @@ -421,12 +502,12 @@ public CSharpSourceBuilder Append(ICSharpSourceComponent component) /// /// A reference to the builder, for chaining of further method calls. /// - [OverloadResolutionPriority(AppendMethodHighOverloadPriority)] + [OverloadResolutionPriority(_appendMethodHighOverloadPriority)] public CSharpSourceBuilder AppendLine() { - _cancellationToken.ThrowIfCancellationRequested(); + CancellationToken.ThrowIfCancellationRequested(); - if (!_appendCondition) + if (!IsAppendConditionMet) { return this; } @@ -462,10 +543,10 @@ public CSharpSourceBuilder AppendLine() /// /// A reference to the builder, for chaining of further method calls. /// - [OverloadResolutionPriority(AppendMethodLowOverloadPriority)] + [OverloadResolutionPriority(_appendMethodLowOverloadPriority)] public CSharpSourceBuilder AppendLine(String text) { - _cancellationToken.ThrowIfCancellationRequested(); + CancellationToken.ThrowIfCancellationRequested(); Append(text); AppendLine(); @@ -474,10 +555,10 @@ public CSharpSourceBuilder AppendLine(String text) } /// - [OverloadResolutionPriority(AppendMethodLowOverloadPriority)] + [OverloadResolutionPriority(_appendMethodLowOverloadPriority)] public CSharpSourceBuilder AppendLine(ReadOnlySpan text) { - _cancellationToken.ThrowIfCancellationRequested(); + CancellationToken.ThrowIfCancellationRequested(); Append(text); AppendLine(); @@ -486,10 +567,10 @@ public CSharpSourceBuilder AppendLine(ReadOnlySpan text) } /// - [OverloadResolutionPriority(AppendMethodLowOverloadPriority)] + [OverloadResolutionPriority(_appendMethodLowOverloadPriority)] public CSharpSourceBuilder AppendLine(Char text) { - _cancellationToken.ThrowIfCancellationRequested(); + CancellationToken.ThrowIfCancellationRequested(); Append(text); AppendLine(); @@ -498,14 +579,14 @@ public CSharpSourceBuilder AppendLine(Char text) } /// - [OverloadResolutionPriority(AppendMethodMediumOverloadPriority)] + [OverloadResolutionPriority(_appendMethodMediumOverloadPriority)] public CSharpSourceBuilder AppendLine( - [InterpolatedStringHandlerArgument("")] - InterpolatedStringHandler text) + [InterpolatedStringHandlerArgument("")] + InterpolatedStringHandler text) { - _cancellationToken.ThrowIfCancellationRequested(); + CancellationToken.ThrowIfCancellationRequested(); - if (!_appendCondition) + if (!IsAppendConditionMet) { return this; } @@ -521,13 +602,13 @@ public CSharpSourceBuilder AppendLine( /// /// The type of component to append, /// - [OverloadResolutionPriority(AppendMethodLowOverloadPriority)] + [OverloadResolutionPriority(_appendMethodLowOverloadPriority)] public CSharpSourceBuilder AppendLine(T component) - where T : ICSharpSourceComponent + where T : ICSharpSourceComponent { - _cancellationToken.ThrowIfCancellationRequested(); + CancellationToken.ThrowIfCancellationRequested(); - component.AppendTo(this, _cancellationToken); + component.AppendTo(this, CancellationToken); AppendLine(); return this; @@ -542,12 +623,12 @@ public CSharpSourceBuilder AppendLine(T component) /// /// A reference to the builder, for chaining of further method calls. /// - [OverloadResolutionPriority(AppendMethodNoOverloadPriority)] + [OverloadResolutionPriority(_appendMethodNoOverloadPriority)] public CSharpSourceBuilder AppendLine(ICSharpSourceComponent component) { - _cancellationToken.ThrowIfCancellationRequested(); + CancellationToken.ThrowIfCancellationRequested(); - component.AppendTo(this, _cancellationToken); + component.AppendTo(this, CancellationToken); AppendLine(); return this; @@ -557,41 +638,85 @@ public CSharpSourceBuilder AppendLine(ICSharpSourceComponent component) #region Append Type Name + /// + public CSharpSourceBuilder AppendTypeName() => AppendTypeName(Options.DefaultTypeNameOptions); + /// /// /// The type whose name to append. /// - public CSharpSourceBuilder AppendTypeName() + public CSharpSourceBuilder AppendTypeName(TypeNameOptions options) { - AppendTypeName(typeof(T)); + AppendTypeName(typeof(T), options); return this; } + /// + public CSharpSourceBuilder AppendTypeName(Type type) => AppendTypeName(type, Options.DefaultTypeNameOptions); + /// /// Appends the globally qualified C# name of a type to the builder. /// + /// + /// The options to use when appending the type name. + /// /// /// The type whose name to append. /// /// /// A reference to the builder, for chaining of further method calls. /// - public CSharpSourceBuilder AppendTypeName(Type type) + public CSharpSourceBuilder AppendTypeName(Type type, TypeNameOptions options) { - _cancellationToken.ThrowIfCancellationRequested(); + CancellationToken.ThrowIfCancellationRequested(); - if (!_appendCondition) + if (!IsAppendConditionMet) { return this; } - Append("global::"); + if (options.UseTypeAliases && _aliasedTypes.TryGetValue(type, out var aliased)) + { + Append(aliased); + } + else + { + AppendMetadataTypeName(type, options); + } - if (type.Namespace is { } ns) + return this; + } + + private static readonly Dictionary _aliasedTypes = new() + { + { typeof(Boolean), "bool" }, + { typeof(Byte), "byte" }, + { typeof(SByte), "sbyte" }, + { typeof(Int16), "short" }, + { typeof(UInt16), "ushort" }, + { typeof(Int32), "int" }, + { typeof(UInt32), "int" }, + { typeof(Int64), "long" }, + { typeof(UInt64), "long" }, + { typeof(Single), "float" }, + { typeof(Double), "double" }, + { typeof(Decimal), "decimal" }, + { typeof(Char), "char" }, + { typeof(String), "string" } + }; + + private void AppendMetadataTypeName(Type type, TypeNameOptions options) + { + if (options.UseGloballyQualifiedName) { - Append(ns); - Append('.'); + Append("global::"); + + if (type.Namespace is { } ns) + { + Append(ns); + Append('.'); + } } var firstIllegalIndex = type.Name.AsSpan().IndexOfAny('`', '['); @@ -605,25 +730,25 @@ public CSharpSourceBuilder AppendTypeName(Type type) Append("[]"); } - if (type.IsGenericType) + if (!type.IsGenericType) { - Append('<'); + return; + } - foreach (var parameter in type.GetGenericArguments()) - { - _cancellationToken.ThrowIfCancellationRequested(); + Append('<'); - AppendTypeName(parameter); - } + foreach (var parameter in type.GetGenericArguments()) + { + CancellationToken.ThrowIfCancellationRequested(); - Append('>'); + AppendTypeName(parameter, options); } - return this; + Append('>'); } #endregion - + #region Cancellation /// @@ -634,7 +759,7 @@ public CSharpSourceBuilder AppendTypeName(Type type) /// public CSharpSourceBuilder SetCancellationToken(CancellationToken cancellationToken) { - _cancellationToken = cancellationToken; + CancellationToken = cancellationToken; return this; } @@ -647,7 +772,7 @@ public CSharpSourceBuilder SetCancellationToken(CancellationToken cancellationTo /// public CSharpSourceBuilder UnsetCancellationToken() { - _cancellationToken = default; + CancellationToken = default; return this; } @@ -665,9 +790,9 @@ public CSharpSourceBuilder UnsetCancellationToken() /// A reference to the builder, for chaining of further method calls. /// public CSharpSourceBuilder SetInterpolationIndentationDetector( - IInterpolationIndentationDetector detector) + IInterpolationIndentationDetector detector) { - if (!_appendCondition) + if (!IsAppendConditionMet) { return this; } @@ -690,12 +815,12 @@ public CSharpSourceBuilder SetInterpolationIndentationDetector( /// A reference to the builder, for chaining of further method calls. /// public CSharpSourceBuilder SetInterpolationIndentationDetector( - IInterpolationIndentationDetector detector, - out IInterpolationIndentationDetector previousDetector) + IInterpolationIndentationDetector detector, + out IInterpolationIndentationDetector previousDetector) { previousDetector = _detector; - if (!_appendCondition) + if (!IsAppendConditionMet) { return this; } @@ -705,6 +830,25 @@ public CSharpSourceBuilder SetInterpolationIndentationDetector( return this; } +#endregion + +#region Miscellaneous + + /// + /// Skips checks to append the prelude until is called. + /// This will cause the prelude to be omitted if no other calls appending + /// text to the builder have been made. + /// + /// + /// A reference to the builder, for chaining of further method calls. + /// + public CSharpSourceBuilder SkipPreludeChecks() + { + _preludeWritten = true; + + return this; + } + #endregion /// diff --git a/Lyra/CSharpSourceBuilderOptions.cs b/Lyra/CSharpSourceBuilderOptions.cs index 1671355..f72c8b3 100644 --- a/Lyra/CSharpSourceBuilderOptions.cs +++ b/Lyra/CSharpSourceBuilderOptions.cs @@ -19,6 +19,11 @@ internal partial class CSharpSourceBuilderOptions /// public static CSharpSourceBuilderOptions Default { get; } = new(); + /// + /// Gets the options to use when writing type names and no other options are explicitly provided. + /// + public virtual TypeNameOptions DefaultTypeNameOptions { get; init; } = TypeNameOptions.Default; + /// /// Gets the initial indentation to apply to the builder. /// diff --git a/Lyra/ComponentFactory.Docs.cs b/Lyra/ComponentFactory.Docs.cs new file mode 100644 index 0000000..d521307 --- /dev/null +++ b/Lyra/ComponentFactory.Docs.cs @@ -0,0 +1,870 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +using System.Collections.Immutable; +using Options = DocsCommentElementComponentOptions; + +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +partial class ComponentFactory +{ + /// + /// Creates docs comment component instances. + /// + public static partial class Docs + { + /// + /// Creates a new docs comment element. + /// + /// + /// The options to be used by the component. + /// + /// + /// A new docs comment element component. + /// + public static DocsCommentElementComponent Element(Options options) + => new(Attributes: null, Body: null, options); + /// + /// Creates a new docs comment element. + /// + /// + /// The state used by the component. + /// + /// + /// The options to be used by the component. + /// + /// + /// The callback to invoke when appending attributes. + /// + /// + /// The callback to invoke when appending the body. + /// + /// + /// The type of state used by the component. + /// + /// + /// A new docs comment element component. + /// + public static DocsCommentElementComponent Element( + TState state, + Options options, + Action? body, + Action? attributes) + => new(state, Attributes: attributes, Body: body, options); + + /// + /// Creates a new docs comment element. + /// + /// + /// The options to be used by the component. + /// + /// + /// The callback to invoke when appending attributes. + /// + /// + /// The callback to invoke when appending the body. + /// + /// + /// A new docs comment element component. + /// + public static DocsCommentElementComponent Element( + Options options, + Action? body, + Action? attributes) + => new(Attributes: attributes, Body: body, options); + + /// + /// Creates a new docs comment element. + /// + /// + /// The options to be used by the component. + /// + /// + /// The text to render in the element. + /// + /// + /// A new docs comment element component. + /// + public static DocsCommentElementComponent Element( + Options options, + String text) + => Element( + text, + options, + body: static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append(t); + }, + attributes: null); + + /// + /// Creates a new summary element component. + /// + /// + /// The text to render in the summary element. + /// + /// + /// A new summary element component. + /// + public static DocsCommentElementComponent Summary(String text) => + Element(Options.Summary, text); + + /// + /// Creates a new summary element component. + /// + /// + /// + /// + /// + /// A new summary element component. + /// + public static DocsCommentElementComponent Summary(Action body) => + Element(Options.Summary, body: body, attributes: null); + + /// + /// Creates a new summary element component. + /// + /// + /// + /// + /// + /// + /// + /// + /// A new summary element component. + /// + public static DocsCommentElementComponent Summary( + TState state, + Action body) => + Element(state, Options.Summary, body: body, attributes: null); + + /// + /// Creates a new returns element component. + /// + /// + /// The text to render in the returns element. + /// + /// + /// A new returns element component. + /// + public static DocsCommentElementComponent Returns(String text) => + Element(Options.Returns, text); + + /// + /// Creates a new returns element component. + /// + /// + /// + /// + /// + /// A new returns element component. + /// + public static DocsCommentElementComponent Returns(Action body) => + Element(Options.Returns, body: body, attributes: null); + + /// + /// Creates a new returns element component. + /// + /// + /// + /// + /// + /// + /// + /// + /// A new returns element component. + /// + public static DocsCommentElementComponent Returns( + TState state, + Action body) => + Element(state, Options.Returns, body: body, attributes: null); + + /// + /// Creates a new remarks element component. + /// + /// + /// The text to render in the remarks element. + /// + /// + /// A new remarks element component. + /// + public static DocsCommentElementComponent Remarks(String text) => + Element(Options.Remarks, text); + + /// + /// Creates a new remarks element component. + /// + /// + /// + /// + /// + /// A new remarks element component. + /// + public static DocsCommentElementComponent Remarks(Action body) => + Element(Options.Remarks, body: body, attributes: null); + + /// + /// Creates a new remarks element component. + /// + /// + /// + /// + /// + /// + /// + /// + /// A new remarks element component. + /// + public static DocsCommentElementComponent Remarks( + TState state, + Action body) => + Element(state, Options.Remarks, body: body, attributes: null); + + /// + /// Creates a new param element component. + /// + /// + /// The text to render in the param element. + /// + /// + /// The name of the parameter documented by the param element. + /// + /// + /// A new param element component. + /// + public static DocsCommentElementComponent<(String, String)> Param(String name, String text) => + Element( + state: (name, text), + Options.Param, + attributes: static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (name, _) = t; + + b.Append($"name=\"{name}\""); + }, + body: static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (_, text) = t; + b.Append(text); + }); + + /// + /// Creates a new param element component. + /// + /// + /// The name of the parameter documented by the param element. + /// + /// + /// + /// + /// + /// + /// + /// + /// A new param element component. + /// + public static + DocsCommentElementComponent<(String, TState, Action)> + Param( + String name, + TState state, + Action body) => + Element( + state: (name, state, body), + Options.Param, + attributes: static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (name, _, _) = t; + + b.Append($"name=\"{name}\""); + }, + body: static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (_, state, body) = t; + + body.Invoke(state, b, ct); + }); + + /// + /// Creates a new param element component. + /// + /// + /// The name of the parameter documented by the param element. + /// + /// + /// + /// + /// + /// A new param element component. + /// + public static + DocsCommentElementComponent<(String, Action)> + Param(String name, Action body) => + Element( + state: (name, body), + Options.Param, + attributes: static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (name, _) = t; + + b.Append($"name=\"{name}\""); + }, + body: static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (_, body) = t; + + body.Invoke(b, ct); + }); + + /// + /// Creates a new typeparam element component. + /// + /// + /// The text to render in the typeparam element. + /// + /// + /// The name of the type parameter documented by the typeparam element. + /// + /// + /// A new typeparam element component. + /// + public static DocsCommentElementComponent<(String, String)> TypeParam(String name, String text) => + Element( + state: (name, text), + Options.TypeParam, + attributes: static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (name, _) = t; + + b.Append($"name=\"{name}\""); + }, + body: static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (_, text) = t; + b.Append(text); + }); + + /// + /// Creates a new typeparam element component. + /// + /// + /// The name of the parameter documented by the typeparam element. + /// + /// + /// + /// + /// + /// + /// + /// + /// A new typeparam element component. + /// + public static + DocsCommentElementComponent<(String, TState, Action)> + TypeParam( + String name, + TState state, + Action body) => + Element( + state: (name, state, body), + Options.TypeParam, + attributes: static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (name, _, _) = t; + + b.Append($"name=\"{name}\""); + }, + body: static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (_, state, body) = t; + + body.Invoke(state, b, ct); + }); + + /// + /// Creates a new see langword element component. + /// + /// + /// The name of the language word referenced by the component. + /// + /// + /// A new see element component referencing the language word. + /// + public static DocsCommentElementComponent Langword(String word) => + Element( + state: word, + Options.See, + body: null, + attributes: static (w, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"langword=\"{w}\""); + }); + + /// + /// Creates a new see cref element component. + /// + /// + /// The member referenced by the see element. + /// + /// + /// A new see element component referencing the member. + /// + public static DocsCommentElementComponent Cref(String memberReference) => + Element( + state: memberReference, + Options.See, + body: null, + attributes: static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"cref=\"{m}\""); + }); + + /// + /// Creates a new see cref element component. + /// + /// + /// The state used by . + /// + /// + /// The callback invoked when appending the member referenced by the see element. + /// + /// + /// The type of state used by . + /// + /// + /// A new see element component referencing the member. + /// + public static DocsCommentElementComponent<(TState, Action)> + Cref( + TState state, + Action memberReference) => + Element( + state: (state, memberReference), + Options.See, + body: null, + attributes: static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (state, memberReference) = t; + + b.Append("cref=\""); + memberReference.Invoke(state, b, ct); + b.Append("\""); + }); + + /// + /// Creates a new paramref element component. + /// + /// + /// The name of the parameter referenced by the paramref element. + /// + /// + /// A new paramref element component. + /// + public static DocsCommentElementComponent ParamRef(String name) => + Element( + state: name, + Options.ParamRef, + body: null, + attributes: static (n, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"name=\"{n}\""); + }); + + /// + /// Creates a new typeparamref element component. + /// + /// + /// The name of the type parameter referenced by the typeparamref element. + /// + /// + /// A new typeparamref element component. + /// + public static DocsCommentElementComponent TypeParamRef(String name) => + Element( + state: name, + Options.TypeParamRef, + body: null, + attributes: static (n, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"name=\"{n}\""); + }); + + /// + /// Creates a new inheritdoc element component. + /// + /// + /// The member referenced by the inheritdoc element. + /// + /// + /// A new inheritdoc element component. + /// + public static DocsCommentElementComponent Inheritdoc(String? reference = null) => + Element( + state: reference, + Options.Inheritdoc, + body: null, + attributes: static (r, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + if (r is null) + { + return; + } + + b.Append($"cref=\"{r}\""); + }); + + /// + /// Creates a new c element component. + /// + /// + /// The text to render in the c element. + /// + /// + /// A new c element component. + /// + public static DocsCommentElementComponent C(String text) => + Element(Options.C, text); + + /// + /// Creates a new c element component. + /// + /// + /// + /// + /// + /// A new c element component. + /// + public static DocsCommentElementComponent C(Action body) => + Element(Options.C, body: body, attributes: null); + + /// + /// Creates a new c element component. + /// + /// + /// + /// + /// + /// + /// + /// + /// A new c element component. + /// + public static DocsCommentElementComponent C( + TState state, + Action body) => + Element(state, Options.C, body: body, attributes: null); + + + /// + /// Creates a new term element component. + /// + /// + /// The text to render in the term element. + /// + /// + /// A new term element component. + /// + public static DocsCommentElementComponent Term(String text) => + Element(Options.Term, text); + + /// + /// Creates a new term element component. + /// + /// + /// + /// + /// + /// A new term element component. + /// + public static DocsCommentElementComponent Term(Action body) => + Element(Options.Term, body: body, attributes: null); + + /// + /// Creates a new term element component. + /// + /// + /// + /// + /// + /// + /// + /// + /// A new term element component. + /// + public static DocsCommentElementComponent Term( + TState state, + Action body) => + Element(state, Options.Term, body: body, attributes: null); + + + /// + /// Creates a new description element component. + /// + /// + /// The text to render in the description element. + /// + /// + /// A new description element component. + /// + public static DocsCommentElementComponent Description(String text) => + Element(Options.Description, text); + + /// + /// Creates a new description element component. + /// + /// + /// + /// + /// + /// A new description element component. + /// + public static DocsCommentElementComponent Description(Action body) => + Element(Options.Description, body: body, attributes: null); + + /// + /// Creates a new description element component. + /// + /// + /// + /// + /// + /// + /// + /// + /// A new description element component. + /// + public static DocsCommentElementComponent Description( + TState state, + Action body) => + Element(state, Options.Description, body: body, attributes: null); + + /// + /// Creates a new list element component. + /// + /// + /// The type of the list component. + /// + /// + /// + /// + /// + /// + /// + /// + /// A new list element component. + /// + public static DocsCommentElementComponent< + (String type, + TState state, + Action body)> + List( + String type, + TState state, + Action body) => + Element( + (type, state, body), + Options.List, + body: static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (_, state, body) = t; + + body.Invoke(state, b, ct); + }, + attributes: static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (type, _, _) = t; + + b.Append($"type=\"{type}\""); + }); + + /// + /// Creates a new item element component. + /// + /// + /// + /// + /// + /// The callback invoked when appending the term child of the item element. + /// + /// + /// The callback invoked when appending the description child of the item element. + /// + /// + /// A new item element component. + /// + public static DocsCommentElementComponent< + (TState state, + Action? term, + Action? description)> + Item( + TState state, + Action? term, + Action? description) => + Element( + (state, term, description), + Options.Item, + body: static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (state, term, description) = t; + + if (term is not null) + { + b.Append(Term(state, term)); + } + + if (description is null) + { + return; + } + + if (term is not null) + { + b.AppendLine(); + } + + b.Append(Description(state, description)); + }, attributes: null); + + /// + /// Creates a new listheader element component. + /// + /// + /// + /// + /// + /// The callback invoked when appending the term child of the listheader element. + /// + /// + /// The callback invoked when appending the description child of the listheader element. + /// + /// + /// A new listheader element component. + /// + public static DocsCommentElementComponent< + (TState state, + Action? term, + Action? description)> + ListHeader( + TState state, + Action? term, + Action? description) => + Element( + (state, term, description), + Options.ListHeader, + body: static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (state, term, description) = t; + + if (term is not null) + { + b.Append(Term(state, term)); + } + + if (description is null) + { + return; + } + + if (term is not null) + { + b.AppendLine(); + } + + b.Append(Description(state, description)); + }, attributes: null); + + /// + /// Creates a new listheader element component. + /// + /// + /// The text to append as the body of the term child of the listheader element. + /// + /// + /// The text to append as the body of the description child of the listheader element. + /// + /// + /// A new listheader element component. + /// + public static DocsCommentElementComponent<(String? term, String? description)> ListHeader( + String? term, + String? description) => + Element( + (term, description), + Options.ListHeader, + body: static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (term, description) = t; + + if (term is not null) + { + b.Append(Term(term)); + } + + if (description is null) + { + return; + } + + if (term is not null) + { + b.AppendLine(); + } + + b.Append(Description(description)); + }, attributes: null); + + /// + /// Creates a new br element. + /// + /// + /// A new br element. + /// + public static DocsCommentElementComponent Br() => Element(Options.Br); + } +} diff --git a/Lyra/ComponentFactory.cs b/Lyra/ComponentFactory.cs index 92786ab..d2242fc 100644 --- a/Lyra/ComponentFactory.cs +++ b/Lyra/ComponentFactory.cs @@ -4,6 +4,7 @@ namespace RhoMicro.CodeAnalysis.Lyra; using System; using System.Collections.Immutable; +using System.Net.Sockets; using System.Threading; /// @@ -17,8 +18,8 @@ internal static partial class ComponentFactory /// /// Creates a new list component. /// - /// - /// The elements to enumerate. + /// + /// The list to render. /// /// /// The separator separating each element. @@ -29,29 +30,30 @@ internal static partial class ComponentFactory /// /// A new list component. /// - public static ListComponent List( - ImmutableArray elements, - String separator = "", - String terminator = "") - => new(Elements: elements, - Append: static (e, _, _, b, ct) => - { - ct.ThrowIfCancellationRequested(); - b.Append(e); - }, - Separator: separator, - Separate: static (s, _, _, b, ct) => - { - ct.ThrowIfCancellationRequested(); - b.Append(s); - }, - Terminator: terminator); + public static ListComponent List( + TList list, + String separator = "", + String terminator = "") + where TList : IList + => new(list, + Append: static (e, _, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + b.Append(e); + }, + Separator: separator, + Separate: static (s, _, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + b.Append(s); + }, + Terminator: terminator); /// /// Creates a new list component. /// - /// - /// The elements to enumerate. + /// + /// The list to render. /// /// /// The callback to invoke to append an element. @@ -65,20 +67,21 @@ public static ListComponent List( /// /// A new list component. /// - public static ListComponent List( - ImmutableArray elements, - Action append, - String separator = "", - String terminator = "") - => new(Elements: elements, - Append: append, - Separator: separator, - Separate: static (s, _, _, b, ct) => - { - ct.ThrowIfCancellationRequested(); - b.Append(s); - }, - Terminator: terminator); + public static ListComponent List( + TList list, + Action append, + String separator = "", + String terminator = "") + where TList : IList + => new(List: list, + Append: append, + Separator: separator, + Separate: static (s, _, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); + b.Append(s); + }, + Terminator: terminator); /// /// Creates a new namespace component. @@ -96,9 +99,23 @@ public static ListComponent List( /// A new namespace component. /// public static NamespaceComponent Namespace(String @namespace, TBody body) - where TBody : ICSharpSourceComponent + where TBody : ICSharpSourceComponent => new(@namespace, body); + /// + /// Creates a new type name component. + /// + /// + /// The options to use when appending the type name. + /// + /// + /// The type whose name to append. + /// + /// + /// A new type name component. + /// + public static TypeNameComponent TypeName(Type type, TypeNameOptions options) => new(type, options); + /// /// Creates a new type name component. /// @@ -110,6 +127,123 @@ public static NamespaceComponent Namespace(String @namespace, TBod /// public static TypeNameComponent TypeName(Type type) => new(type); + /// + /// Creates a new type name component. + /// + /// + /// The options to use when appending the type name. + /// + /// + /// The type whose name to append. + /// + /// + /// A new type name component. + /// + public static TypeNameComponent TypeName(TypeNameOptions options) => new(typeof(T), options); + + /// + /// Creates a new type name component. + /// + /// + /// The type whose name to append. + /// + /// + /// A new type name component. + /// + public static TypeNameComponent TypeName() => new(typeof(T)); + + /// + /// Gets a backing type for enums with no more than members. + /// + /// + /// The options to use when appending the type name. + /// + /// + /// The amount of members in the enum. + /// + /// + /// A type name for the smallest required backing type to accommodate enum members. + /// + public static TypeNameComponent EnumBackingType(Int32 memberCount, TypeNameOptions options) + => memberCount switch + { + <= Byte.MaxValue + 1 => TypeName(options), + <= Int16.MaxValue + 1 => TypeName(options), + <= Int32.MaxValue => TypeName(options), + }; + + /// + /// Gets a backing type for enums with no more than members. + /// + /// + /// The amount of members in the enum. + /// + /// + /// A type name for the smallest required backing type to accommodate enum members. + /// + public static TypeNameComponent EnumBackingType(Int32 memberCount) + => memberCount switch + { + <= Byte.MaxValue + 1 => TypeName(), + <= Int16.MaxValue + 1 => TypeName(), + <= Int32.MaxValue => TypeName(), + }; + + /// + /// Gets a backing type for flags enums with no more than flags. + /// + /// + /// For example, to represent 16 flags + the None = 0 member, 27 should be passed: + /// + /// var backingType = FlagsEnumBackingType(16); // short: 16 flags + None + /// + /// + /// + /// The options to use when appending the type name. + /// + /// + /// The amount of flags in the enum. + /// + /// + /// A type name for the smallest required backing type to accommodate individual flags, + /// + public static TypeNameComponent FlagsEnumBackingType(Int32 flagsCount, TypeNameOptions options) + => flagsCount switch + { + <= 8 => TypeName(options), + <= 16 => TypeName(options), + <= 32 => TypeName(options), + <= 64 => TypeName(options), + _ => throw new ArgumentOutOfRangeException(nameof(flagsCount), flagsCount, + $"{nameof(flagsCount)} must be less than 65.") + }; + + /// + /// Gets a backing type for flags enums with no more than flags. + /// + /// + /// For example, to represent 16 flags + the None = 0 member, 27 should be passed: + /// + /// var backingType = FlagsEnumBackingType(16); // short: 16 flags + None + /// + /// + /// + /// The amount of flags in the enum. + /// + /// + /// A type name for the smallest required backing type to accommodate individual flags, + /// + public static TypeNameComponent FlagsEnumBackingType(Int32 flagsCount) + => flagsCount switch + { + <= 8 => TypeName(), + <= 16 => TypeName(), + <= 32 => TypeName(), + <= 64 => TypeName(), + _ => throw new ArgumentOutOfRangeException(nameof(flagsCount), flagsCount, + $"{nameof(flagsCount)} must be less than 65.") + }; + /// /// Creates a new type name component. /// @@ -134,8 +268,8 @@ public static NamespaceComponent Namespace(String @namespace, TBod /// A new attribute argument component. /// public static AttributeArgumentComponent PositionalAttributeArgument( - TypeNameComponent type, - String? parameterName = null) + TypeNameComponent type, + String? parameterName = null) => AttributeArgumentComponent.CreatePositional(type, parameterName); /// @@ -151,8 +285,8 @@ public static AttributeArgumentComponent PositionalAttributeArgument( /// A new attribute argument component. /// public static AttributeArgumentComponent PositionalAttributeArgument( - ICSharpSourceComponent expressionComponent, - String? parameterName = null) + ICSharpSourceComponent expressionComponent, + String? parameterName = null) => AttributeArgumentComponent.CreatePositional(expressionComponent, parameterName); /// @@ -168,8 +302,8 @@ public static AttributeArgumentComponent PositionalAttributeArgument( /// A new attribute argument component. /// public static AttributeArgumentComponent PositionalAttributeArgument( - String expression, - String? parameterName = null) + String expression, + String? parameterName = null) => AttributeArgumentComponent.CreatePositional(expression, parameterName); /// @@ -185,8 +319,8 @@ public static AttributeArgumentComponent PositionalAttributeArgument( /// A new attribute argument component. /// public static AttributeArgumentComponent NamedAttributeArgument( - TypeNameComponent type, - String propertyName) + TypeNameComponent type, + String propertyName) => AttributeArgumentComponent.CreateNamed(type, propertyName); /// @@ -202,8 +336,8 @@ public static AttributeArgumentComponent NamedAttributeArgument( /// A new attribute argument component. /// public static AttributeArgumentComponent NamedAttributeArgument( - ICSharpSourceComponent expressionComponent, - String propertyName) + ICSharpSourceComponent expressionComponent, + String propertyName) => AttributeArgumentComponent.CreateNamed(expressionComponent, propertyName); /// @@ -219,8 +353,8 @@ public static AttributeArgumentComponent NamedAttributeArgument( /// A new attribute argument component. /// public static AttributeArgumentComponent NamedAttributeArgument( - String expression, - String propertyName) + String expression, + String propertyName) => AttributeArgumentComponent.CreateNamed(expression, propertyName); /// @@ -236,8 +370,8 @@ public static AttributeArgumentComponent NamedAttributeArgument( /// A new attribute component. /// public static AttributeComponent Attribute( - TypeNameComponent type, - ImmutableArray arguments) + TypeNameComponent type, + params ImmutableArray arguments) => new(type, arguments); /// @@ -268,28 +402,28 @@ public static AttributeComponent Attribute( /// A new type component. /// public static TypeComponent Type( - String modifiers, - String name, - TBody body, - ImmutableArray typeParameters = default, - ImmutableArray baseTypeList = default, - ImmutableArray attributes = default) - where TBody : ICSharpSourceComponent + String modifiers, + String name, + TBody body, + ImmutableArray typeParameters = default, + ImmutableArray baseTypeList = default, + ImmutableArray attributes = default) + where TBody : ICSharpSourceComponent => new( - Modifiers: modifiers, - Name: name, - TypeParameters: typeParameters.IsDefault ? [] : typeParameters, - BaseTypeList: baseTypeList.IsDefault ? [] : baseTypeList, - Attributes: attributes.IsDefault ? [] : attributes, - Body: body); + Modifiers: modifiers, + Name: name, + TypeParameters: typeParameters.IsDefault ? [] : typeParameters, + BaseTypeList: baseTypeList.IsDefault ? [] : baseTypeList, + Attributes: attributes.IsDefault ? [] : attributes, + Body: body); /// public static TypeComponent Type( - String modifiers, - String name, - ImmutableArray typeParameters = default, - ImmutableArray baseTypeList = default, - ImmutableArray attributes = default) + String modifiers, + String name, + ImmutableArray typeParameters = default, + ImmutableArray baseTypeList = default, + ImmutableArray attributes = default) => Type(modifiers, name, EmptyComponent.Instance, typeParameters, baseTypeList, attributes); /// @@ -302,4 +436,42 @@ public static TypeComponent Type( /// A new strategy component. /// public static StrategyComponent Create(Action append) => new(append); + + /// + /// Creates a new strategy based component. + /// + /// + /// The strategy to use when appending. + /// + /// + /// The state used by the component. + /// + /// + /// The type of state used by the component. + /// + /// + /// A new strategy component. + /// + public static StrategyComponent Create( + TState state, + Action append) => new(state, append); + + /// + /// Creates a new region component. + /// + /// + /// The name of the region. + /// + /// + /// The body of the region. + /// + /// + /// The type of the body. + /// + /// + /// A new region component. + /// + public static RegionComponent Region(String name, TBody body) + where TBody : ICSharpSourceComponent + => new(name, body); } diff --git a/Lyra/DocsCommentElementComponent.cs b/Lyra/DocsCommentElementComponent.cs new file mode 100644 index 0000000..07a81d0 --- /dev/null +++ b/Lyra/DocsCommentElementComponent.cs @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +/// +/// Implements a docs comment XML element. +/// +/// +/// This type does not support value equality. +/// +/// +/// The state used by the component. +/// +/// +/// The callback to invoke when appending attributes. +/// +/// +/// The callback to invoke when appending the body. +/// +/// +/// The options used by the component. +/// +/// +/// The type of state used by the component. +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +internal readonly record struct DocsCommentElementComponent( + TState State, + Action? Attributes, + Action? Body, + DocsCommentElementComponentOptions Options) + : ICSharpSourceComponent +{ + /// + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (Options.IndentSelfWithComment) + { + builder.Indent("/// "); + } + + builder.Append('<'); + + builder.Append(Options.Element); + + if (Attributes is not null) + { + builder.Append(' '); + Attributes.Invoke(State, builder, cancellationToken); + } + + if (Body is not null) + { + builder.Append('>'); + + if (Options.Multiline) + { + builder.AppendLine(); + } + + if (Options.IndentBody) + { + builder.Indent(); + } + + Body.Invoke(State, builder, cancellationToken); + + if (Options.IndentBody) + { + builder.Detent(); + } + + if (Options.Multiline) + { + builder.AppendLine(); + } + + builder.Append($"'); + + if (Options.IndentSelfWithComment) + { + builder.Detent(); + } + } + + /// + public Boolean Equals(DocsCommentElementComponent other) => + throw new NotSupportedException("Equals is not supported on this type."); + + /// + public override Int32 GetHashCode() => + throw new NotSupportedException("GetHashCode is not supported on this type."); +} + +/// +/// Implements a docs comment XML element. +/// +/// +/// The callback to invoke when appending attributes. +/// +/// +/// The callback to invoke when appending the body. +/// +/// +/// The options used by the component. +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +internal readonly record struct DocsCommentElementComponent( + Action? Attributes, + Action? Body, + DocsCommentElementComponentOptions Options) + : ICSharpSourceComponent +{ + /// + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (Options.IndentSelfWithComment) + { + builder.Indent("/// "); + } + + builder.Append('<'); + + builder.Append(Options.Element); + + if (Attributes is not null) + { + builder.Append(' '); + Attributes.Invoke(builder, cancellationToken); + } + + if (Body is not null) + { + builder.Append('>'); + + if (Options.Multiline) + { + builder.AppendLine(); + } + + if (Options.IndentBody) + { + builder.Indent(); + } + + Body.Invoke(builder, cancellationToken); + + if (Options.IndentBody) + { + builder.Detent(); + } + + if (Options.Multiline) + { + builder.AppendLine(); + } + + builder.Append($"'); + + if (Options.IndentSelfWithComment) + { + builder.Detent(); + } + } + + /// + public Boolean Equals(DocsCommentElementComponent other) => + throw new NotSupportedException("Equals is not supported on this type."); + + /// + public override Int32 GetHashCode() => + throw new NotSupportedException("GetHashCode is not supported on this type."); +} diff --git a/Lyra/DocsCommentElementComponentOptions.cs b/Lyra/DocsCommentElementComponentOptions.cs new file mode 100644 index 0000000..cdd701f --- /dev/null +++ b/Lyra/DocsCommentElementComponentOptions.cs @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +using System.Runtime.CompilerServices; + +/// +/// Provides options for rendering docs comment elements. +/// +/// +/// The name of the element. +/// +/// +/// Indicates whether to indent using /// regardless of whether +/// is or not. +/// +/// +/// Indicates whether to render docs comment elements on multiple lines. +/// This should be set to for elements whose contents +/// span multiple lines. +/// +/// +/// Indicates whether to indent the body using the builders default indentation. +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +internal readonly record struct DocsCommentElementComponentOptions( + String Element, + Boolean IndentSelfWithComment, + Boolean Multiline, + Boolean IndentBody) +{ + private static DocsCommentElementComponentOptions CreateRoot([CallerMemberName] String element = "") => + new(Element: element.ToLowerInvariant(), + IndentSelfWithComment: true, + Multiline: true, + IndentBody: false); + + private static DocsCommentElementComponentOptions CreateMultilineChild([CallerMemberName] String element = "") => + new(Element: element.ToLowerInvariant(), + IndentSelfWithComment: false, + Multiline: true, + IndentBody: false); + + private static DocsCommentElementComponentOptions CreateInlineChild([CallerMemberName] String element = "") => + new(Element: element.ToLowerInvariant(), + IndentSelfWithComment: false, + Multiline: false, + IndentBody: false); + + private static DocsCommentElementComponentOptions + CreateIndentingMultilineChild([CallerMemberName] String element = "") => + new(Element: element.ToLowerInvariant(), + IndentSelfWithComment: false, + Multiline: true, + IndentBody: true); + + /// + /// Gets options for the summary element. + /// + public static DocsCommentElementComponentOptions Summary { get; } = CreateRoot(); + + /// + /// Gets options for the remarks element. + /// + public static DocsCommentElementComponentOptions Remarks { get; } = CreateRoot(); + + /// + /// Gets options for the returns element. + /// + public static DocsCommentElementComponentOptions Returns { get; } = CreateRoot(); + + /// + /// Gets options for the param element. + /// + public static DocsCommentElementComponentOptions Param { get; } = CreateRoot(); + + /// + /// Gets options for the typeparam element. + /// + public static DocsCommentElementComponentOptions TypeParam { get; } = CreateRoot(); + + /// + /// Gets options for the inheritdoc element. + /// + public static DocsCommentElementComponentOptions Inheritdoc { get; } = CreateRoot(); + + /// + /// Gets options for the c element. + /// + public static DocsCommentElementComponentOptions C { get; } = CreateInlineChild(); + + /// + /// Gets options for the b element. + /// + public static DocsCommentElementComponentOptions B { get; } = CreateInlineChild(); + + /// + /// Gets options for the br element. + /// + public static DocsCommentElementComponentOptions Br { get; } = CreateInlineChild(); + + /// + /// Gets options for the i element. + /// + public static DocsCommentElementComponentOptions I { get; } = CreateInlineChild(); + + /// + /// Gets options for the em element. + /// + public static DocsCommentElementComponentOptions Em { get; } = CreateInlineChild(); + + /// + /// Gets options for the paramref element. + /// + public static DocsCommentElementComponentOptions ParamRef { get; } = CreateInlineChild(); + + /// + /// Gets options for the typeparamref element. + /// + public static DocsCommentElementComponentOptions TypeParamRef { get; } = CreateInlineChild(); + + /// + /// Gets options for the see element. + /// + public static DocsCommentElementComponentOptions See { get; } = CreateInlineChild(); + + /// + /// Gets options for the code element. + /// + public static DocsCommentElementComponentOptions Code { get; } = CreateMultilineChild(); + + /// + /// Gets options for the br element. + /// + public static DocsCommentElementComponentOptions Para { get; } = CreateMultilineChild(); + + /// + /// Gets options for the list element. + /// + public static DocsCommentElementComponentOptions List { get; } = CreateIndentingMultilineChild(); + + /// + /// Gets options for the term element. + /// + public static DocsCommentElementComponentOptions Term { get; } = CreateIndentingMultilineChild(); + + /// + /// Gets options for the description element. + /// + public static DocsCommentElementComponentOptions Description { get; } = CreateIndentingMultilineChild(); + + /// + /// Gets options for the listheader element. + /// + public static DocsCommentElementComponentOptions ListHeader { get; } = CreateIndentingMultilineChild(); + + /// + /// Gets options for the item element. + /// + public static DocsCommentElementComponentOptions Item { get; } = CreateIndentingMultilineChild(); +} diff --git a/Lyra/Generator.cs b/Lyra/Generator.cs index 2f8ade2..3a1cacb 100644 --- a/Lyra/Generator.cs +++ b/Lyra/Generator.cs @@ -9,7 +9,7 @@ namespace RhoMicro.CodeAnalysis.Lyra; /// Generates sources for using the and related types. /// [Generator(LanguageNames.CSharp)] -public class Generator : IIncrementalGenerator +public sealed class Generator : IIncrementalGenerator { /// public void Initialize(IncrementalGeneratorInitializationContext context) diff --git a/Lyra/ListComponent.cs b/Lyra/ListComponent.cs index 89f7c98..a202fca 100644 --- a/Lyra/ListComponent.cs +++ b/Lyra/ListComponent.cs @@ -14,7 +14,7 @@ namespace RhoMicro.CodeAnalysis.Lyra; /// This type supports value equality. The and /// members are not taken into account when calculating equality. /// -/// +/// /// The elements to append to the builder. /// /// @@ -29,6 +29,9 @@ namespace RhoMicro.CodeAnalysis.Lyra; /// /// The terminator to append to the final element. /// +/// +/// The type of list to render. +/// /// /// The type of element to append. /// @@ -38,25 +41,21 @@ namespace RhoMicro.CodeAnalysis.Lyra; #if CSHARPSOURCEBUILDER_GENERATOR [IncludeFile] #endif -internal readonly record struct ListComponent( - ImmutableArray Elements, - Action Append, - TSeparator Separator, - Action Separate, - TSeparator Terminator) - : ICSharpSourceComponent +internal readonly record struct ListComponent( + TList List, + Action Append, + TSeparator Separator, + Action Separate, + TSeparator Terminator) + : ICSharpSourceComponent + where TList : IList { /// public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); - if (Elements.IsDefault) - { - return; - } - - var length = Elements.Length; + var length = List.Count; for (var index = 0; index < length; index++) { @@ -67,7 +66,7 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation Separate.Invoke(Separator, index, length, builder, cancellationToken); } - var element = Elements[index]; + var element = List[index]; Append.Invoke(element, index, length, builder, cancellationToken); if (index == length - 1) @@ -78,7 +77,7 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation } /// - public Boolean Equals(ListComponent other) + public Boolean Equals(ListComponent other) { if (!EqualityComparer.Default.Equals(other.Separator, Separator)) { @@ -90,7 +89,7 @@ public Boolean Equals(ListComponent other) return false; } - if (!ImmutableArrayEqualityComparer.Equals(other.Elements, Elements)) + if (!other.List.SequenceEqual(List)) { return false; } @@ -104,7 +103,12 @@ public override Int32 GetHashCode() var hc = new HashCode(); hc.Add(Separator); hc.Add(Terminator); - hc.Add(Elements, ImmutableArrayEqualityComparer.Default); + + foreach (var element in List) + { + hc.Add(element); + } + var result = hc.ToHashCode(); return result; diff --git a/Lyra/Lyra.csproj b/Lyra/Lyra.csproj index a0188df..ee93ddc 100644 --- a/Lyra/Lyra.csproj +++ b/Lyra/Lyra.csproj @@ -6,6 +6,7 @@ true true true + 1.2.0 @@ -30,9 +31,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - - all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Lyra/NamespaceComponent.cs b/Lyra/NamespaceComponent.cs index 1fce513..f4d75cc 100644 --- a/Lyra/NamespaceComponent.cs +++ b/Lyra/NamespaceComponent.cs @@ -42,11 +42,11 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation builder.AppendLine($"namespace {Name}").AppendLine("{").Indent(); } - builder.Append(Body); + builder.AppendLine(Body); if (Name is not []) { - builder.Detent().AppendLine("}"); + builder.Detent().Append("}"); } } } diff --git a/Lyra/README.md b/Lyra/README.md index 9d161a1..03a9e44 100644 --- a/Lyra/README.md +++ b/Lyra/README.md @@ -17,13 +17,13 @@ This project is licensed under the `MPL-2.0` license. .NET CLI: ``` -dotnet add package RhoMicro.CodeAnalysis.Lyra --version 1.0.1 +dotnet add package RhoMicro.CodeAnalysis.Lyra --version 1.1.0 ``` PackageReference: ```xml - + all runtime; build; native; contentfiles; analyzers @@ -141,3 +141,44 @@ Note how the initial indentation in front of `foo` is carried along. The detection of these indentations can be configured using the `CSharpSourceBuilderOptions.InitialInterpolationIndentationDetector` property. + +### Components + +Some useful components are provided out of the box: +```cs +var body = ComponentFactory.Create( + myModel, + static (myModel, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append(myModel.Value); + }); + +var type = ComponentFactory.Type( + "partial struct", + myModel.Name, + body, + baseTypeList: + [ + TypeName() + ]); + +var @namespace = ComponentFactory.Namespace( + myModel.Namespace, + type); + +builder.AppendLine(@namespace); +``` + +The output will look similar to this: +```cs +namespace Namespace +{ + partial struct Name : global::System.IComparable + { + // myModel.Value + } +} + +``` diff --git a/Lyra/RegionComponent.cs b/Lyra/RegionComponent.cs new file mode 100644 index 0000000..95c5932 --- /dev/null +++ b/Lyra/RegionComponent.cs @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +/// +/// Represents a #region. +/// +/// +/// This type supports value equality. +/// +/// +/// The name of the region. +/// +/// +/// The body of the region. +/// +/// +/// The type of the body. +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +internal readonly record struct RegionComponent(String Name, TBody Body) : ICSharpSourceComponent + where TBody : ICSharpSourceComponent +{ + /// + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + builder.DetentAll(out var indentations) + .Indent(builder.Options.InitialIndentation) + .AppendLine($"#region {Name}") + .Indent(indentations.Skip(builder.Options.InitialIndentation.Length)) + .AppendLine() + .AppendLine(Body) + .AppendLine() + .DetentAll(out indentations) + .Indent(builder.Options.InitialIndentation) + .Append("#endregion") + .Indent(indentations.Skip(builder.Options.InitialIndentation.Length)); + } + + /// + public Boolean Equals(RegionComponent other) + { + if (other.Name != Name) + { + return false; + } + + if (!EqualityComparer.Default.Equals(other.Body, Body)) + { + return false; + } + + return true; + } + + /// + public override Int32 GetHashCode() + { + var hc = new HashCode(); + hc.Add(Name); + hc.Add(Body); + var result = hc.ToHashCode(); + + return result; + } +} diff --git a/Lyra/StrategyComponent.cs b/Lyra/StrategyComponent.cs index fbe9250..cc423f3 100644 --- a/Lyra/StrategyComponent.cs +++ b/Lyra/StrategyComponent.cs @@ -8,27 +8,66 @@ namespace RhoMicro.CodeAnalysis.Lyra; /// /// This type does not support value equality. /// -/// +/// /// The callback to invoke when appending to a builder. /// #if CSHARPSOURCEBUILDER_GENERATOR [IncludeFile] #endif -readonly record struct StrategyComponent(Action append) : ICSharpSourceComponent +readonly record struct StrategyComponent(Action Append) : ICSharpSourceComponent { /// public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); - append.Invoke(builder, cancellationToken); + Append.Invoke(builder, cancellationToken); } /// public Boolean Equals(StrategyComponent other) => - throw new NotSupportedException("Equals is not supported on this type."); + throw new NotSupportedException("Equals is not supported on this type."); /// public override Int32 GetHashCode() => - throw new NotSupportedException("GetHashCode is not supported on this type."); + throw new NotSupportedException("GetHashCode is not supported on this type."); +} + +/// +/// Implements an arbitrary component that uses state. +/// +/// +/// This type does not support value equality. +/// +/// +/// The state used by the component. +/// +/// +/// The callback to invoke when appending to a builder. +/// +/// +/// The type of state used by the component. +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +readonly record struct StrategyComponent( + TState State, + Action Append) : ICSharpSourceComponent +{ + /// + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + Append.Invoke(State, builder, cancellationToken); + } + + /// + public Boolean Equals(StrategyComponent other) => + throw new NotSupportedException("Equals is not supported on this type."); + + /// + public override Int32 GetHashCode() => + throw new NotSupportedException("GetHashCode is not supported on this type."); } diff --git a/Lyra/TypeComponent.cs b/Lyra/TypeComponent.cs index 6f8cdb0..9b80f5e 100644 --- a/Lyra/TypeComponent.cs +++ b/Lyra/TypeComponent.cs @@ -112,9 +112,9 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation builder.AppendLine() .AppendLine('{') .Indent() - .Append(Body) + .AppendLine(Body) .Detent() - .AppendLine('}'); + .Append('}'); } /// diff --git a/Lyra/TypeNameComponent.cs b/Lyra/TypeNameComponent.cs index 63034ae..599c435 100644 --- a/Lyra/TypeNameComponent.cs +++ b/Lyra/TypeNameComponent.cs @@ -22,9 +22,14 @@ namespace RhoMicro.CodeAnalysis.Lyra; /// /// The type whose name to append. /// - public TypeNameComponent(Type type) + /// + /// The options to use when appending the type name, or + /// to use the builders default type name options. + /// + public TypeNameComponent(Type type, TypeNameOptions? options = null) { _type = type; + _options = options; _typeName = null; } @@ -40,6 +45,7 @@ public TypeNameComponent(String typeName) _typeName = typeName; } + private readonly TypeNameOptions? _options; private readonly Type? _type; private readonly String? _typeName; @@ -50,7 +56,14 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation if (_type is not null) { - builder.AppendTypeName(_type); + if (_options is { } o) + { + builder.AppendTypeName(_type, o); + } + else + { + builder.AppendTypeName(_type); + } } else if (_typeName is not null) { diff --git a/Lyra/TypeNameOptions.cs b/Lyra/TypeNameOptions.cs new file mode 100644 index 0000000..e958998 --- /dev/null +++ b/Lyra/TypeNameOptions.cs @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +/// +/// Provides options for appending type names. +/// +/// +/// Indicates whether primitives should be appended using their language +/// alias, as opposed to their fully qualified framework name. +/// +/// +/// Indicates whether type names should be globally qualified. +/// +#if CSHARPSOURCEBUILDER_GENERATOR +[IncludeFile] +#endif +internal readonly record struct TypeNameOptions( + Boolean UseTypeAliases = true, + Boolean UseGloballyQualifiedName = true) +{ + /// + /// Gets the default instance. + /// + public static TypeNameOptions Default { get; } = new(UseTypeAliases: true, UseGloballyQualifiedName: true); +} From 22dea92438b0321778494bb41ac0b0696b54b51e Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Tue, 9 Dec 2025 03:06:41 +0100 Subject: [PATCH 14/36] fixed nuget setup --- Janus.Tests/Janus.Tests.csproj | 3 ++- Janus/Janus.csproj | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Janus.Tests/Janus.Tests.csproj b/Janus.Tests/Janus.Tests.csproj index 36eae04..6685fb7 100644 --- a/Janus.Tests/Janus.Tests.csproj +++ b/Janus.Tests/Janus.Tests.csproj @@ -35,7 +35,8 @@ - + + diff --git a/Janus/Janus.csproj b/Janus/Janus.csproj index 4deece3..1090421 100644 --- a/Janus/Janus.csproj +++ b/Janus/Janus.csproj @@ -22,14 +22,14 @@ - 1.0.9 + 1.0.10 ..\dist\dev - - + + From b9aeab45bf71ed3a69e6f225764365ef1f672cd1 Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Tue, 9 Dec 2025 03:10:48 +0100 Subject: [PATCH 15/36] wip --- Janus.Analyzers/AnalyzerReleases.Shipped.md | 2 +- .../Components/InspectionsComponent.cs | 4 -- Janus.Analyzers/JanusAnalyzer.cs | 3 - .../Janus.TestApplication.csproj | 17 ++++++ Janus.TestApplication/Program.cs | 58 +++++++++++++++++++ Janus.TestLibrary/IntDoubleString.cs | 11 ++++ Janus.TestLibrary/Janus.TestLibrary.csproj | 20 +++++++ Janus/Janus.csproj | 17 ++---- 8 files changed, 111 insertions(+), 21 deletions(-) create mode 100644 Janus.TestApplication/Janus.TestApplication.csproj create mode 100644 Janus.TestApplication/Program.cs create mode 100644 Janus.TestLibrary/IntDoubleString.cs create mode 100644 Janus.TestLibrary/Janus.TestLibrary.csproj diff --git a/Janus.Analyzers/AnalyzerReleases.Shipped.md b/Janus.Analyzers/AnalyzerReleases.Shipped.md index 7df4a24..f3c849d 100644 --- a/Janus.Analyzers/AnalyzerReleases.Shipped.md +++ b/Janus.Analyzers/AnalyzerReleases.Shipped.md @@ -1,4 +1,4 @@ -## Release 23.0.0-rc0 +## Release 23.0.0 ### New Rules diff --git a/Janus.Analyzers/Components/InspectionsComponent.cs b/Janus.Analyzers/Components/InspectionsComponent.cs index 1a11a4e..b3fdecb 100644 --- a/Janus.Analyzers/Components/InspectionsComponent.cs +++ b/Janus.Analyzers/Components/InspectionsComponent.cs @@ -18,10 +18,6 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation { ct.ThrowIfCancellationRequested(); - // TODO: - // when mayBeNull in Is(out u): - // comment explaining that value is guaranteed to be null if returning false, even without annotation - var mayBeNull = m.Variants.Any(static v => v.Type is { IsNullable: true } or { Kind: VariantTypeKind.Unknown }); diff --git a/Janus.Analyzers/JanusAnalyzer.cs b/Janus.Analyzers/JanusAnalyzer.cs index 584ec89..88ad9a7 100644 --- a/Janus.Analyzers/JanusAnalyzer.cs +++ b/Janus.Analyzers/JanusAnalyzer.cs @@ -49,10 +49,7 @@ public override void Initialize(AnalysisContext context) _ = context ?? throw new ArgumentNullException(nameof(context)); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - -#if !DEBUG context.EnableConcurrentExecution(); -#endif context.RegisterOperationAction( ReportToStringSettingIgnored, OperationKind.Attribute); diff --git a/Janus.TestApplication/Janus.TestApplication.csproj b/Janus.TestApplication/Janus.TestApplication.csproj new file mode 100644 index 0000000..2e80e4d --- /dev/null +++ b/Janus.TestApplication/Janus.TestApplication.csproj @@ -0,0 +1,17 @@ + + + + Exe + net10.0 + preview + enable + enable + false + true + + + + + + + diff --git a/Janus.TestApplication/Program.cs b/Janus.TestApplication/Program.cs new file mode 100644 index 0000000..7bfd698 --- /dev/null +++ b/Janus.TestApplication/Program.cs @@ -0,0 +1,58 @@ +// See https://aka.ms/new-console-template for more information + +using System.Runtime.CompilerServices; +using System.Text; +using System.Text.Json; +using RhoMicro.CodeAnalysis; +using TestApplication; + +var options = new JsonSerializerOptions(JsonSerializerDefaults.Web) +{ + PropertyNamingPolicy = new CustomNamingPolicy(), PropertyNameCaseInsensitive = true, +}; + +var union = IntDoubleString.Create(42d); +union = "foo"; +union = 47; + +var typeName = union.Switch( + onDouble: _ => "double", + onInt32: _ => "int", + onImmutableArray: _ => "array", + onString: _ => "string", + onFile: _ => "file"); + +if (union.IsDouble) +{ + var d = union.AsDouble; +} + +Console.WriteLine(union); +var serialized = JsonSerializer.Serialize(union, options); +Console.WriteLine(serialized); +serialized = serialized.ToLower().Replace("47", "[9,8,7]").Replace("2", "3"); +Console.WriteLine(serialized); +var deserialized = + JsonSerializer.Deserialize( + serialized, options); +Console.WriteLine(deserialized); + +internal sealed class CustomNamingPolicy : JsonNamingPolicy +{ + public override String ConvertName(String name) + { + var sb = new StringBuilder(); + + for (var i = 0; i < name.Length; i++) + { + if (i is not 0) + { + sb.Append('-'); + } + + sb.Append(name[i]); + } + + return sb.ToString(); + } +} diff --git a/Janus.TestLibrary/IntDoubleString.cs b/Janus.TestLibrary/IntDoubleString.cs new file mode 100644 index 0000000..2e0a6e5 --- /dev/null +++ b/Janus.TestLibrary/IntDoubleString.cs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace TestApplication; + +using System.Collections.Immutable; +using RhoMicro.CodeAnalysis; + +[UnionType, string>(Groups = ["Foo"])] +[UnionType(IsNullable = true, Name = "File", Description = "a file stream")] +[UnionTypeSettings(JsonConverterSetting = JsonConverterSetting.EmitJsonConverter)] +public sealed partial class IntDoubleString; diff --git a/Janus.TestLibrary/Janus.TestLibrary.csproj b/Janus.TestLibrary/Janus.TestLibrary.csproj new file mode 100644 index 0000000..51108bc --- /dev/null +++ b/Janus.TestLibrary/Janus.TestLibrary.csproj @@ -0,0 +1,20 @@ + + + + netstandard2.0 + preview + enable + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + diff --git a/Janus/Janus.csproj b/Janus/Janus.csproj index 1090421..1d8f1ab 100644 --- a/Janus/Janus.csproj +++ b/Janus/Janus.csproj @@ -4,11 +4,6 @@ netstandard2.0 false - true - true - true - enable - @@ -21,10 +16,10 @@ https://raw.githubusercontent.com/PaulBraetz/RhoMicro.CodeAnalysis/master/Janus/PackageLogo.svg - - 1.0.10 - ..\dist\dev - + + + + @@ -32,10 +27,6 @@ - - - - From 5960017a52a92ef00ba9dabf5d2c3978358a45c8 Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Tue, 9 Dec 2025 03:11:29 +0100 Subject: [PATCH 16/36] remove unions generator --- .../EqualityTests.cs | 18 - .../FactoryTests.cs | 29 - .../GlobalUsings.cs | 6 - .../NullableTests.cs | 42 - .../ToStringTests.cs | 41 - .../UnionsGenerator.EndToEnd.Tests.csproj | 27 - UnionsGenerator.TestApplication/Output.g.cs | 32 - UnionsGenerator.TestApplication/Program.cs | 191 --- .../UnionsGenerator.TestApplication.csproj | 29 - UnionsGenerator.TestLibrary/Foo.cs | 0 UnionsGenerator.TestLibrary/IUnion.cs | 14 - UnionsGenerator.TestLibrary/IUnionFactory.cs | 11 - UnionsGenerator.TestLibrary/IntDouble.cs | 694 ---------- .../IntDoubleString.cs | 1057 --------------- .../UnionsGenerator.TestLibrary.csproj | 21 - UnionsGenerator.Tests/ConstructorTests.cs | 88 -- UnionsGenerator.Tests/ConversionTests.cs | 213 --- .../DiagnosticsLevelTests.cs | 44 - UnionsGenerator.Tests/EnumerableExtensions.cs | 15 - UnionsGenerator.Tests/JsonConverterTests.cs | 118 -- UnionsGenerator.Tests/NullableTests.cs | 123 -- UnionsGenerator.Tests/TestBase.cs | 212 --- .../UnionsGenerator.Tests.csproj | 33 - UnionsGenerator/Analyzers/Analyzer.cs | 71 - UnionsGenerator/Analyzers/Diagnostics.cs | 99 -- .../Analyzers/DiagnosticsStrings.cs | 100 -- UnionsGenerator/Analyzers/Extensions.cs | 19 - UnionsGenerator/Analyzers/Providers.cs | 342 ----- .../Attributes/RelationAttribute.cs | 101 -- .../Attributes/UnionTypeAttribute.Internal.cs | 25 - .../Attributes/UnionTypeAttribute.cs | 292 ---- .../Attributes/UnionTypeFactoryAttribute.cs | 27 - .../Attributes/UnionTypeSettingsAttribute.cs | 276 ---- .../Generators/SettingsAttributeData.cs | 149 --- UnionsGenerator/Generators/UnionsGenerator.cs | 525 -------- UnionsGenerator/Models/FactoryModel.cs | 42 - UnionsGenerator/Models/GroupModel.cs | 14 - UnionsGenerator/Models/GroupsModel.cs | 59 - UnionsGenerator/Models/IModel.cs | 33 - .../Models/NamedOrTypeParameterType.cs | 93 -- .../Models/PartialRepresentableTypeModel.cs | 113 -- .../Models/PartialUnionTypeModel.cs | 185 --- UnionsGenerator/Models/RelatedTypeModel.cs | 44 - UnionsGenerator/Models/RelationModel.cs | 82 -- UnionsGenerator/Models/RelationType.cs | 81 -- .../Models/RepresentableTypeModel.cs | 73 - UnionsGenerator/Models/SettingsModel.cs | 118 -- .../Models/SymbolDisplayFormats.cs | 62 - UnionsGenerator/Models/TypeNamesModel.cs | 236 ---- UnionsGenerator/Models/TypeNature.cs | 125 -- UnionsGenerator/Models/TypeSignatureModel.cs | 208 --- UnionsGenerator/Models/UnionTypeModel.cs | 200 --- .../Properties/launchSettings.json | 16 - .../Transformation/CommentBuilder.cs | 22 - .../Transformation/IndentedStringBuilder.cs | 322 ----- .../Storage/StorageSelectionViolation.cs | 71 - .../StorageStrategy.FieldContainerStrategy.cs | 38 - ...rageStrategy.ReferenceContainerStrategy.cs | 28 - .../StorageStrategy.ValueContainerStrategy.cs | 45 - .../Transformation/Storage/StorageStrategy.cs | 162 --- .../Storage/StrategySourceHost.cs | 175 --- .../Storage/StringExtensions.cs | 35 - .../Visitors/AppendableSourceText.cs | 1174 ----------------- .../Transformation/Visitors/IVisitor.cs | 23 - .../Visitors/SourceTextVisitor.cs | 15 - .../StructuralRepresentationVisitor.cs | 228 ---- UnionsGenerator/UnionsGenerator.csproj | 58 - UnionsGenerator/Usings.cs | 3 - UnionsGenerator/Utils/ConstantSources.cs | 142 -- UnionsGenerator/Utils/EnumerableExtensions.cs | 20 - UnionsGenerator/Utils/Qualifications.cs | 139 -- UnionsGenerator/Utils/Throw.cs | 13 - UnionsGenerator/requirements.md | 74 -- UnionsGenerator/tailwind.extension.json | 21 - 74 files changed, 9676 deletions(-) delete mode 100644 UnionsGenerator.EndToEnd.Tests/EqualityTests.cs delete mode 100644 UnionsGenerator.EndToEnd.Tests/FactoryTests.cs delete mode 100644 UnionsGenerator.EndToEnd.Tests/GlobalUsings.cs delete mode 100644 UnionsGenerator.EndToEnd.Tests/NullableTests.cs delete mode 100644 UnionsGenerator.EndToEnd.Tests/ToStringTests.cs delete mode 100644 UnionsGenerator.EndToEnd.Tests/UnionsGenerator.EndToEnd.Tests.csproj delete mode 100644 UnionsGenerator.TestApplication/Output.g.cs delete mode 100644 UnionsGenerator.TestApplication/Program.cs delete mode 100644 UnionsGenerator.TestApplication/UnionsGenerator.TestApplication.csproj delete mode 100644 UnionsGenerator.TestLibrary/Foo.cs delete mode 100644 UnionsGenerator.TestLibrary/IUnion.cs delete mode 100644 UnionsGenerator.TestLibrary/IUnionFactory.cs delete mode 100644 UnionsGenerator.TestLibrary/IntDouble.cs delete mode 100644 UnionsGenerator.TestLibrary/IntDoubleString.cs delete mode 100644 UnionsGenerator.TestLibrary/UnionsGenerator.TestLibrary.csproj delete mode 100644 UnionsGenerator.Tests/ConstructorTests.cs delete mode 100644 UnionsGenerator.Tests/ConversionTests.cs delete mode 100644 UnionsGenerator.Tests/DiagnosticsLevelTests.cs delete mode 100644 UnionsGenerator.Tests/EnumerableExtensions.cs delete mode 100644 UnionsGenerator.Tests/JsonConverterTests.cs delete mode 100644 UnionsGenerator.Tests/NullableTests.cs delete mode 100644 UnionsGenerator.Tests/TestBase.cs delete mode 100644 UnionsGenerator.Tests/UnionsGenerator.Tests.csproj delete mode 100644 UnionsGenerator/Analyzers/Analyzer.cs delete mode 100644 UnionsGenerator/Analyzers/Diagnostics.cs delete mode 100644 UnionsGenerator/Analyzers/DiagnosticsStrings.cs delete mode 100644 UnionsGenerator/Analyzers/Extensions.cs delete mode 100644 UnionsGenerator/Analyzers/Providers.cs delete mode 100644 UnionsGenerator/Attributes/RelationAttribute.cs delete mode 100644 UnionsGenerator/Attributes/UnionTypeAttribute.Internal.cs delete mode 100644 UnionsGenerator/Attributes/UnionTypeAttribute.cs delete mode 100644 UnionsGenerator/Attributes/UnionTypeFactoryAttribute.cs delete mode 100644 UnionsGenerator/Attributes/UnionTypeSettingsAttribute.cs delete mode 100644 UnionsGenerator/Generators/SettingsAttributeData.cs delete mode 100644 UnionsGenerator/Generators/UnionsGenerator.cs delete mode 100644 UnionsGenerator/Models/FactoryModel.cs delete mode 100644 UnionsGenerator/Models/GroupModel.cs delete mode 100644 UnionsGenerator/Models/GroupsModel.cs delete mode 100644 UnionsGenerator/Models/IModel.cs delete mode 100644 UnionsGenerator/Models/NamedOrTypeParameterType.cs delete mode 100644 UnionsGenerator/Models/PartialRepresentableTypeModel.cs delete mode 100644 UnionsGenerator/Models/PartialUnionTypeModel.cs delete mode 100644 UnionsGenerator/Models/RelatedTypeModel.cs delete mode 100644 UnionsGenerator/Models/RelationModel.cs delete mode 100644 UnionsGenerator/Models/RelationType.cs delete mode 100644 UnionsGenerator/Models/RepresentableTypeModel.cs delete mode 100644 UnionsGenerator/Models/SettingsModel.cs delete mode 100644 UnionsGenerator/Models/SymbolDisplayFormats.cs delete mode 100644 UnionsGenerator/Models/TypeNamesModel.cs delete mode 100644 UnionsGenerator/Models/TypeNature.cs delete mode 100644 UnionsGenerator/Models/TypeSignatureModel.cs delete mode 100644 UnionsGenerator/Models/UnionTypeModel.cs delete mode 100644 UnionsGenerator/Properties/launchSettings.json delete mode 100644 UnionsGenerator/Transformation/CommentBuilder.cs delete mode 100644 UnionsGenerator/Transformation/IndentedStringBuilder.cs delete mode 100644 UnionsGenerator/Transformation/Storage/StorageSelectionViolation.cs delete mode 100644 UnionsGenerator/Transformation/Storage/StorageStrategy.FieldContainerStrategy.cs delete mode 100644 UnionsGenerator/Transformation/Storage/StorageStrategy.ReferenceContainerStrategy.cs delete mode 100644 UnionsGenerator/Transformation/Storage/StorageStrategy.ValueContainerStrategy.cs delete mode 100644 UnionsGenerator/Transformation/Storage/StorageStrategy.cs delete mode 100644 UnionsGenerator/Transformation/Storage/StrategySourceHost.cs delete mode 100644 UnionsGenerator/Transformation/Storage/StringExtensions.cs delete mode 100644 UnionsGenerator/Transformation/Visitors/AppendableSourceText.cs delete mode 100644 UnionsGenerator/Transformation/Visitors/IVisitor.cs delete mode 100644 UnionsGenerator/Transformation/Visitors/SourceTextVisitor.cs delete mode 100644 UnionsGenerator/Transformation/Visitors/StructuralRepresentationVisitor.cs delete mode 100644 UnionsGenerator/UnionsGenerator.csproj delete mode 100644 UnionsGenerator/Usings.cs delete mode 100644 UnionsGenerator/Utils/ConstantSources.cs delete mode 100644 UnionsGenerator/Utils/EnumerableExtensions.cs delete mode 100644 UnionsGenerator/Utils/Qualifications.cs delete mode 100644 UnionsGenerator/Utils/Throw.cs delete mode 100644 UnionsGenerator/requirements.md delete mode 100644 UnionsGenerator/tailwind.extension.json diff --git a/UnionsGenerator.EndToEnd.Tests/EqualityTests.cs b/UnionsGenerator.EndToEnd.Tests/EqualityTests.cs deleted file mode 100644 index 29bc48d..0000000 --- a/UnionsGenerator.EndToEnd.Tests/EqualityTests.cs +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.EndToEnd.Tests; - -using RhoMicro.CodeAnalysis; - -using System; - -public partial class EqualityTests -{ - [UnionType] - private partial class Foo - { - public Boolean Equals(Foo? foo) => true; - } - [UnionType] - private partial class Bar; -} diff --git a/UnionsGenerator.EndToEnd.Tests/FactoryTests.cs b/UnionsGenerator.EndToEnd.Tests/FactoryTests.cs deleted file mode 100644 index d3763ba..0000000 --- a/UnionsGenerator.EndToEnd.Tests/FactoryTests.cs +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.EndToEnd.Tests; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -public partial class FactoryTests -{ - [UnionType(FactoryName = "MyFactory")] - private partial class NamedFactoryUnion; - - [Fact] - public void UsesProvidedFactoryName() - { - _ = NamedFactoryUnion.MyFactory(0); - } - - [UnionType] - private partial class UnnamedFactoryUnion; - - [Fact] - public void UsesDefaultFactoryName() - { - _ = UnnamedFactoryUnion.Create(0); - } -} diff --git a/UnionsGenerator.EndToEnd.Tests/GlobalUsings.cs b/UnionsGenerator.EndToEnd.Tests/GlobalUsings.cs deleted file mode 100644 index 31ff61e..0000000 --- a/UnionsGenerator.EndToEnd.Tests/GlobalUsings.cs +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -global using Xunit; - -[assembly: RhoMicro.CodeAnalysis.UnionTypeSettings(Miscellaneous = RhoMicro.CodeAnalysis.MiscellaneousSettings.Default | RhoMicro.CodeAnalysis.MiscellaneousSettings.GenerateJsonConverter | - RhoMicro.CodeAnalysis.MiscellaneousSettings.EmitStructuralRepresentation)] diff --git a/UnionsGenerator.EndToEnd.Tests/NullableTests.cs b/UnionsGenerator.EndToEnd.Tests/NullableTests.cs deleted file mode 100644 index b3d53bc..0000000 --- a/UnionsGenerator.EndToEnd.Tests/NullableTests.cs +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.EndToEnd.Tests; - -using System; - -public partial class NullableTests -{ - [UnionType] - private readonly partial struct NullableBoolUnion; - - [UnionType(Alias = "Int", Options = UnionTypeOptions.Nullable)] - private readonly partial struct PedroUnion; - [UnionType] - private readonly partial struct PedroLongUnion; - - [Fact] - public void NullableBoolTrueFactoryCall() - { - var u = NullableBoolUnion.Create((Boolean?)true); - Assert.True(u.IsNullable_of_Boolean); - Assert.True(u.AsNullable_of_Boolean.HasValue); - Assert.True(u.AsNullable_of_Boolean.Value); - } - - [Fact] - public void NullableBoolFalseFactoryCall() - { - var u = NullableBoolUnion.Create((Boolean?)false); - Assert.True(u.IsNullable_of_Boolean); - Assert.True(u.AsNullable_of_Boolean.HasValue); - Assert.False(u.AsNullable_of_Boolean.Value); - } - - [Fact] - public void NullableBoolNullFactoryCall() - { - var u = NullableBoolUnion.Create((Boolean?)null); - Assert.True(u.IsNullable_of_Boolean); - Assert.False(u.AsNullable_of_Boolean.HasValue); - } -} diff --git a/UnionsGenerator.EndToEnd.Tests/ToStringTests.cs b/UnionsGenerator.EndToEnd.Tests/ToStringTests.cs deleted file mode 100644 index a8a519f..0000000 --- a/UnionsGenerator.EndToEnd.Tests/ToStringTests.cs +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.EndToEnd.Tests; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -public partial class ToStringTests -{ - [UnionType] - [UnionTypeSettings(ToStringSetting = ToStringSetting.None)] - private partial class NoToStringUnionType; - - [Fact] - public void UsesDefaultToString() - { - NoToStringUnionType u = "Foo"; - var expected = typeof(NoToStringUnionType).FullName; - var actual = u.ToString(); - - Assert.Equal(expected, actual); - } - - [UnionType] - private partial class CustomToStringUnionType - { - public override String ToString() => "Foo"; - } - - [Fact] - public void UsesCustomToString() - { - CustomToStringUnionType u = "Bar"; - var expected = "Foo"; - var actual = u.ToString(); - - Assert.Equal(expected, actual); - } -} diff --git a/UnionsGenerator.EndToEnd.Tests/UnionsGenerator.EndToEnd.Tests.csproj b/UnionsGenerator.EndToEnd.Tests/UnionsGenerator.EndToEnd.Tests.csproj deleted file mode 100644 index f81b108..0000000 --- a/UnionsGenerator.EndToEnd.Tests/UnionsGenerator.EndToEnd.Tests.csproj +++ /dev/null @@ -1,27 +0,0 @@ - - - - net8.0 - enable - enable - true - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - diff --git a/UnionsGenerator.TestApplication/Output.g.cs b/UnionsGenerator.TestApplication/Output.g.cs deleted file mode 100644 index b74d2b4..0000000 --- a/UnionsGenerator.TestApplication/Output.g.cs +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// -// This file was generated using the global::RhoMicro.CodeAnalysis.Lyra.CSharpSourceBuilder. -// -namespace SampleNamespace -{ - partial class IntDoubleString - { - [global::System.FlagsAttribute] - public enum VariantGroups - { - None = 0, - Integer = 1 << 0, - Number = 1 << 1, - ReferenceType = 1 << 2, - ValueType = 1 << 3 - } - } -} -namespace SampleNamespace -{ - partial class IntDoubleString - { - [global::System.FlagsAttribute] - public enum VariantGroups - { - Double = 0, - Int32 = 1, - String = 2 - } - } -} diff --git a/UnionsGenerator.TestApplication/Program.cs b/UnionsGenerator.TestApplication/Program.cs deleted file mode 100644 index 6d2a85a..0000000 --- a/UnionsGenerator.TestApplication/Program.cs +++ /dev/null @@ -1,191 +0,0 @@ -// See https://aka.ms/new-console-template for more information - -using System.Collections.Immutable; -using System.ComponentModel; -using System.Net.Http.Headers; -using System.Runtime.InteropServices; -using RhoMicro.CodeAnalysis; -using RhoMicro.CodeAnalysis.Lyra; -using static RhoMicro.CodeAnalysis.Lyra.ComponentFactory; -using Type = System.Type; - -// var ids = IntDoubleString.Create(32); -// Console.WriteLine(ids.Variant); -// Console.WriteLine(ids.Value); -// var id = IntDouble.Create(ids); -// Console.WriteLine(id.Variant); -// Console.WriteLine(id.Value); -// id = 42d; -// Console.WriteLine(id.Variant); -// Console.WriteLine(id.Value); -// ids = "Foo"; -// Console.WriteLine(ids.Variant); -// Console.WriteLine(ids.Value); - -using var sb1 = new CSharpSourceBuilder( - new CSharpSourceBuilderOptions() - { - Prelude = (b,_)=>b.AppendLine("// "), - DefaultIndentation = " ".AsMemory() - }); -sb1.Append("namespace ").AppendLine("Foo").AppendLine('{'); -sb1.Indent(); -sb1.Append( - ComponentFactory.Type( - "enum", - "Bar", - ComponentFactory.Create((b, _) => b.AppendLine("Baz = 0")))); -sb1.Detent(); -sb1.AppendLine('}'); -Console.WriteLine(sb1); - -var placeholder = "\nbar\nbaz"; -sb1.Clear().AppendLine( - $""" - foo{placeholder} - """); -Console.WriteLine(sb1); - - -var variants = new VariantModel[3] -{ - new("Double", 0, new VariantGroupModel[2]), new("Int32", 1, new VariantGroupModel[3]), - new("String", 2, new VariantGroupModel[1]) -}; -var groups = new VariantGroupModel[5] -{ - new("None", 0, []), new("Integer", 1, [variants[1]]), new("Number", 2, [variants[0], variants[1]]), - new("ReferenceType", 3, [variants[2]]), new("ValueType", 4, [variants[0], variants[1]]) -}; - -variants[0].Groups![0] = groups[2]; -variants[0].Groups![1] = groups[4]; - -variants[1].Groups![0] = groups[1]; -variants[1].Groups![1] = groups[2]; -variants[1].Groups![2] = groups[4]; - -variants[2].Groups![0] = groups[3]; - - -var model = new UnionModel( - "class", - "IntDoubleString", - "SampleNamespace", - [new ContainingTypeModel("class", "ContainingType")], - ImmutableCollectionsMarshal.AsImmutableArray(variants), - ImmutableCollectionsMarshal.AsImmutableArray(groups)); - -using var sb = new CSharpSourceBuilder(new CSharpSourceBuilderOptions() -{ - Prelude = (b, _) => b - .SetInterpolationIndentationDetector( - NullInterpolationIndentationDetector.Instance, - out var previous) - .AppendLine( - $""" - // SPDX-License-Identifier: MPL-2.0 - // - // This file was generated using the {typeof(CSharpSourceBuilder)}. - // - """) - .SetInterpolationIndentationDetector(previous) -}); - - -sb.Append(new VariantGroupKindsComponent(model)); -sb.Append(new VariantKindsComponent(model)); - -File.WriteAllText(Path.Combine(Environment.CurrentDirectory, "../../../", "Output.g.cs"), sb.ToString()); - -readonly record struct VariantGroupKindsComponent(UnionModel Model) : ICSharpSourceComponent -{ - public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) - { - cancellationToken.ThrowIfCancellationRequested(); - - var component = Namespace( - Model.Namespace, - Type( - $"partial {Model.Modifier}", - name: Model.Name, - body: Type( - modifiers: "public enum", - name: "VariantGroups", - attributes: - [ - Attribute(TypeName(typeof(FlagsAttribute)), arguments: []) - ], - body: List( - Model.VariantGroups, - static (v, i, l, b, ct) => - { - ct.ThrowIfCancellationRequested(); - - if (i is 0) - { - b.Append($"{v.Name} = 0"); - } - else - { - b.Append($"{v.Name} = 1 << {i - 1}"); - } - }, - separator: ",\n", - terminator: "\n")))); - - builder.Append(component); - } -} - -readonly record struct VariantKindsComponent(UnionModel Model) : ICSharpSourceComponent -{ - public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) - { - cancellationToken.ThrowIfCancellationRequested(); - - var component = Namespace( - Model.Namespace, - Type( - modifiers: $"partial {Model.Modifier}", - name: Model.Name, - body: Type( - modifiers: "public enum", - name: "VariantGroups", - attributes: - [ - Attribute(TypeName(typeof(FlagsAttribute)), arguments: []) - ], - body: List( - Model.Variants, - static (v, i, l, b, ct) => - { - ct.ThrowIfCancellationRequested(); - - b.Append($"{v.Name} = {i}"); - }, - separator: ",\n", - terminator: "\n")))); - - builder.Append(component); - } -} - - -readonly record struct VariantModel(String Name, Int32 Index, VariantGroupModel[] Groups); - -readonly record struct VariantGroupModel(String Name, Int32 Index, VariantModel[] Variants); - -readonly record struct ContainingTypeModel(String Modifier, String Name); - -sealed record UnionModel( - String Modifier, - String Name, - String Namespace, - ImmutableArray ContainingTypes, - ImmutableArray Variants, - ImmutableArray VariantGroups); - -// ############### -// generated below -// ############### diff --git a/UnionsGenerator.TestApplication/UnionsGenerator.TestApplication.csproj b/UnionsGenerator.TestApplication/UnionsGenerator.TestApplication.csproj deleted file mode 100644 index 38a21a2..0000000 --- a/UnionsGenerator.TestApplication/UnionsGenerator.TestApplication.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - Exe - net8.0 - preview - enable - enable - - false - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - diff --git a/UnionsGenerator.TestLibrary/Foo.cs b/UnionsGenerator.TestLibrary/Foo.cs deleted file mode 100644 index e69de29..0000000 diff --git a/UnionsGenerator.TestLibrary/IUnion.cs b/UnionsGenerator.TestLibrary/IUnion.cs deleted file mode 100644 index 7e1b66d..0000000 --- a/UnionsGenerator.TestLibrary/IUnion.cs +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis; - -using System.Diagnostics.CodeAnalysis; - -public interface IUnion -{ - TUnion MapTo(TFactory factory) - where TFactory : IUnionFactory; - - bool TryMapTo(TFactory factory, [NotNullWhen(true)] out TUnion? union) - where TFactory : IUnionFactory; -} diff --git a/UnionsGenerator.TestLibrary/IUnionFactory.cs b/UnionsGenerator.TestLibrary/IUnionFactory.cs deleted file mode 100644 index cb090a4..0000000 --- a/UnionsGenerator.TestLibrary/IUnionFactory.cs +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis; - -using System.Diagnostics.CodeAnalysis; - -public interface IUnionFactory -{ - TUnion Create(TVariant value); - bool TryCreate(TVariant value, [NotNullWhen(true)] out TUnion? union); -} diff --git a/UnionsGenerator.TestLibrary/IntDouble.cs b/UnionsGenerator.TestLibrary/IntDouble.cs deleted file mode 100644 index 1e8f755..0000000 --- a/UnionsGenerator.TestLibrary/IntDouble.cs +++ /dev/null @@ -1,694 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -using System.Collections.Immutable; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; -using RhoMicro.CodeAnalysis; - -public partial class IntDouble : IUnion, IEquatable -{ -#region VariantGroupKinds - - [Flags] - public enum VariantGroupKinds : int - { - None = 0, - Number = 1 << 0, - Integer = 1 << 1, - ValueType = 1 << 3 - } - -#endregion - -#region VariantKind - - public enum VariantKind : byte - { - Int32, - Double - } - -#endregion - -#region Factory - - readonly struct Factory : IUnionFactory - { - public Boolean TryCreate(TVariant value, [NotNullWhen(true)] out IntDouble? union) - { - switch (value) - { - // switch against all variants - case Int32 v: return IntDouble.TryCreate(v, out union); - case Double v: return IntDouble.TryCreate(v, out union); - // By supporting the union itself as a variant, we avoid SOE when matching IUnion, - // which would otherwise recurse into this method. - case IntDouble v: - union = new IntDouble(v); - return true; - // TODO: detect/register related unions, match here (before IUnion box) - - // attempt to convert to this union through double dispatch, this will - // effectively convert between related unions, albeit while incurring boxing - case IUnion v: return v.TryMapTo(this, out union); - // switch against nullable variants here - /* - * case null: - * in this placeholder code, variants for string? and SomeVirtualClass? are assumed - * first, we check exact matches: - * - * if(typeof(TVariant) == typeof(string)) - * { - * return Union.TryCreate((string?)null, out union); - * } - * if(typeof(TVariant) == typeof(SomeVirtualClass)) - * { - * return Union.TryCreate((SomeVirtualClass?)null, out union); - * } - * - * next, we check polymorphic relationships on non-sealed variants: - * - * if(typeof(SomeVirtualClass).IsAssignableFrom(typeof(TVariant))) - * { - * return Union.TryCreate((SomeVirtualClass?)null, out union); - * } - */ - default: - union = default; - return false; - } - } - - public IntDouble Create(TVariant value) - { - // factory is responsible for exact variant match conversion - switch (value) - { - // switch against all variants - case Int32 v: return new IntDouble(v); - case Double v: return new IntDouble(v); - // By supporting the union itself as a variant, we avoid SOE when matching IUnion, - // which would otherwise recurse into this method. - case IntDouble v: return new IntDouble(v); - // TODO: detect/register related unions, match here (before IUnion box) - - // attempt to convert to this union through double dispatch, this will - // effectively convert between related unions, albeit while incurring boxing - case IUnion v: return v.MapTo(this); - case null: - // switch against nullable variants here - /* - * in this placeholder code, variants for string? and SomeVirtualClass? are assumed - * first, we check exact matches: - * - * if(typeof(TVariant) == typeof(string)) - * { - * return Union.Create((string?)null); - * } - * if(typeof(TVariant) == typeof(SomeVirtualClass)) - * { - * return Union.Create((SomeVirtualClass?)null); - * } - * - * next, we check polymorphic relationships on non-sealed variants: - * - * if(typeof(SomeVirtualClass).IsAssignableFrom(typeof(TVariant))) - * { - * return Union.Create((SomeVirtualClass?)null); - * } - */ - default: - throw new ArgumentOutOfRangeException( - nameof(value), - value, - $"Unable to create an instance of '{typeof(IntDouble)}' from a value of type '{value?.GetType() ?? typeof(TVariant)}'"); - } - } - } - -#endregion - -#region UnmanagedVariantsContainer - - [StructLayout(LayoutKind.Explicit)] - private readonly struct UnmanagedVariantsContainer - { - public UnmanagedVariantsContainer(Int32 value) - { - Int32 = value; - } - - public UnmanagedVariantsContainer(Double value) - { - Double = value; - } - - [FieldOffset(0)] public readonly Int32 Int32; - [FieldOffset(0)] public readonly Double Double; - } - -#endregion - -#region Constructors - - public IntDouble(IntDouble prototype) - { - } - - public IntDouble(Int32 value) : this(value, validate: true) - { - } - - public IntDouble(Double value) : this(value, validate: true) - { - } - - private IntDouble(Int32 value, Boolean validate) - { - if (validate) - { - var isValid = true; - Validate(value, throwIfInvalid: true, ref isValid); - } - - // assign reference container with null - // omit if no reference variants - // __referenceVariantsContainer = null!; - // assign unmanaged container if unmanaged variant - __unmanagedVariantsContainer = new UnmanagedVariantsContainer(value); - // enumerate unused managed variant field assignments with default - - Variant = VariantKind.Int32; - } - - private IntDouble(Double value, Boolean validate) - { - if (validate) - { - var isValid = true; - Validate(value, throwIfInvalid: true, ref isValid); - } - - // assign reference container with null - // omit if no reference variants - // __referenceVariantsContainer = null!; - // assign unmanaged container if unmanaged variant - __unmanagedVariantsContainer = new UnmanagedVariantsContainer(value); - // enumerate unused managed variant field assignments with default - - Variant = VariantKind.Double; - } - -#endregion - -#region Fields - - private readonly UnmanagedVariantsContainer __unmanagedVariantsContainer; - // private readonly Object __referenceVariantsContainer; - // enumerate fields for managed structs, make boxing optional - -#endregion - -#region Properties - - public VariantKind Variant { get; } - - public Object Value - { - get - { - Object result = Variant switch - { - VariantKind.Int32 => __unmanagedVariantsContainer.Int32, - VariantKind.Double => __unmanagedVariantsContainer.Double, - _ => throw new InvalidOperationException( - $"Unable to determine the variant of this union, as '{nameof(Variant)}' is not representing a valid variant of this union: '{Variant}'. This could be either because the union itself was not initialized correctly, or due to a bug in the 'UnionsGenerator' that generated this union type. Please report an issue to the maintainer.") - }; - - return result; - } - } - - public Boolean IsInt32 => Variant is VariantKind.Int32; - public Int32 AsInt32 => __unmanagedVariantsContainer.Int32; - - public Int32 ToInt32 => IsInt32 - ? AsInt32 - : throw new InvalidOperationException( - $"Unable to convert union to 'Int32', as it is currently representing the '{Variant}' variant."); - - public Boolean IsDouble => Variant is VariantKind.Double; - public Double AsDouble => __unmanagedVariantsContainer.Double; - - public Double ToDouble => IsDouble - ? AsDouble - : throw new InvalidOperationException( - $"Unable to convert union to 'Double', as it is currently representing the '{Variant}' variant."); - -#endregion - -#region Is/As Methods - - // warn that value is guaranteed to be null for class TVariant and false return - // attach [NNW(true)] and TVariant? when not representing any nullable reference variants - public Boolean TryAs(out TVariant value) - { - // TODO: fix variant comparisons not actually checking the currently represented variant - var variant = typeof(TVariant); - value = default!; - - if (variant == typeof(Int32)) - { - if (IsInt32) - { - var fromValue = AsInt32; - value = Unsafe.As(ref fromValue); - return true; - } - } - - if (variant == typeof(Double)) - { - var fromValue = AsDouble; - value = Unsafe.As(ref fromValue); - return IsDouble; - } - - if (variant == typeof(IntDouble)) - { - return true; - } - - if (variant == typeof(Object)) - { - return true; - } - - // include this check if we have value type variants - if (variant == typeof(Enum)) - { - // disjunct all value type variants - return Variant is VariantKind.Int32 or VariantKind.Double; - } - - return false; - } - - public Boolean Is() - { - var variant = typeof(TVariant); - - if (variant == Variant.Type) - { - return true; - } - - if (variant == typeof(IntDouble)) - { - return true; - } - - if (variant == typeof(Object)) - { - return true; - } - - // include this check if we have value type variants, replicate for enum types - if (variant == typeof(ValueType)) - { - // disjunct all value type variants - return Variant is VariantKind.Int32 or VariantKind.Double; - } - - return false; - } - -#endregion - -#region Validation - - static partial void Validate(Int32 value, Boolean throwIfInvalid, ref Boolean isValid); - static partial void Validate(Double value, Boolean throwIfInvalid, ref Boolean isValid); - -#endregion - -#region Factories - - public static IntDouble Create(IntDouble value) => new(value); - - public static Boolean TryCreate(IntDouble value, [NotNullWhen(true)] out IntDouble? union) - { - union = Create(value); - - return true; - } - - public static IntDouble Create(Int32 value) => new IntDouble(value, validate: true); - - public static Boolean TryCreate(Int32 value, [NotNullWhen(true)] out IntDouble? union) - { - var isValid = true; - Validate(value, throwIfInvalid: false, ref isValid); - union = isValid - ? new IntDouble(value, validate: false) - : default; - - return isValid; - } - - public static IntDouble Create(Double value) => new IntDouble(value, validate: true); - - public static Boolean TryCreate(Double value, [NotNullWhen(true)] out IntDouble? union) - { - var isValid = true; - Validate(value, throwIfInvalid: false, ref isValid); - union = isValid - ? new IntDouble(value, validate: false) - : default; - - return isValid; - } - - public static IntDouble Create(T value) - => new Factory().Create(value); - - public static Boolean TryCreate(T value, [NotNullWhen(true)] out IntDouble? union) - => new Factory().TryCreate(value, out union); - -#endregion - -#region Mapping - - // this method is intended for inter-union conversion only - public TUnion MapTo( - TFactory factory) - where TFactory : IUnionFactory - { - var result = Variant switch - { - VariantKind.Int32 => factory.Create(__unmanagedVariantsContainer.Int32), - VariantKind.Double => factory.Create(__unmanagedVariantsContainer.Double), - _ => throw new InvalidOperationException( - $"Unable to determine the variant of this union, as '{nameof(Variant)}' is not representing a valid variant of this union: '{Variant}'. This could be either because the union itself was not initialized correctly, or due to a bug in the 'UnionsGenerator' that generated this union type. Please report an issue to the maintainer.") - }; - - return result; - } - - // this method is intended for inter-union conversion only - public Boolean TryMapTo( - TFactory factory, - [NotNullWhen(true)] out TUnion? union) - where TFactory : IUnionFactory - { - var result = Variant switch - { - VariantKind.Int32 => factory.TryCreate(__unmanagedVariantsContainer.Int32, out union), - VariantKind.Double => factory.TryCreate(__unmanagedVariantsContainer.Double, out union), - _ => throw new InvalidOperationException( - $"Unable to determine the variant of this union, as '{nameof(Variant)}' is not representing a valid variant of this union: '{Variant}'. This could be either because the union itself was not initialized correctly, or due to a bug in the 'UnionsGenerator' that generated this union type. Please report an issue to the maintainer.") - }; - - return result; - } - -#endregion - -#region Equality - - public override Boolean Equals(Object? obj) => obj is IntDouble union && Equals(union); - - public Boolean Equals(IntDouble other) - { - // only emit for reference unions - if (ReferenceEquals(this, other)) - { - return true; - } - - if (Variant != other.Variant) - { - return false; - } - - var result = Variant switch - { - VariantKind.Int32 => - EqualityComparer.Default.Equals( - __unmanagedVariantsContainer.Int32, - other.__unmanagedVariantsContainer.Int32), - VariantKind.Double => - EqualityComparer.Default.Equals( - __unmanagedVariantsContainer.Double, - other.__unmanagedVariantsContainer.Double), - _ => throw new InvalidOperationException( - $"Unable to determine the variant of this union, as '{nameof(Variant)}' is not representing a valid variant of this union: '{Variant}'. This could be either because the union itself was not initialized correctly, or due to a bug in the 'UnionsGenerator' that generated this union type. Please report an issue to the maintainer.") - }; - - return result; - } - - public override Int32 GetHashCode() - { - var result = Variant switch - { - VariantKind.Int32 => HashCode.Combine(Variant, __unmanagedVariantsContainer.Int32), - VariantKind.Double => HashCode.Combine(__unmanagedVariantsContainer.Double), - _ => throw new InvalidOperationException( - $"Unable to determine the variant of this union, as '{nameof(Variant)}' is not representing a valid variant of this union: '{Variant}'. This could be either because the union itself was not initialized correctly, or due to a bug in the 'UnionsGenerator' that generated this union type. Please report an issue to the maintainer.") - }; - - return result; - } - -#endregion - -#region Conversion - - public static implicit operator IntDouble(Int32 value) => Create(value); - public static explicit operator Int32(IntDouble union) => union.ToInt32; - - public static implicit operator IntDouble(Double value) => Create(value); - public static explicit operator Double(IntDouble union) => union.ToDouble; - -#endregion -} - -public static partial class IntDoubleVariantGroupKindsOperations -{ - extension(IntDouble.VariantGroupKinds @this) - { - public Boolean HasFlag(IntDouble.VariantGroupKinds flag) => (@this & flag) == flag; - - public static ImmutableArray GetAllValues() => - [ - IntDouble.VariantGroupKinds.Number, - IntDouble.VariantGroupKinds.Integer, - IntDouble.VariantGroupKinds.ValueType - ]; - - public Int32 IndividualGroupCount => PopCount((UInt32)@this); - - private void GetIndividualGroups(Span buffer) - { - var count = @this.IndividualGroupCount; - - if (count is 0) - { - return; - } - - if (buffer.Length < count) - { - throw new ArgumentOutOfRangeException( - nameof(buffer), - $"{nameof(buffer)} did not have the required length of IndividualGroupCount. The required length was {count}, but the span provided had a length of {buffer.Length}."); - } - - if (count is 1) - { - buffer[0] = @this; - return; - } - - var groupIndex = 0; - for (var i = LeadingZeroCount((UInt32)@this) + 1; i < 33 && groupIndex < count; i++) - { - var flagPosition = 32 - i; - var flag = 1 << flagPosition; - if (((Int32)@this & flag) == flag) - { - buffer[groupIndex++] = (IntDouble.VariantGroupKinds)flag; - } - } - } - - public ImmutableArray GetIndividualGroups() - { - var groups = new IntDouble.VariantGroupKinds[@this.IndividualGroupCount]; - @this.GetIndividualGroups(groups); - var result = ImmutableCollectionsMarshal.AsImmutableArray(groups); - - return result; - } - - public String Name - { - get - { - var count = @this.IndividualGroupCount; - - if (count is 0 or 1) - { - return @this.DegenerateName; - } - - Span - groups = stackalloc IntDouble.VariantGroupKinds[count]; // Count never exceeds 32 - @this.GetIndividualGroups(groups); - var builder = new StringBuilder(); - for (var i = 0; i < count; i++) - { - var group = groups[i]; - var name = group.DegenerateName; - - if (i is not 0) - { - builder.Append(" | "); - } - - builder.Append(name); - } - - var result = builder.ToString(); - - return result; - } - } - - private String DegenerateName - { - get - { - return @this switch - { - IntDouble.VariantGroupKinds.None => nameof(IntDouble.VariantGroupKinds.None), - IntDouble.VariantGroupKinds.Number => nameof(IntDouble.VariantGroupKinds.Number), - IntDouble.VariantGroupKinds.Integer => nameof(IntDouble.VariantGroupKinds.Integer), - IntDouble.VariantGroupKinds.ValueType => nameof(IntDouble.VariantGroupKinds - .ValueType), - _ => throw new InvalidOperationException( - $"The VariantGroups instance was not initialized correctly and is holding an invalid value: {@this}") - }; - } - } - } - - private static ReadOnlySpan Log2DeBruijn => - [ - 00, 09, 01, 10, 13, 21, 02, 29, - 11, 14, 16, 18, 22, 25, 03, 30, - 08, 12, 20, 28, 15, 17, 24, 07, - 19, 27, 23, 06, 26, 05, 04, 31 - ]; - - private static Int32 LeadingZeroCount(UInt32 value) - { - if (value == 0) - { - return 32; - } - - value |= value >> 01; - value |= value >> 02; - value |= value >> 04; - value |= value >> 08; - value |= value >> 16; - - var result = 31 ^ Unsafe.AddByteOffset( - ref MemoryMarshal.GetReference(Log2DeBruijn), - (IntPtr)(Int32)((value * 0x07C4ACDDu) >> 27)); - - return result; - } - - private static Int32 PopCount(UInt32 value) - { - const UInt32 c1 = 0x_55555555u; - const UInt32 c2 = 0x_33333333u; - const UInt32 c3 = 0x_0F0F0F0Fu; - const UInt32 c4 = 0x_01010101u; - - value -= (value >> 1) & c1; - value = (value & c2) + ((value >> 2) & c2); - value = (((value + (value >> 4)) & c3) * c4) >> 24; - - return (Int32)value; - } -} - -public static partial class IntDoubleVariantKindsOperations -{ - extension(IntDouble.VariantKind @this) - { - public static ImmutableArray GetAllValues() => - [ - IntDouble.VariantKind.Int32, - IntDouble.VariantKind.Double, - ]; - - public String Name - { - get - { - var name = @this switch - { - IntDouble.VariantKind.Int32 => nameof(Int32), - IntDouble.VariantKind.Double => nameof(Double), - _ => throw new InvalidOperationException( - "Unable to determine name, as the variant was not initialized correctly.") - }; - - return name; - } - } - - public Type Type - { - get - { - var type = @this switch - { - IntDouble.VariantKind.Int32 => typeof(Int32), - IntDouble.VariantKind.Double => typeof(Double), - _ => throw new InvalidOperationException( - "Unable to determine type, as the variant was not initialized correctly.") - }; - - return type; - } - } - - public IntDouble.VariantGroupKinds Groups - { - get - { - var groups = @this switch - { - IntDouble.VariantKind.Int32 => IntDouble.VariantGroupKinds.Number | - IntDouble.VariantGroupKinds.Integer | - IntDouble.VariantGroupKinds.ValueType, - IntDouble.VariantKind.Double => IntDouble.VariantGroupKinds.Number | - IntDouble.VariantGroupKinds.ValueType, - _ => throw new InvalidOperationException( - "Unable to determine groups, as the variant was not initialized correctly.") - }; - - return groups; - } - } - } -} diff --git a/UnionsGenerator.TestLibrary/IntDoubleString.cs b/UnionsGenerator.TestLibrary/IntDoubleString.cs deleted file mode 100644 index 200ab73..0000000 --- a/UnionsGenerator.TestLibrary/IntDoubleString.cs +++ /dev/null @@ -1,1057 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Text; -using RhoMicro.CodeAnalysis; - -public partial class IntDoubleString : IUnion, IEquatable, IComparable -{ -#region VariantGroupKinds - - [Flags] - public enum VariantGroupKinds : byte - { - None = 0, - Number = 1 << 0, - Integer = 1 << 1, - ReferenceType = 1 << 2, - ValueType = 1 << 3, - } - -#endregion - -#region VariantKind - - public enum VariantKind : byte - { - Int32, - Double, - String - } - -#endregion - -#region Factory - - readonly struct Factory : IUnionFactory - { - public Boolean TryCreate(TVariant value, - [NotNullWhen(true)] out IntDoubleString? union) - { - switch (value) - { - // switch against all variants - case Int32 v: return IntDoubleString.TryCreate(v, out union); - case Double v: return IntDoubleString.TryCreate(v, out union); - case String v: return IntDoubleString.TryCreate(v, out union); - // By supporting the union itself as a variant, we avoid SOE when matching IUnion, - // which would otherwise recurse into this method. - case IntDoubleString v: - union = new IntDoubleString(v); - return true; - // TODO: detect/register related unions, match here (before IUnion box) - - // attempt to convert to this union through double dispatch - case IUnion v: return v.TryMapTo(this, out union); - // switch against nullable variants here - /* - * case null: - * in this placeholder code, variants for string? and SomeVirtualClass? are assumed - * first, we check exact matches: - * - * if(typeof(TVariant) == typeof(string)) - * { - * return Union.TryCreate((string?)null, out union); - * } - * if(typeof(TVariant) == typeof(SomeVirtualClass)) - * { - * return Union.TryCreate((SomeVirtualClass?)null, out union); - * } - * - * next, we check polymorphic relationships on non-sealed variants: - * - * if(typeof(SomeVirtualClass).IsAssignableFrom(typeof(TVariant))) - * { - * return Union.TryCreate((SomeVirtualClass?)null, out union); - * } - */ - default: - union = default; - return false; - } - } - - public IntDoubleString Create(TVariant value) - { - // factory is responsible for exact variant match conversion - switch (value) - { - // switch against all variants - case Int32 v: return new IntDoubleString(v); - case Double v: return new IntDoubleString(v); - case String v: return new IntDoubleString(v); - // By supporting the union itself as a variant, we avoid SOE when matching IUnion, - // which would otherwise recurse into this method. - case IntDoubleString v: return new IntDoubleString(v); - // TODO: detect/register related unions, match here (before IUnion box) - - // attempt to convert to this union through double dispatch - case IUnion v: return v.MapTo(this); - case null: - // switch against nullable variants here - /* - * in this placeholder code, variants for string? and SomeVirtualClass? are assumed - * first, we check exact matches: - * - * if(typeof(TVariant) == typeof(string)) - * { - * return Union.Create((string?)null); - * } - * if(typeof(TVariant) == typeof(SomeVirtualClass)) - * { - * return Union.Create((SomeVirtualClass?)null); - * } - * - * next, we check polymorphic relationships on non-sealed variants: - * - * if(typeof(SomeVirtualClass).IsAssignableFrom(typeof(TVariant))) - * { - * return Union.Create((SomeVirtualClass?)null); - * } - */ - default: - throw new ArgumentOutOfRangeException( - nameof(value), - value, - $"Unable to create an instance of '{typeof(IntDoubleString)}' from a value of type '{value?.GetType() ?? typeof(TVariant)}'"); - } - } - } - -#endregion - -#region UnmanagedVariantsContainer - - [StructLayout(LayoutKind.Explicit)] - private readonly struct UnmanagedVariantsContainer - { - public UnmanagedVariantsContainer(Int32 value) - { - Int32 = value; - } - - public UnmanagedVariantsContainer(Double value) - { - Double = value; - } - - [FieldOffset(0)] public readonly Int32 Int32; - [FieldOffset(0)] public readonly Double Double; - } - -#endregion - -#region Constructors - - public IntDoubleString(IntDoubleString prototype) - { - } - - public IntDoubleString(Int32 value) : this(value, validate: true) - { - } - - public IntDoubleString(Double value) : this(value, validate: true) - { - } - - public IntDoubleString(String value) : this(value, validate: true) - { - } - - private IntDoubleString(Int32 value, Boolean validate) - { - if (validate) - { - var isValid = true; - Validate(value, throwIfInvalid: true, ref isValid); - } - - // assign reference container with null - // omit if no reference variants - __referenceVariantsContainer = null!; - // assign unmanaged container if unmanaged variant - __unmanagedVariantsContainer = new UnmanagedVariantsContainer(value); - // enumerate unused managed variant field assignments with default - - Variant = VariantKind.Int32; - } - - private IntDoubleString(Double value, Boolean validate) - { - if (validate) - { - var isValid = true; - Validate(value, throwIfInvalid: true, ref isValid); - } - - // assign reference container with null - // omit if no reference variants - __referenceVariantsContainer = null!; - // assign unmanaged container if unmanaged variant - __unmanagedVariantsContainer = new UnmanagedVariantsContainer(value); - // enumerate unused managed variant field assignments with default - - Variant = VariantKind.Double; - } - - private IntDoubleString(String value, Boolean validate) - { - if (validate) - { - var isValid = true; - Validate(value, throwIfInvalid: true, ref isValid); - } - - // assign reference container with null - // omit if no reference variants - __referenceVariantsContainer = value; - // assign unmanaged container if unmanaged variants exist - __unmanagedVariantsContainer = default; - // enumerate unused managed variant field assignments with default - - Variant = VariantKind.String; - } - -#endregion - -#region Fields - - private readonly UnmanagedVariantsContainer __unmanagedVariantsContainer; - private readonly Object __referenceVariantsContainer; - // enumerate fields for managed structs, make boxing optional - -#endregion - -#region Properties - - public VariantKind Variant { get; } - - public Object Value - { - get - { - Object result = Variant switch - { - VariantKind.Int32 => __unmanagedVariantsContainer.Int32, - VariantKind.Double => __unmanagedVariantsContainer.Double, - VariantKind.String => __referenceVariantsContainer, - _ => throw CreateUnknownVariantException() - }; - - return result; - } - } - - public Boolean IsInt32 => Variant is VariantKind.Int32; - public Int32 AsInt32 => __unmanagedVariantsContainer.Int32; - - public Int32 ToInt32 => IsInt32 - ? AsInt32 - : throw CreateInvalidCastException(VariantKind.Int32); - - public Boolean IsDouble => Variant is VariantKind.Double; - public Double AsDouble => __unmanagedVariantsContainer.Double; - - public Double ToDouble => IsDouble - ? AsDouble - : throw CreateInvalidCastException(VariantKind.Double); - - // MNNW for non-nullable reference variants - [MemberNotNullWhen(true, nameof(AsString))] - public Boolean IsString => Variant is VariantKind.String; - - public String? AsString => (String?)__referenceVariantsContainer; - - public String ToString => IsString - ? AsString - : throw CreateInvalidCastException(VariantKind.String); - -#endregion - -#region Switch - - public void Switch( - Action onInt32, - Action onDouble, - Action onString) - { - switch (Variant) - { - case VariantKind.Int32: - onInt32.Invoke(__unmanagedVariantsContainer.Int32); - break; - case VariantKind.Double: - onDouble.Invoke(__unmanagedVariantsContainer.Double); - break; - case VariantKind.String: - onString.Invoke((string)__referenceVariantsContainer); - break; - } - } - - public void Switch( - TState state, - Action onInt32, - Action onDouble, - Action onString) - { - switch (Variant) - { - case VariantKind.Int32: - onInt32.Invoke(__unmanagedVariantsContainer.Int32, state); - break; - case VariantKind.Double: - onDouble.Invoke(__unmanagedVariantsContainer.Double, state); - break; - case VariantKind.String: - onString.Invoke((string)__referenceVariantsContainer, state); - break; - } - } - - public void Switch( - Action defaultHandler, - Action? onInt32 = null, - Action? onDouble = null, - Action? onString = null) - { - switch (Variant) - { - case VariantKind.Int32: - onInt32?.Invoke(__unmanagedVariantsContainer.Int32); - break; - case VariantKind.Double: - onDouble?.Invoke(__unmanagedVariantsContainer.Double); - break; - case VariantKind.String: - onString?.Invoke((string)__referenceVariantsContainer); - break; - } - } - - public void Switch( - TState state, - Action defaultHandler, - Action? onInt32 = null, - Action? onDouble = null, - Action? onString = null) - { - switch (Variant) - { - case VariantKind.Int32: - onInt32?.Invoke(__unmanagedVariantsContainer.Int32, state); - break; - case VariantKind.Double: - onDouble?.Invoke(__unmanagedVariantsContainer.Double, state); - break; - case VariantKind.String: - onString?.Invoke((string)__referenceVariantsContainer, state); - break; - } - } - - public TResult Switch( - Func onInt32, - Func onDouble, - Func onString) - { - switch (Variant) - { - case VariantKind.Int32: - return onInt32.Invoke(__unmanagedVariantsContainer.Int32); - case VariantKind.Double: - return onDouble.Invoke(__unmanagedVariantsContainer.Double); - case VariantKind.String: - return onString.Invoke((string)__referenceVariantsContainer); - default: - throw CreateUnknownVariantException(); - } - } - - public TResult Switch( - Func defaultHandler, - Func? onInt32 = null, - Func? onDouble = null, - Func? onString = null) - { - switch (Variant) - { - case VariantKind.Int32: - return onInt32 is not null - ? onInt32.Invoke(__unmanagedVariantsContainer.Int32) - : defaultHandler.Invoke(); - case VariantKind.Double: - return onDouble is not null - ? onDouble.Invoke(__unmanagedVariantsContainer.Double) - : defaultHandler.Invoke(); - case VariantKind.String: - return onString is not null - ? onString.Invoke((string)__referenceVariantsContainer) - : defaultHandler.Invoke(); - default: - throw CreateUnknownVariantException(); - } - } - - public TResult Switch( - TResult defaultResult, - Func? onInt32 = null, - Func? onDouble = null, - Func? onString = null) - { - switch (Variant) - { - case VariantKind.Int32: - return onInt32 is not null - ? onInt32.Invoke(__unmanagedVariantsContainer.Int32) - : defaultResult; - case VariantKind.Double: - return onDouble is not null - ? onDouble.Invoke(__unmanagedVariantsContainer.Double) - : defaultResult; - case VariantKind.String: - return onString is not null - ? onString.Invoke((string)__referenceVariantsContainer) - : defaultResult; - default: - throw CreateUnknownVariantException(); - } - } - - public TResult Switch( - TState state, - Func onInt32, - Func onDouble, - Func onString) - { - switch (Variant) - { - case VariantKind.Int32: - return onInt32.Invoke(__unmanagedVariantsContainer.Int32, state); - case VariantKind.Double: - return onDouble.Invoke(__unmanagedVariantsContainer.Double, state); - case VariantKind.String: - return onString.Invoke((string)__referenceVariantsContainer, state); - default: - throw CreateUnknownVariantException(); - } - } - - public TResult Switch( - TState state, - Func defaultHandler, - Func? onInt32 = null, - Func? onDouble = null, - Func? onString = null) - { - switch (Variant) - { - case VariantKind.Int32: - return onInt32 is not null - ? onInt32.Invoke(__unmanagedVariantsContainer.Int32, state) - : defaultHandler.Invoke(state); - case VariantKind.Double: - return onDouble is not null - ? onDouble.Invoke(__unmanagedVariantsContainer.Double, state) - : defaultHandler.Invoke(state); - case VariantKind.String: - return onString is not null - ? onString.Invoke((string)__referenceVariantsContainer, state) - : defaultHandler.Invoke(state); - default: - throw CreateUnknownVariantException(); - } - } - - public TResult Switch( - TState state, - TResult defaultResult, - Func? onInt32 = null, - Func? onDouble = null, - Func? onString = null) - { - switch (Variant) - { - case VariantKind.Int32: - return onInt32 is not null - ? onInt32.Invoke(__unmanagedVariantsContainer.Int32, state) - : defaultResult; - case VariantKind.Double: - return onDouble is not null - ? onDouble.Invoke(__unmanagedVariantsContainer.Double, state) - : defaultResult; - case VariantKind.String: - return onString is not null - ? onString.Invoke((string)__referenceVariantsContainer, state) - : defaultResult; - default: - throw CreateUnknownVariantException(); - } - } - -#endregion - -#region Inspection - - // warn that value is guaranteed to be null for class TVariant and false return - // attach [NNW(true)] and TVariant? when not representing any nullable reference variants - public Boolean TryAs(out TVariant value) - { - // TODO: fix variant comparisons not actually checking the currently represented variant - var variant = typeof(TVariant); - value = default!; - - if (variant == typeof(Int32)) - { - if (IsInt32) - { - var fromValue = AsInt32; - value = Unsafe.As(ref fromValue); - return true; - } - } - - if (variant == typeof(Double)) - { - var fromValue = AsDouble; - value = Unsafe.As(ref fromValue); - return IsDouble; - } - - if (variant == typeof(String)) - { - var fromValue = AsString; - value = Unsafe.As(ref fromValue); - return IsString; - } - - if (variant == typeof(IntDoubleString)) - { - return true; - } - - if (variant == typeof(Object)) - { - return true; - } - - // include this check if we have value type variants - if (variant == typeof(Enum)) - { - // disjunct all value type variants - return Variant is VariantKind.Int32 or VariantKind.Double; - } - - // enumerate reference variants that do not directly inherit from object - if (variant.IsAssignableFrom(typeof(String))) - { - return IsString; - } - - return false; - } - - public Boolean Is() - { - var variant = typeof(TVariant); - - if (variant == Variant.Type) - { - return true; - } - - if (variant == typeof(IntDoubleString)) - { - return true; - } - - if (variant == typeof(Object)) - { - return true; - } - - // include this check if we have value type variants, replicate for enum types - if (variant == typeof(ValueType)) - { - // disjunct all value type variants - return Variant is VariantKind.Int32 or VariantKind.Double; - } - - // enumerate reference variants that do not directly inherit from object - if (variant.IsAssignableFrom(typeof(String))) - { - return IsString; - } - - return false; - } - -#endregion - -#region Validation - - static partial void Validate( - Int32 value, - Boolean throwIfInvalid, - ref Boolean isValid); - - static partial void Validate( - Double value, - Boolean throwIfInvalid, - ref Boolean isValid); - - static partial void Validate( - String value, - Boolean throwIfInvalid, - ref Boolean isValid); - - private InvalidCastException CreateInvalidCastException( - VariantKind variant) - => new InvalidCastException( - $"Unable to convert union to '{variant.Name}', as it is currently representing the '{Variant}' variant."); - - private InvalidOperationException CreateUnknownVariantException() - => new InvalidOperationException( - $"Unable to determine the variant of this union, as '{nameof(Variant)}' is not representing a valid variant of this union: '{Variant}'. This could be either because the union itself was not initialized correctly, or due to a bug in the 'UnionsGenerator' that generated this union type. Please report an issue to the maintainer."); - -#endregion - -#region Factories - - public static IntDoubleString Create( - IntDoubleString value) => new(value); - - public static Boolean TryCreate( - IntDoubleString value, - [NotNullWhen(true)] out IntDoubleString? union) - { - union = Create(value); - - return true; - } - - public static IntDoubleString Create( - Int32 value) => new IntDoubleString(value, validate: true); - - public static Boolean TryCreate( - Int32 value, - [NotNullWhen(true)] out IntDoubleString? union) - { - var isValid = true; - Validate(value, throwIfInvalid: false, ref isValid); - union = isValid - ? new IntDoubleString(value, validate: false) - : default; - - return isValid; - } - - public static IntDoubleString Create( - Double value) => new IntDoubleString(value, validate: true); - - public static Boolean TryCreate( - Double value, - [NotNullWhen(true)] out IntDoubleString? union) - { - var isValid = true; - Validate(value, throwIfInvalid: false, ref isValid); - union = isValid - ? new IntDoubleString(value, validate: false) - : default; - - return isValid; - } - - public static IntDoubleString Create( - String value) => new IntDoubleString(value, validate: true); - - public static Boolean TryCreate( - String value, - [NotNullWhen(true)] out IntDoubleString? union) - { - var isValid = true; - Validate(value, throwIfInvalid: false, ref isValid); - union = isValid - ? new IntDoubleString(value, validate: false) - : default; - - return isValid; - } - - public static IntDoubleString Create( - T value) - => new Factory().Create(value); - - public static Boolean TryCreate( - T value, - [NotNullWhen(true)] out IntDoubleString? union) - => new Factory().TryCreate(value, out union); - -#endregion - -#region Mapping - - // this method is intended for inter-union conversion only - public TUnion MapTo( - TFactory factory) - where TFactory : IUnionFactory - { - var result = Variant switch - { - VariantKind.Int32 => factory.Create(__unmanagedVariantsContainer.Int32), - VariantKind.Double => factory.Create(__unmanagedVariantsContainer.Double), - VariantKind.String => factory.Create((String)__referenceVariantsContainer), - _ => throw CreateUnknownVariantException() - }; - - return result; - } - - // this method is intended for inter-union conversion only - public Boolean TryMapTo( - TFactory factory, - [NotNullWhen(true)] out TUnion? union) - where TFactory : IUnionFactory - { - var result = Variant switch - { - VariantKind.Int32 => factory.TryCreate(__unmanagedVariantsContainer.Int32, out union), - VariantKind.Double => factory.TryCreate(__unmanagedVariantsContainer.Double, out union), - VariantKind.String => factory.TryCreate((String)__referenceVariantsContainer, out union), - _ => throw CreateUnknownVariantException() - }; - - return result; - } - -#endregion - -#region Equality - - public override Boolean Equals( - Object? obj) - => obj is IntDoubleString union && Equals(union); - - public Boolean Equals( - IntDoubleString other) - { - // only emit for reference unions - if (ReferenceEquals(this, other)) - { - return true; - } - - if (Variant != other.Variant) - { - return false; - } - - var result = Variant switch - { - VariantKind.Int32 => - EqualityComparer.Default.Equals( - __unmanagedVariantsContainer.Int32, - other.__unmanagedVariantsContainer.Int32), - VariantKind.Double => - EqualityComparer.Default.Equals( - __unmanagedVariantsContainer.Double, - other.__unmanagedVariantsContainer.Double), - VariantKind.String => - EqualityComparer.Default.Equals( - (String)__referenceVariantsContainer, - (String)other.__referenceVariantsContainer), - _ => throw CreateUnknownVariantException() - }; - - return result; - } - - public override Int32 GetHashCode() - { - var result = Variant switch - { - VariantKind.Int32 => HashCode.Combine(Variant, __unmanagedVariantsContainer.Int32), - VariantKind.Double => HashCode.Combine(__unmanagedVariantsContainer.Double), - VariantKind.String => HashCode.Combine((String)__referenceVariantsContainer), - _ => throw CreateUnknownVariantException() - }; - - return result; - } - -#endregion - -#region Conversion - - public static implicit operator IntDoubleString(Int32 value) => Create(value); - public static explicit operator Int32(IntDoubleString union) => union.ToInt32; - - public static implicit operator IntDoubleString(Double value) => Create(value); - public static explicit operator Double(IntDoubleString union) => union.ToDouble; - - public static implicit operator IntDoubleString(String value) => Create(value); - public static explicit operator String(IntDoubleString union) => union.ToString; - -#endregion - -#region Interface Implementations - - // - // track required names signatures of all union members and implement conflicting interface - // members explicitly? - // alternatively, explicitly implement all interface members to avoid that complexity? - Int32 IComparable.CompareTo(object obj) - { - switch (Variant) - { - case VariantKind.Int32: - // check if variant is implementing interface explicitly and cast when needed - return __unmanagedVariantsContainer.Int32.CompareTo(obj); - case VariantKind.Double: - return __unmanagedVariantsContainer.Double.CompareTo(obj); - case VariantKind.String: - return ((string)__referenceVariantsContainer).CompareTo(obj); - default: - throw CreateUnknownVariantException(); - } - } - -#endregion -} - -public static partial class IntDoubleStringVariantGroupKindsOperations -{ - extension(IntDoubleString.VariantGroupKinds @this) - { - public Boolean HasFlag(IntDoubleString.VariantGroupKinds flag) => (@this & flag) == flag; - - public static ImmutableArray GetAllValues() => - [ - IntDoubleString.VariantGroupKinds.Number, - IntDoubleString.VariantGroupKinds.Integer, - IntDoubleString.VariantGroupKinds.ReferenceType, - IntDoubleString.VariantGroupKinds.ValueType - ]; - - public Int32 IndividualGroupCount => PopCount((UInt32)@this); - - private void GetIndividualGroups(Span buffer) - { - var count = @this.IndividualGroupCount; - - if (count is 0) - { - return; - } - - if (buffer.Length < count) - { - throw new ArgumentOutOfRangeException( - nameof(buffer), - $"{nameof(buffer)} did not have the required length of IndividualGroupCount. The required length was {count}, but the span provided had a length of {buffer.Length}."); - } - - if (count is 1) - { - buffer[0] = @this; - return; - } - - var groupIndex = 0; - for (var i = LeadingZeroCount((UInt32)@this) + 1; i < 33 && groupIndex < count; i++) - { - var flagPosition = 32 - i; - var flag = 1 << flagPosition; - if (((Int32)@this & flag) == flag) - { - buffer[groupIndex++] = (IntDoubleString.VariantGroupKinds)flag; - } - } - } - - public ImmutableArray GetIndividualGroups() - { - var groups = new IntDoubleString.VariantGroupKinds[@this.IndividualGroupCount]; - @this.GetIndividualGroups(groups); - var result = ImmutableCollectionsMarshal.AsImmutableArray(groups); - - return result; - } - - public String Name - { - get - { - var count = @this.IndividualGroupCount; - - if (count is 0 or 1) - { - return @this.DegenerateName; - } - - Span - groups = stackalloc IntDoubleString.VariantGroupKinds[count]; // Count never exceeds 32 - @this.GetIndividualGroups(groups); - var builder = new StringBuilder(); - for (var i = 0; i < count; i++) - { - var group = groups[i]; - var name = group.DegenerateName; - - if (i is not 0) - { - builder.Append(" | "); - } - - builder.Append(name); - } - - var result = builder.ToString(); - - return result; - } - } - - private String DegenerateName - { - get - { - return @this switch - { - IntDoubleString.VariantGroupKinds.None => nameof(IntDoubleString.VariantGroupKinds.None), - IntDoubleString.VariantGroupKinds.Number => nameof(IntDoubleString.VariantGroupKinds.Number), - IntDoubleString.VariantGroupKinds.Integer => nameof(IntDoubleString.VariantGroupKinds.Integer), - IntDoubleString.VariantGroupKinds.ReferenceType => nameof(IntDoubleString.VariantGroupKinds - .ReferenceType), - IntDoubleString.VariantGroupKinds.ValueType => nameof(IntDoubleString.VariantGroupKinds - .ValueType), - _ => throw new InvalidOperationException( - $"The VariantGroups instance was not initialized correctly and is holding an invalid value: {@this}") - }; - } - } - } - - private static ReadOnlySpan Log2DeBruijn => - [ - 00, 09, 01, 10, 13, 21, 02, 29, - 11, 14, 16, 18, 22, 25, 03, 30, - 08, 12, 20, 28, 15, 17, 24, 07, - 19, 27, 23, 06, 26, 05, 04, 31 - ]; - - private static Int32 LeadingZeroCount(UInt32 value) - { - if (value == 0) - { - return 32; - } - - value |= value >> 01; - value |= value >> 02; - value |= value >> 04; - value |= value >> 08; - value |= value >> 16; - - var result = 31 ^ Unsafe.AddByteOffset( - ref MemoryMarshal.GetReference(Log2DeBruijn), - (IntPtr)(Int32)((value * 0x07C4ACDDu) >> 27)); - - return result; - } - - private static Int32 PopCount(UInt32 value) - { - const UInt32 c1 = 0x_55555555u; - const UInt32 c2 = 0x_33333333u; - const UInt32 c3 = 0x_0F0F0F0Fu; - const UInt32 c4 = 0x_01010101u; - - value -= (value >> 1) & c1; - value = (value & c2) + ((value >> 2) & c2); - value = (((value + (value >> 4)) & c3) * c4) >> 24; - - return (Int32)value; - } -} - -public static partial class IntDoubleStringVariantKindsOperations -{ - extension(IntDoubleString.VariantKind @this) - { - public static ImmutableArray GetAllValues() => - [ - IntDoubleString.VariantKind.Int32, - IntDoubleString.VariantKind.Double, - IntDoubleString.VariantKind.String - ]; - - public String Name - { - get - { - var name = @this switch - { - IntDoubleString.VariantKind.Int32 => nameof(Int32), - IntDoubleString.VariantKind.Double => nameof(Double), - IntDoubleString.VariantKind.String => nameof(String), - _ => throw new InvalidOperationException( - "Unable to determine name, as the variant was not initialized correctly.") - }; - - return name; - } - } - - public Type Type - { - get - { - var type = @this switch - { - IntDoubleString.VariantKind.Int32 => typeof(Int32), - IntDoubleString.VariantKind.Double => typeof(Double), - IntDoubleString.VariantKind.String => typeof(String), - _ => throw new InvalidOperationException( - "Unable to determine type, as the variant was not initialized correctly.") - }; - - return type; - } - } - - public IntDoubleString.VariantGroupKinds Groups - { - get - { - var groups = @this switch - { - IntDoubleString.VariantKind.Int32 => IntDoubleString.VariantGroupKinds.Number | - IntDoubleString.VariantGroupKinds.Integer | - IntDoubleString.VariantGroupKinds.ValueType, - IntDoubleString.VariantKind.Double => IntDoubleString.VariantGroupKinds.Number | - IntDoubleString.VariantGroupKinds.ValueType, - IntDoubleString.VariantKind.String => IntDoubleString.VariantGroupKinds.ReferenceType, - _ => throw new InvalidOperationException( - "Unable to determine groups, as the variant was not initialized correctly.") - }; - - return groups; - } - } - } -} diff --git a/UnionsGenerator.TestLibrary/UnionsGenerator.TestLibrary.csproj b/UnionsGenerator.TestLibrary/UnionsGenerator.TestLibrary.csproj deleted file mode 100644 index d08f80d..0000000 --- a/UnionsGenerator.TestLibrary/UnionsGenerator.TestLibrary.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - netstandard2.0 - preview - enable - - false - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - diff --git a/UnionsGenerator.Tests/ConstructorTests.cs b/UnionsGenerator.Tests/ConstructorTests.cs deleted file mode 100644 index a227398..0000000 --- a/UnionsGenerator.Tests/ConstructorTests.cs +++ /dev/null @@ -1,88 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Tests; - -using System.Linq; - -public class ConstructorTests : TestBase -{ - [Fact] - public void GeneratesPrivateInterfaceAccessibilityForPublicIfInconvertible() => - TestUnionType( - """ - using RhoMicro.CodeAnalysis; - [UnionType] - [UnionType] - [UnionTypeSettings(ConstructorAccessibility = ConstructorAccessibilitySetting.PublicIfInconvertible)] - readonly partial struct IntOrString { } - """, - s => s.Constructors.All(c => - c.DeclaredAccessibility == Microsoft.CodeAnalysis.Accessibility.Private - || c.Parameters.Single().Type.TypeKind == Microsoft.CodeAnalysis.TypeKind.Interface)); - - [Fact] - public void GeneratesPrivateObjectAccessibilityForPublicIfInconvertible() => - TestUnionType( - """ - using RhoMicro.CodeAnalysis; - [UnionType] - [UnionType] - [UnionTypeSettings(ConstructorAccessibility = ConstructorAccessibilitySetting.PublicIfInconvertible)] - readonly partial struct IntOrString { } - """, - s => s.Constructors.All(c => - c.DeclaredAccessibility == Microsoft.CodeAnalysis.Accessibility.Private - || c.Parameters.Single().Type.SpecialType == Microsoft.CodeAnalysis.SpecialType.System_Object)); - - [Fact] - public void GeneratesPrivateSupertypeAccessibilityForPublicIfInconvertible() => - TestUnionType( - """ - using RhoMicro.CodeAnalysis; - class Supertype { } - [UnionType] - [UnionType] - [UnionTypeSettings(ConstructorAccessibility = ConstructorAccessibilitySetting.PublicIfInconvertible)] - partial class IntOrString : Supertype { } - """, - s => s.Constructors.All(c => - c.DeclaredAccessibility == Microsoft.CodeAnalysis.Accessibility.Private - || c.Parameters.Single().Type.Name == "Supertype"), - unionTypeName: "IntOrString"); - - [Fact] - public void GeneratesPrivateAccessibilityForPublicIfInconvertible() => - TestUnionType( - """ - using RhoMicro.CodeAnalysis; - [UnionType] - [UnionType] - [UnionTypeSettings(ConstructorAccessibility = ConstructorAccessibilitySetting.PublicIfInconvertible)] - readonly partial struct IntOrString { } - """, - s => s.Constructors.All(c => c.DeclaredAccessibility == Microsoft.CodeAnalysis.Accessibility.Private)); - - [Fact] - public void GeneratesPrivateAccessibilityForPrivate() => - TestUnionType( - """ - using RhoMicro.CodeAnalysis; - [UnionType] - [UnionType] - [UnionTypeSettings(ConstructorAccessibility = ConstructorAccessibilitySetting.Private)] - readonly partial struct IntOrString { } - """, - s => s.Constructors.All(c => c.DeclaredAccessibility == Microsoft.CodeAnalysis.Accessibility.Private)); - - [Fact] - public void GeneratesPublicAccessibilityForPublic() => - TestUnionType( - """ - using RhoMicro.CodeAnalysis; - [UnionType] - [UnionType] - [UnionTypeSettings(ConstructorAccessibility = ConstructorAccessibilitySetting.Public)] - readonly partial struct IntOrString { } - """, - s => s.Constructors.All(c => c.DeclaredAccessibility == Microsoft.CodeAnalysis.Accessibility.Public)); -} diff --git a/UnionsGenerator.Tests/ConversionTests.cs b/UnionsGenerator.Tests/ConversionTests.cs deleted file mode 100644 index ba671c0..0000000 --- a/UnionsGenerator.Tests/ConversionTests.cs +++ /dev/null @@ -1,213 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Tests; - -using System.Security.Cryptography.X509Certificates; - -using Microsoft.CodeAnalysis; - -public class ConversionTests : TestBase -{ - [Theory] - [InlineData( - """ - using RhoMicro.CodeAnalysis; - [UnionType] - readonly partial struct Union { } - """)] - [InlineData( - """ - using RhoMicro.CodeAnalysis; - [UnionType] - readonly partial struct Union { } - """)] - [InlineData( - """ - using RhoMicro.CodeAnalysis; - interface IInterface { } - [UnionType] - readonly partial struct Union { } - """)] - public void OmitsInterfaceConversions(String source) => - TestUnionType( - source, - t => {/*diagnostics due to supertype/interface conversions would fail*/ }, - unionTypeName: "Union"); - - [Theory] - [InlineData( - """ - using RhoMicro.CodeAnalysis; - using System; - [UnionType] - partial class Subset { } - [UnionType] - [UnionType] - [Relation] - partial class Union { } - """)] - public void SubsetConversions(String source) - { - AssertConversionOperator( - source, - "Union", - toPredicate: SymbolEqualityComparer.Default.Equals, - fromPredicate: (parameter, union) => parameter.Name == "Subset", - opName: WellKnownMemberNames.ImplicitConversionName); - - AssertConversionOperator( - source, - "Union", - toPredicate: (parameter, union) => parameter.Name == "Subset", - fromPredicate: SymbolEqualityComparer.Default.Equals, - opName: WellKnownMemberNames.ExplicitConversionName); - } - - [Theory] - [InlineData( - """ - using RhoMicro.CodeAnalysis; - using System; - [UnionType] - [UnionType] - [UnionType] - partial class Superset { } - [UnionType] - [UnionType] - [Relation] - partial class Union { } - """)] - public void SupersetConversions(String source) - { - AssertConversionOperator( - source, - "Union", - toPredicate: SymbolEqualityComparer.Default.Equals, - fromPredicate: (parameter, union) => parameter.Name == "Superset", - opName: WellKnownMemberNames.ExplicitConversionName); - - AssertConversionOperator( - source, - "Union", - toPredicate: (parameter, union) => parameter.Name == "Superset", - fromPredicate: SymbolEqualityComparer.Default.Equals, - opName: WellKnownMemberNames.ImplicitConversionName); - } - - [Theory] - [InlineData( - """ - using RhoMicro.CodeAnalysis; - using System; - [UnionType] - [UnionType] - partial class Congruent { } - [UnionType] - [UnionType] - [Relation] - partial class Union { } - """)] - public void CongruentConversions(String source) - { - AssertConversionOperator( - source, - "Union", - toPredicate: SymbolEqualityComparer.Default.Equals, - fromPredicate: (parameter, union) => parameter.Name == "Congruent", - opName: WellKnownMemberNames.ImplicitConversionName); - - AssertConversionOperator( - source, - "Union", - toPredicate: (parameter, union) => parameter.Name == "Congruent", - fromPredicate: SymbolEqualityComparer.Default.Equals, - opName: WellKnownMemberNames.ImplicitConversionName); - } - - [Theory] - [InlineData( - """ - using RhoMicro.CodeAnalysis; - using System; - [UnionType] - [UnionType] - partial class Intersection { } - [UnionType] - [UnionType] - [Relation] - partial class Union { } - """)] - public void IntersectionConversions(String source) - { - AssertConversionOperator( - source, - "Union", - toPredicate: SymbolEqualityComparer.Default.Equals, - fromPredicate: (parameter, union) => parameter.Name == "Intersection", - opName: WellKnownMemberNames.ExplicitConversionName); - - AssertConversionOperator( - source, - "Union", - toPredicate: (parameter, union) => parameter.Name == "Intersection", - fromPredicate: SymbolEqualityComparer.Default.Equals, - opName: WellKnownMemberNames.ExplicitConversionName); - } - - [Theory] - [InlineData( - """ - using RhoMicro.CodeAnalysis; - using System; - [UnionType] - partial class Disjunct { } - [UnionType] - [UnionType] - [Relation] - partial class Union { } - """)] - public void DisjunctConversions(String source) - { - AssertConversionOperator( - source, - "Union", - toPredicate: SymbolEqualityComparer.Default.Equals, - fromPredicate: (parameter, union) => parameter.Name == "Disjunct", - opName: WellKnownMemberNames.ExplicitConversionName, - assertExists: false); - - AssertConversionOperator( - source, - "Union", - toPredicate: (parameter, union) => parameter.Name == "Disjunct", - fromPredicate: SymbolEqualityComparer.Default.Equals, - opName: WellKnownMemberNames.ExplicitConversionName, - assertExists: false); - } - - private void AssertConversionOperator( - String source, - String unionTypeName, - Func toPredicate, - Func fromPredicate, - String opName, - Boolean assertExists = true) => - TestUnionType( - source, - t => - { - var exists = t.GetMembers() - .OfType() - .One(m => - m.IsStatic - && m.MethodKind == MethodKind.Conversion - && m.Parameters.Length == 1 - && toPredicate.Invoke(m.ReturnType, t) - && fromPredicate.Invoke(m.Parameters[0].Type, t) - && m.DeclaredAccessibility == Accessibility.Public - && ( m.Name == opName || !assertExists )); - - Assert.Equal(exists, assertExists); - }, - unionTypeName); -} diff --git a/UnionsGenerator.Tests/DiagnosticsLevelTests.cs b/UnionsGenerator.Tests/DiagnosticsLevelTests.cs deleted file mode 100644 index 5ff54dc..0000000 --- a/UnionsGenerator.Tests/DiagnosticsLevelTests.cs +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Tests; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using Basic.Reference.Assemblies; - -using Microsoft.CodeAnalysis; - -public class DiagnosticsLevelTests : TestBase -{ - public DiagnosticsLevelTests() : base(Net80.References.All) { } - - [Theory] - [InlineData( - """ - using RhoMicro.CodeAnalysis; - using System; - - [UnionType(Storage = StorageOption.Value)] - [UnionTypeSettings(Miscellaneous = MiscellaneousSettings.EmitStructuralRepresentation, DiagnosticsLevel = DiagnosticsLevelSettings.Error)] - readonly partial struct GetUserResult { } - - sealed record User(String Name) { } - - enum ErrorCode - { - NotFound, - Unauthorized - } - - readonly record struct MultipleUsersError(Int32 Count) { } - """, "GetUserResult")] - public void OmitsWarningDiagnostics(String source, String unionTypeName) => - TestDiagnostics(source, async compilation => - { - var diagnostics = await compilation.GetAnalyzerDiagnosticsAsync(); - Assert.All(diagnostics, d => Assert.Equal(DiagnosticSeverity.Error, d.Severity)); - }); -} diff --git a/UnionsGenerator.Tests/EnumerableExtensions.cs b/UnionsGenerator.Tests/EnumerableExtensions.cs deleted file mode 100644 index 5283a08..0000000 --- a/UnionsGenerator.Tests/EnumerableExtensions.cs +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Tests; - -using Microsoft.CodeAnalysis; - -internal static class EnumerableExtensions -{ - public static Boolean None(this IEnumerable enumeration) - => !enumeration.Any(); - public static Boolean None(this IEnumerable enumeration, Func predicate) - => !enumeration.Any(predicate); - public static Boolean One(this IEnumerable enumeration, Func predicate) - => enumeration.Where(predicate).Count() == 1; -} diff --git a/UnionsGenerator.Tests/JsonConverterTests.cs b/UnionsGenerator.Tests/JsonConverterTests.cs deleted file mode 100644 index dbeb702..0000000 --- a/UnionsGenerator.Tests/JsonConverterTests.cs +++ /dev/null @@ -1,118 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Tests; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using Basic.Reference.Assemblies; - -using Microsoft.CodeAnalysis; - -public class JsonConverterTests() : TestBase(Net80.References.All) -{ - [Theory] - [InlineData( - """ - using RhoMicro.CodeAnalysis; - using System; - - [UnionType] - partial class Union { } - """)] - [InlineData( - """ - using RhoMicro.CodeAnalysis; - using System; - - [UnionType] - [UnionTypeSettings(Miscellaneous = MiscellaneousSettings.Default)] - partial class Union { } - """)] - [InlineData( - """ - using RhoMicro.CodeAnalysis; - using System; - - [UnionType] - [UnionTypeSettings(Miscellaneous = MiscellaneousSettings.None)] - partial class Union { } - """)] - [InlineData( - """ - using RhoMicro.CodeAnalysis; - using System; - - [UnionType] - [UnionTypeSettings] - partial class Union { } - """)] - [InlineData( - """ - using RhoMicro.CodeAnalysis; - using System; - - [assembly: UnionTypeSettings(Miscellaneous = MiscellaneousSettings.None)] - - [UnionType] - partial class Union { } - """)] - [InlineData( - """ - using RhoMicro.CodeAnalysis; - using System; - - [assembly: UnionTypeSettings(Miscellaneous = MiscellaneousSettings.Default)] - - [UnionType] - partial class Union { } - """)] - [InlineData( - """ - using RhoMicro.CodeAnalysis; - using System; - - [assembly: UnionTypeSettings] - - [UnionType] - partial class Union { } - """)] - public void OmitsJsonConverter(String source) => - TestUnionType(source, t => - { - var omitted = t.GetTypeMembers().None(t => - t.Name == "JsonConverter"); - - Assert.True(omitted); - }); - [Theory] - [InlineData( - """ - using RhoMicro.CodeAnalysis; - using System; - - [UnionType] - [UnionTypeSettings(Miscellaneous = MiscellaneousSettings.GenerateJsonConverter)] - partial class Union { } - """)] - [InlineData( - """ - using RhoMicro.CodeAnalysis; - using System; - - [assembly: UnionTypeSettings(Miscellaneous = MiscellaneousSettings.GenerateJsonConverter)] - - [UnionType] - partial class Union { } - """)] - public void IncludesJsonConverter(String source) => - TestUnionType(source, t => - { - var included = t.GetTypeMembers().One(t => - t.Name == "JsonConverter"); - - Assert.True(included); - }); -} diff --git a/UnionsGenerator.Tests/NullableTests.cs b/UnionsGenerator.Tests/NullableTests.cs deleted file mode 100644 index 32a22e4..0000000 --- a/UnionsGenerator.Tests/NullableTests.cs +++ /dev/null @@ -1,123 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Tests; -using System; -using System.Linq; - -using Microsoft.CodeAnalysis; - -public class NullableTests : TestBase -{ - [Fact] - public void AllowsForNullableValueType() - { - TestUnionType( - """ - using System; - using RhoMicro.CodeAnalysis; - - [UnionType>] - partial struct NullableBoolUnion { } - """, - s => { }); - } - - [Theory] - [InlineData(new[] { "Foo", "Bar" }, new String[] { }, "IsFoo", true, "AsFoo")] - [InlineData(new[] { "Foo", "Bar" }, new String[] { }, "IsBar", true, "AsBar")] - [InlineData(new[] { "Foo", "Bar" }, new String[] { }, "IsFoo", false, "AsBar")] - [InlineData(new[] { "Foo", "Bar" }, new String[] { }, "IsBar", false, "AsFoo")] - - [InlineData(new[] { "Foo" }, new String[] { "Bar" }, "IsFoo", true, "AsFoo")] - [InlineData(new[] { "Foo" }, new String[] { "Bar" }, "IsBar", true)] - [InlineData(new[] { "Foo" }, new String[] { "Bar" }, "IsFoo", false)] - [InlineData(new[] { "Foo" }, new String[] { "Bar" }, "IsBar", false, "AsFoo")] - - [InlineData(new String[] { }, new String[] { "Foo", "Bar" }, "IsFoo", true)] - [InlineData(new String[] { }, new String[] { "Foo", "Bar" }, "IsBar", true)] - [InlineData(new String[] { }, new String[] { "Foo", "Bar" }, "IsFoo", false)] - [InlineData(new String[] { }, new String[] { "Foo", "Bar" }, "IsBar", false)] - - [InlineData(new[] { "Foo", "Bar", "Baz" }, new String[] { }, "IsFoo", true, "AsFoo")] - [InlineData(new[] { "Foo", "Bar", "Baz" }, new String[] { }, "IsBar", true, "AsBar")] - [InlineData(new[] { "Foo", "Bar", "Baz" }, new String[] { }, "IsBaz", true, "AsBaz")] - [InlineData(new[] { "Foo", "Bar", "Baz" }, new String[] { }, "IsFoo", false)] - [InlineData(new[] { "Foo", "Bar", "Baz" }, new String[] { }, "IsBar", false)] - [InlineData(new[] { "Foo", "Bar", "Baz" }, new String[] { }, "IsBaz", false)] - - [InlineData(new[] { "Foo", "Bar" }, new String[] { "Baz" }, "IsFoo", true, "AsFoo")] - [InlineData(new[] { "Foo", "Bar" }, new String[] { "Baz" }, "IsBar", true, "AsBar")] - [InlineData(new[] { "Foo", "Bar" }, new String[] { "Baz" }, "IsBaz", true)] - [InlineData(new[] { "Foo", "Bar" }, new String[] { "Baz" }, "IsFoo", false)] - [InlineData(new[] { "Foo", "Bar" }, new String[] { "Baz" }, "IsBar", false)] - [InlineData(new[] { "Foo", "Bar" }, new String[] { "Baz" }, "IsBaz", false)] - - [InlineData(new[] { "Foo" }, new String[] { "Baz", "Bar" }, "IsFoo", true, "AsFoo")] - [InlineData(new[] { "Foo" }, new String[] { "Baz", "Bar" }, "IsBar", true)] - [InlineData(new[] { "Foo" }, new String[] { "Baz", "Bar" }, "IsBaz", true)] - [InlineData(new[] { "Foo" }, new String[] { "Baz", "Bar" }, "IsFoo", false)] - [InlineData(new[] { "Foo" }, new String[] { "Baz", "Bar" }, "IsBar", false)] - [InlineData(new[] { "Foo" }, new String[] { "Baz", "Bar" }, "IsBaz", false)] - - [InlineData(new String[] { }, new String[] { "Foo", "Bar", "Baz" }, "IsFoo", true)] - [InlineData(new String[] { }, new String[] { "Foo", "Bar", "Baz" }, "IsBar", true)] - [InlineData(new String[] { }, new String[] { "Foo", "Bar", "Baz" }, "IsBaz", true)] - [InlineData(new String[] { }, new String[] { "Foo", "Bar", "Baz" }, "IsFoo", false)] - [InlineData(new String[] { }, new String[] { "Foo", "Bar", "Baz" }, "IsBar", false)] - [InlineData(new String[] { }, new String[] { "Foo", "Bar", "Baz" }, "IsBaz", false)] - public void AnnotatesWithMemberNotNullWhenAttribute( - String[] classNames, - String[] structNames, - String propertyName, - Boolean expectedCondition, - params String[] expectedMemberNames) - { - TestUnionType( - $$""" - using RhoMicro.CodeAnalysis; - - {{String.Join('\n', classNames.Select(n => $"class {n.TrimEnd('?')} {{}}"))}} - {{String.Join('\n', structNames.Select(n => $"struct {n} {{}}"))}} - - [UnionType<{{String.Join(',', structNames.Concat(classNames.Select(n => n.TrimEnd('?'))))}}>] - partial struct Union { } - """, - s => - { - var actualMatch = s.GetMembers() - .OfType() - .Single(p => p.Name == propertyName) - .GetAttributes() - .Where(a => - a.AttributeClass?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == - "global::System.Diagnostics.CodeAnalysis.MemberNotNullWhenAttribute") - .Select(a => a.ConstructorArguments) - .Select(args => - { - var result = args.Length == expectedMemberNames.Length + 1 - && args[0] is - { - Kind: TypedConstantKind.Primitive, - Value: Boolean actualCondition - } - && actualCondition == expectedCondition - && args.Skip(1) - .Zip(expectedMemberNames) - .All(t => t.First is - { - Kind: TypedConstantKind.Primitive, - Value: String actualMemberName - } && actualMemberName == t.Second); - - return result; - }) - .Where(v => v) - .SingleOrDefault(); - - var expectedMatch = expectedMemberNames is [.., { }]; - - Assert.Equal(expectedMatch, actualMatch); - }, - "Union"); - } -} diff --git a/UnionsGenerator.Tests/TestBase.cs b/UnionsGenerator.Tests/TestBase.cs deleted file mode 100644 index dce6629..0000000 --- a/UnionsGenerator.Tests/TestBase.cs +++ /dev/null @@ -1,212 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Tests; - -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -using Basic.Reference.Assemblies; - -using RhoMicro.CodeAnalysis.UnionsGenerator.Generators; - -using Microsoft.CodeAnalysis.Diagnostics; - -using System.Collections.Immutable; - -/// -/// Base class for tests verifying outputs. -/// -public abstract class TestBase -{ - protected TestBase() : this(NetStandard20.References.All.ToArray()) { } - - protected TestBase(IEnumerable references) => - _references = [.. references]; - - private readonly MetadataReference[] _references; - - //requiring >= C#11 due to file scoped modifiers - private const LanguageVersion _targetLanguageVersion = LanguageVersion.CSharp11; - - private static readonly CSharpParseOptions _parseOptions = - new(languageVersion: _targetLanguageVersion, - documentationMode: DocumentationMode.Diagnose, - kind: SourceCodeKind.Regular); - - /// - /// Invokes an assertion on the union type implementation generated from a source. - /// - /// - /// - /// - public void TestUnionType(String source, Action assertion, String? unionTypeName = null) - { - _ = assertion ?? throw new ArgumentNullException(nameof(assertion)); - - Compilation compilation = CreateCompilation(source, out var sourceTree); - _ = RunGenerator(ref compilation); - var declaration = sourceTree.GetRoot() - .DescendantNodesAndSelf() - .OfType() - .SingleOrDefault(d => unionTypeName == null || d.Identifier.Text == unionTypeName); - Assert.NotNull(declaration); - var symbol = compilation.GetSemanticModel(sourceTree) - .GetDeclaredSymbol(declaration); - Assert.NotNull(symbol); - assertion.Invoke(symbol!); - } - - /// - /// Invokes an assertion on the result of running the generator once on a source. - /// - /// - /// - public void TestDriverResult(String source, Action assertion) - { - _ = assertion ?? throw new ArgumentNullException(nameof(assertion)); - - Compilation compilation = CreateCompilation(source, out var _); - var result = RunGenerator(ref compilation); - assertion.Invoke(result); - } - - public Task TestDiagnostics(String source, Func assertion) - { - _ = assertion ?? throw new ArgumentNullException(nameof(assertion)); - - var compilation = CreateCompilation(source, out _); - var compilationWithDiagnostics = AttachAnalyzer(compilation); - return assertion.Invoke(compilationWithDiagnostics); - } - - private CompilationWithAnalyzers AttachAnalyzer(Compilation compilation) - { - var result = compilation.WithAnalyzers([(DiagnosticAnalyzer)new Analyzers.Analyzer()]); - - return result; - } - - private GeneratorDriverRunResult RunGenerator(ref Compilation compilation) - { - var generator = new UnionsGenerator(); - - var driver = CSharpGeneratorDriver.Create(generator) - .WithUpdatedParseOptions(_parseOptions); - - // Run the generation pass - // (Note: the generator driver itself is immutable, and all calls return an updated version of the driver that you should use for subsequent calls) - driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out compilation, out var diagnostics); - - // We can now assert things about the resulting compilation: - Assert.Empty(diagnostics); // there were no diagnostics created by the generators - var aggregateDiagnostics = compilation.GetDiagnostics(); - Assert.Empty(aggregateDiagnostics); // verify the compilation with the added source has no diagnostics - - // Or we can look at the results directly: - var result = driver.GetRunResult(); - - // The runResult contains the combined results of all generators passed to the driver - Assert.Empty(result.Diagnostics); - - return result; - } - - private CSharpCompilation CreateCompilation(String source, out SyntaxTree sourceTree) - { - var options = CreateCompilationOptions(); - sourceTree = CSharpSyntaxTree.ParseText(source, _parseOptions); - var attributeTree = CSharpSyntaxTree.ParseText( - """ - // - #pragma warning disable - #nullable enable annotations - - // Licensed to the .NET Foundation under one or more agreements. - // The .NET Foundation licenses this file to you under the MIT license. - - namespace System.Diagnostics.CodeAnalysis - { - /// - /// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. - /// - [global::System.AttributeUsage(global::System.AttributeTargets.Parameter, Inherited = false)] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal sealed class NotNullWhenAttribute : global::System.Attribute - { - /// - /// Initializes the attribute with the specified return value condition. - /// - /// The return value condition. If the method returns this value, the associated parameter will not be null. - public NotNullWhenAttribute(bool returnValue) - { - ReturnValue = returnValue; - } - - /// Gets the return value condition. - public bool ReturnValue { get; } - } - [global::System.AttributeUsage( - global::System.AttributeTargets.Method | - global::System.AttributeTargets.Property, - Inherited = false, AllowMultiple = true)] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - internal sealed class MemberNotNullWhenAttribute : global::System.Attribute - { - /// - /// Initializes the attribute with the specified return value condition and a field or property member. - /// - /// The return value condition. If the method returns this value, the associated parameter will not be null. - /// The field or property member that is promised to be not-null. - public MemberNotNullWhenAttribute(bool returnValue, string member) - { - ReturnValue = returnValue; - Members = new[] { member }; - } - - /// - /// Initializes the attribute with the specified return value condition and list of field and property members. - /// - /// The return value condition. If the method returns this value, the associated parameter will not be null. - /// The list of field and property members that are promised to be not-null. - public MemberNotNullWhenAttribute(bool returnValue, params string[] members) - { - ReturnValue = returnValue; - Members = members; - } - - /// - /// Gets the return value condition. - /// - public bool ReturnValue { get; } - - /// - /// Gets field or property member names. - /// - public string[] Members { get; } - } - } - """, _parseOptions); - - var result = CSharpCompilation.Create( - assemblyName: null, - syntaxTrees: [sourceTree, attributeTree], - references: [.. _references], - options: options); - - return result; - } - - private static CSharpCompilationOptions CreateCompilationOptions() - { - String[] args = ["/warnaserror"]; -#pragma warning disable RS1035 // Do not use APIs banned for analyzers (not an analyzer????) - var commandLineArguments = CSharpCommandLineParser.Default.Parse(args, - baseDirectory: Environment.CurrentDirectory, sdkDirectory: Environment.CurrentDirectory); -#pragma warning restore RS1035 // Do not use APIs banned for analyzers - var result = commandLineArguments.CompilationOptions - .WithOutputKind(OutputKind.DynamicallyLinkedLibrary); - - return result; - } -} diff --git a/UnionsGenerator.Tests/UnionsGenerator.Tests.csproj b/UnionsGenerator.Tests/UnionsGenerator.Tests.csproj deleted file mode 100644 index 99e1af2..0000000 --- a/UnionsGenerator.Tests/UnionsGenerator.Tests.csproj +++ /dev/null @@ -1,33 +0,0 @@ - - - - net8.0 - RhoMicro.CodeAnalysis.UnionsGenerator.Tests - false - true - - - - $(DefineConstants);UNIONS_GENERATOR - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - diff --git a/UnionsGenerator/Analyzers/Analyzer.cs b/UnionsGenerator/Analyzers/Analyzer.cs deleted file mode 100644 index 5bcebe8..0000000 --- a/UnionsGenerator/Analyzers/Analyzer.cs +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Analyzers; - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -using RhoMicro.CodeAnalysis.Library; -using RhoMicro.CodeAnalysis.UnionsGenerator.Models; -using RhoMicro.CodeAnalysis.UnionsGenerator.Utils; - -using System.Collections.Immutable; - -[DiagnosticAnalyzer(LanguageNames.CSharp)] -public sealed class Analyzer : DiagnosticAnalyzer -{ - public override void Initialize(AnalysisContext context) - { - _ = context ?? throw new ArgumentNullException(nameof(context)); - - context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); - context.EnableConcurrentExecution(); - context.RegisterSymbolStartAction(ctx => - { - ctx.CancellationToken.ThrowIfCancellationRequested(); - if(ctx is not { IsGeneratedCode: false, Symbol: INamedTypeSymbol target } || !Qualifications.IsUnionTypeSymbol(target, ctx.CancellationToken)) - return; - - ctx.RegisterSymbolEndAction(ctx => - { - ctx.CancellationToken.ThrowIfCancellationRequested(); - if(ctx is not { IsGeneratedCode: false, Symbol: INamedTypeSymbol target } || !Qualifications.IsUnionTypeSymbol(target, ctx.CancellationToken)) - return; - - var model = UnionTypeModel.Create(target, ctx.CancellationToken); - - if(model.Settings.DiagnosticsLevel == DiagnosticsLevelSettings.None) - return; - - var accumulator = DiagnosticsAccumulator.Create(model) - .DiagnoseNonHiddenSeverities(); - - if(model.Settings.DiagnosticsLevel != DiagnosticsLevelSettings.None) - { - if(model.Settings.DiagnosticsLevel.HasFlag(DiagnosticsLevelSettings.Info)) - { - accumulator = accumulator.ReportSeverity(DiagnosticSeverity.Info); - } - - if(model.Settings.DiagnosticsLevel.HasFlag(DiagnosticsLevelSettings.Warning)) - { - accumulator = accumulator.ReportSeverity(DiagnosticSeverity.Warning); - } - - if(model.Settings.DiagnosticsLevel.HasFlag(DiagnosticsLevelSettings.Error)) - { - accumulator = accumulator.ReportSeverity(DiagnosticSeverity.Error); - } - } - - accumulator.Receive(Providers.All, ctx.CancellationToken) - .ReportDiagnostics(ctx.ReportDiagnostic); - }); - }, SymbolKind.NamedType); - } - - public override ImmutableArray SupportedDiagnostics { get; } = Diagnostics.Descriptors.ToImmutableArray(); -} diff --git a/UnionsGenerator/Analyzers/Diagnostics.cs b/UnionsGenerator/Analyzers/Diagnostics.cs deleted file mode 100644 index 879964e..0000000 --- a/UnionsGenerator/Analyzers/Diagnostics.cs +++ /dev/null @@ -1,99 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Analyzers; - -using Microsoft.CodeAnalysis; - -using System; - -internal static partial class Diagnostics -{ - private const String _category = "RhoMicro.CodeAnalysis.UnionsGenerator"; - - //IMPORTANT: append new values at the end to preserve error codes - private enum Id - { - DuplicateUnionTypeAttributes = 1, - GeneratorException = 2, - //MissingUnionTypeAttribute = 3, - //NonPartialDeclaration = 4, - StaticTarget = 5, - RecordTarget = 6, - TooManyTypes = 7, - ImplicitConversionOptionOnNonSolitary = 8, - //ImplicitConversionOptionOnSolitary = 9, - InvalidAttributeTarget = 10, - AliasCollision = 11, - UnionTypeSettingsOnNonUnionType = 12, - RepresentableTypeIsSupertype = 13, - RepresentableTypeIsInterface = 14, - ReservedGenericParameterName = 15, - //UnknownGenericParameterName = 16, - PossibleBoxingStrategy = 17, - BoxingStrategy = 18, - PossibleTleStrategy = 19, - TleStrategy = 20, - SmallGenericUnion = 21, - GenericViolationStrategy = 22, - GenericRelation = 23, - BidirectionalRelation = 24, - DuplicateRelation = 25, - SelfReference = 26, - Inheritance = 27, - NullableOptionOnValueType = 28 - } - public static Diagnostic NullableOptionOnValueType(Location location, String alias) => - Create(Id.NullableOptionOnValueType, location, alias); - public static Diagnostic Inheritance(Location location) => - Create(Id.Inheritance, location); - public static Diagnostic SelfReference(Location location) => - Create(Id.SelfReference, location); - public static Diagnostic DuplicateRelation(Location location, String relationName) => - Create(Id.DuplicateRelation, location, relationName); - public static Diagnostic BidirectionalRelation(Location location, String relationName) => - Create(Id.BidirectionalRelation, location, relationName); - public static Diagnostic GenericRelation(Location location) => - Create(Id.GenericRelation, location); - public static Diagnostic GenericViolationStrategy(Location location, String typeName) => - Create(Id.GenericViolationStrategy, location, typeName); - public static Diagnostic SmallGenericUnion(Location location) => - Create(Id.SmallGenericUnion, location); - public static Diagnostic PossibleBoxingStrategy(Location location, String typeName) => - Create(Id.PossibleBoxingStrategy, location, typeName); - public static Diagnostic BoxingStrategy(Location location, String typeName) => - Create(Id.BoxingStrategy, location, typeName); - public static Diagnostic PossibleTleStrategy(Location location, String typeName) => - Create(Id.PossibleTleStrategy, location, typeName); - public static Diagnostic TleStrategy(Location location, String typeName) => - Create(Id.TleStrategy, location, typeName); - //public static Diagnostic UnknownGenericParameterName(Location location, String name) => - // Create(Id.UnknownGenericParameterName, location, name); - public static Diagnostic ReservedGenericParameterName(Location location, String name) => - Create(Id.ReservedGenericParameterName, location, name); - public static Diagnostic RepresentableTypeIsInterface(Location location, String representableTypeName) => - Create(Id.RepresentableTypeIsInterface, location, representableTypeName); - public static Diagnostic RepresentableTypeIsSupertype(Location location, String representableTypeName) => - Create(Id.RepresentableTypeIsSupertype, location, representableTypeName); - public static Diagnostic UnionTypeSettingsOnNonUnionType(Location location) => - Create(Id.UnionTypeSettingsOnNonUnionType, location); - public static Diagnostic AliasCollision(Location location, String representableTypeName) => - Create(Id.AliasCollision, location, representableTypeName); - public static Diagnostic InvalidAttributeTarget(Location location) => - Create(Id.InvalidAttributeTarget, location); - public static Diagnostic TooManyTypes(Location location) => - Create(Id.TooManyTypes, location); - public static Diagnostic StaticTarget(Location location) => - Create(Id.StaticTarget, location); - public static Diagnostic RecordTarget(Location location) => - Create(Id.RecordTarget, location); - //public static Diagnostic NonPartialDeclaration(Location location) => - // Create(Id.NonPartialDeclaration, location); - public static Diagnostic GeneratorException(Exception exception) => - Create(Id.GeneratorException, Location.None, exception.Message); - public static Diagnostic DuplicateUnionTypeAttributes(Location location, String unionTypeName) => - Create(Id.DuplicateUnionTypeAttributes, location, unionTypeName); - public static Diagnostic ImplicitConversionOptionOnNonSolitary(Location location) => - Create(Id.ImplicitConversionOptionOnNonSolitary, location); - //public static Diagnostic ImplicitConversionOptionOnSolitary(String unionTypeName, String representableTypeName, Location location) => - // Create(Id.ImplicitConversionOptionOnSolitary, location, $"ISuperset<{representableTypeName}, {unionTypeName}>"); -} diff --git a/UnionsGenerator/Analyzers/DiagnosticsStrings.cs b/UnionsGenerator/Analyzers/DiagnosticsStrings.cs deleted file mode 100644 index 820e49d..0000000 --- a/UnionsGenerator/Analyzers/DiagnosticsStrings.cs +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Analyzers; - -using Microsoft.CodeAnalysis; - -using RhoMicro.CodeAnalysis.UnionsGenerator.Utils; - -using System; -using System.Collections.Generic; -using System.Linq; - -internal static partial class Diagnostics -{ - private readonly struct DiagnosticInfo(String title, String message, DiagnosticSeverity severity) : IEquatable - { - public readonly String Title = title; - public readonly String Message = message; - public readonly DiagnosticSeverity Severity = severity; - - public override Boolean Equals(Object? obj) => obj is DiagnosticInfo item && Equals(item); - public Boolean Equals(DiagnosticInfo other) => Title == other.Title && Message == other.Message && Severity == other.Severity; - - public override Int32 GetHashCode() - { - var hashCode = -940627307; - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Title); - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Message); - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Severity); - return hashCode; - } - - public static Boolean operator ==(DiagnosticInfo left, DiagnosticInfo right) => left.Equals(right); - public static Boolean operator !=(DiagnosticInfo left, DiagnosticInfo right) => !( left == right ); - } - - private static readonly IReadOnlyDictionary _infos = new Dictionary() - { -{Id.AliasCollision , new DiagnosticInfo(severity: DiagnosticSeverity.Error,message:"An alias collision has been detected for the representable type `{0}`. Make sure that for colliding type names (excluding generic arguments) you provide a unique alias. If no explicit alias is provided, the simple type name of the representable type will be used, e.g.: `List` will be used for variables of the `List` type. Aliae are used for parameter, variable, enum and field names.",title:"Alias Collision")}, -{Id.BoxingStrategy , new DiagnosticInfo(severity: DiagnosticSeverity.Warning,message:"Boxing will occur for the representable type {0}. Consider using a different storage option.",title:"Boxing Storage Detected")}, -{Id.DuplicateUnionTypeAttributes , new DiagnosticInfo(severity: DiagnosticSeverity.Error,message:"Union types may not be declared using duplicate 'UnionType' attributes of the same representable type ({0}).",title:"Duplicate Union Type Declaration")}, -{Id.TooManyTypes, new DiagnosticInfo(severity: DiagnosticSeverity.Error,message:$"Union types may represent no more that {Qualifications.MaxRepresentableTypesCount} types.",title:"Too many Union Types")}, -{Id.GeneratorException , new DiagnosticInfo(severity: DiagnosticSeverity.Error,message:"The generator encountered an unexpected error: {0}",title:"Unexpected Generator Error")}, -{Id.GenericViolationStrategy , new DiagnosticInfo(severity: DiagnosticSeverity.Warning,message:"The selected storage option for {0} was ignored because the target union type is generic.",title:"Generic Storage Violation Detected")}, -{Id.ImplicitConversionOptionOnNonSolitary , new DiagnosticInfo(severity: DiagnosticSeverity.Warning,message:"The `ImplicitConversionIfSolitary` option will be ignored because the target union type may represent more than one type.",title:"Union Type Option Ignored")}, -//{Id.ImplicitConversionOptionOnSolitary , new DiagnosticInfo(severity: DiagnosticSeverity.Info,message:"The interface implementation for `{0}` was omitted because the `ImplicitConversionIfSolitary` option was used.",title:"Omitting Interface Implementation")}, -{Id.InvalidAttributeTarget , new DiagnosticInfo(severity: DiagnosticSeverity.Error,message:"Only type declarations may be annotated with union type attributes.",title:"Invalid Attribute Target")}, -//{Id.MissingUnionTypeAttribute , new DiagnosticInfo(severity: DiagnosticSeverity.Error,message:"Union types must be declared using at least one instance of the 'UnionType' attribute.",title:"Missing 'UnionType' Attribute")}, -//{Id.NonPartialDeclaration , new DiagnosticInfo(severity: DiagnosticSeverity.Error,message:"Union types must be declared using the 'partial' keyword.",title:"Nonpartial Union Declaration")}, -{Id.PossibleBoxingStrategy , new DiagnosticInfo(severity: DiagnosticSeverity.Warning,message:"Boxing may occur for the representable type {0}. Consider constraining it to either `class` or `struct` in order to help the generator emit an efficient implementation. Alternatively, use the `StorageOption.Field` option to generate dedicated field for the type.",title:"Possible Boxing Storage Detected")}, -{Id.PossibleTleStrategy , new DiagnosticInfo(severity: DiagnosticSeverity.Warning,message:"The selected storage option for {0} was ignored because it could cause a `TypeLoadException` to be thrown.",title:"Possible TypeLoadException Detected")}, -{Id.RecordTarget , new DiagnosticInfo(severity: DiagnosticSeverity.Error,message:"Union types may not be declared as 'record' types.",title:"Record Union Type Declaration")}, -{Id.RepresentableTypeIsInterface , new DiagnosticInfo(severity: DiagnosticSeverity.Info,message:"No conversion operators will be generated for the representable type {0} because it is an interface.",title:"Conversion Operators Omitted")}, -{Id.RepresentableTypeIsSupertype , new DiagnosticInfo(severity: DiagnosticSeverity.Info,message:"No conversion operators will be generated for the representable type {0} because it is a supertype of the target union type.",title:"Conversion Operators Omitted")}, -{Id.ReservedGenericParameterName , new DiagnosticInfo(severity: DiagnosticSeverity.Error,message:"The targeted union type contains a generic parameter named `{0}` which is reserved for generated code. Either change its name or use the `UnionTypeSettings` attribute to set generated generic type parameter names.",title:"Reserved Generic Parameter Name")}, -{Id.SmallGenericUnion , new DiagnosticInfo(severity: DiagnosticSeverity.Info,message:"The small layout setting was ignored because the target union type is generic.",title:"Ignoring Small Layout")}, -{Id.StaticTarget , new DiagnosticInfo(severity: DiagnosticSeverity.Error,message:"Union types may not be declared using the 'static' modifier.",title:"Static Union Type Declaration")}, -{Id.TleStrategy , new DiagnosticInfo(severity: DiagnosticSeverity.Warning,message:"The selected storage option for {0} was ignored because it would cause a `TypeLoadException` to be thrown.",title:"TypeLoadException Prevented")}, -{Id.UnionTypeSettingsOnNonUnionType , new DiagnosticInfo(severity: DiagnosticSeverity.Warning,message:"The union type settings attribute will be ignored because the target type is not a union type.",title:"Union Type Settings Ignored")}, -//{Id.UnknownGenericParameterName , new DiagnosticInfo(severity: DiagnosticSeverity.Error,message:"The targeted union type contains an unknown parameter name `{0}`. It could not be located in the type parameter list.",title:"Unknown Generic Parameter Name") }, -{Id.GenericRelation, new DiagnosticInfo(severity: DiagnosticSeverity.Error,message:"Relations may not be declared between generic types.",title:"Generic Relation") }, -{Id.BidirectionalRelation, new DiagnosticInfo(severity: DiagnosticSeverity.Error,message:"The bidirectional relation with {0} will be ignored.",title:"Bidirectional Relation") }, -{Id.DuplicateRelation, new DiagnosticInfo(severity: DiagnosticSeverity.Warning,message:"The duplicate relation with {0} will be ignored.",title:"Duplicate Relation") }, -{Id.SelfReference, new DiagnosticInfo(severity: DiagnosticSeverity.Error,message:"Union types may not reference themselves.",title:"Self Reference") }, -{Id.Inheritance, new DiagnosticInfo(severity: DiagnosticSeverity.Error,message:"Inheritance on union types is not supported yet. Try working around this restriction by adding the base type to the unions list of representable types.",title:"Inheritance") }, - {Id.NullableOptionOnValueType, new DiagnosticInfo(severity: DiagnosticSeverity.Warning, message:"The nullable option used for the representable type {0} will be ignored because the representable type is a value type. If you intended to use a nullable value type, use the '?' annotation on the attribute type argument. Alternatively, use the 'System.Nullable<>' type.", title:"Nullable Option On Value Type") } -}; - - public static readonly IReadOnlyList Descriptors = - Enum.GetValues(typeof(Id)) - .OfType() - .Select(id => new DiagnosticDescriptor( - $"RUG{(Int32)id:0000}", - _infos[id].Title, - _infos[id].Message, - _category, - _infos[id].Severity, - true)) - .ToList(); - private static readonly Dictionary _idDescriptorMap = - Enum.GetValues(typeof(Id)) - .OfType() - .Select(id => (Id: id, Descriptor: new DiagnosticDescriptor( - $"RUG{(Int32)id:0000}", - _infos[id].Title, - _infos[id].Message, - _category, - _infos[id].Severity, - true))) - .ToDictionary(t => t.Id, t => t.Descriptor); - - private static Diagnostic Create( - Id id, - Location location, - params Object[] messageArgs) => - Diagnostic.Create( - _idDescriptorMap[id], - location, - messageArgs); -} diff --git a/UnionsGenerator/Analyzers/Extensions.cs b/UnionsGenerator/Analyzers/Extensions.cs deleted file mode 100644 index 84cd0a4..0000000 --- a/UnionsGenerator/Analyzers/Extensions.cs +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Analyzers; -using System; -using System.Collections.Generic; -using System.Text; - -using Microsoft.CodeAnalysis; - -using RhoMicro.CodeAnalysis.UnionsGenerator.Models; - -internal static class Extensions -{ - public static void AddRange(this IDiagnosticsAccumulator diagnostics, IEnumerable range) - { - foreach(var diagnostic in range) - _ = diagnostics.Add(diagnostic); - } -} diff --git a/UnionsGenerator/Analyzers/Providers.cs b/UnionsGenerator/Analyzers/Providers.cs deleted file mode 100644 index cdb587f..0000000 --- a/UnionsGenerator/Analyzers/Providers.cs +++ /dev/null @@ -1,342 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Analyzers; - -using System.Xml.Linq; - -using Microsoft.CodeAnalysis; - -using RhoMicro.CodeAnalysis.UnionsGenerator.Models; -using RhoMicro.CodeAnalysis.UnionsGenerator.Transformation.Storage; -using RhoMicro.CodeAnalysis.UnionsGenerator.Utils; - -internal static class Providers -{ - private static void Add( - UnionTypeModel model, - IDiagnosticsAccumulator diagnostics, - Func factory) => diagnostics.AddRange(model.Locations.Value.Select(factory)); - private static void Add( - UnionTypeModel model, - IDiagnosticsAccumulator diagnostics, - Func> factory) => diagnostics.AddRange(model.Locations.Value.SelectMany(factory)); - public static readonly IDiagnosticProvider NullableOptionOnValueType = - DiagnosticProvider.Create(static (model, diagnostics) => - { - var nullableValueTypes = model.RepresentableTypes - .Where(t => t.Signature.IsNullableAnnotated); - - if(!nullableValueTypes.Any()) - return; - - Add(model, diagnostics, - l => nullableValueTypes.Select(t => Diagnostics.NullableOptionOnValueType(l, t.Alias))); - }); - public static readonly IDiagnosticProvider BidirectionalRelation = - DiagnosticProvider.Create(static (model, diagnostics) => - { - var bidirectionalRelationNames = model.Relations - .Where(r => r.RelatedType.Signature.Names.FullGenericName == model.Signature.Names.FullGenericName) - .Select(r => r.RelatedType.Signature.Names.FullGenericName); - - if(!bidirectionalRelationNames.Any()) - return; - - Add(model, diagnostics, - l => bidirectionalRelationNames.Select(n => Diagnostics.BidirectionalRelation(l, n))); - }); - public static readonly IDiagnosticProvider DuplicateRelation = - DiagnosticProvider.Create(static (model, diagnostics) => - { - var duplicateRelationNames = model.Relations - .GroupBy(r => r.RelatedType.Signature.Names.FullGenericName) - .Select(g => g.ToArray()) - .Where(g => g.Length > 1) - .Select(g => g[0].RelatedType.Signature.Names.FullGenericName); - - if(!duplicateRelationNames.Any()) - return; - - Add(model, diagnostics, - l => duplicateRelationNames.Select(n => Diagnostics.DuplicateRelation(l, n))); - }); - public static IDiagnosticProvider GenericRelation = - DiagnosticProvider.Create(static (model, diagnostics) => - { - var relations = model.Relations - .Where(r => r.RelatedType.Signature.IsGenericType); - - if(!( model.Signature.IsGenericType && relations.Any() )) - return; - - Add(model, diagnostics, - Diagnostics.GenericRelation); - }); - public static IDiagnosticProvider StorageSelectionViolations = - DiagnosticProvider.Create(static (model, diagnostics) => - { - var offendingRepresentables = model.RepresentableTypes - .Where(d => d.StorageStrategy.Value.Violation != StorageSelectionViolation.None); - - foreach(var representableType in offendingRepresentables) - { - var name = representableType.Signature.Names.FullGenericNullableName; - var violation = representableType.StorageStrategy.Value.Violation; - - var factory = violation switch - { - StorageSelectionViolation.PureValueReferenceSelection => - //cast once, infer following exprs - (Func)Diagnostics.BoxingStrategy, - StorageSelectionViolation.PureValueValueSelectionGeneric => - Diagnostics.GenericViolationStrategy, - StorageSelectionViolation.ImpureValueReference => - Diagnostics.BoxingStrategy, - StorageSelectionViolation.ImpureValueValue => - Diagnostics.TleStrategy, - StorageSelectionViolation.ReferenceValue => - Diagnostics.TleStrategy, - StorageSelectionViolation.UnknownReference => - Diagnostics.PossibleBoxingStrategy, - StorageSelectionViolation.UnknownValue => - Diagnostics.PossibleTleStrategy, - _ => null - }; - - if(factory != null) - { - Add(model, diagnostics, - l => factory.Invoke(l, name)); - } - } - }); - public static IDiagnosticProvider SmallGenericUnion = - DiagnosticProvider.Create(static (model, diagnostics) => - { - if(!model.Signature.IsGenericType || model.Settings.Layout != LayoutSetting.Small) - { - return; - } - - var newLocations = model.Locations.Value - .Select(Diagnostics.SmallGenericUnion); - diagnostics.AddRange(newLocations); - }); - //public static IDiagnosticProvider UnknownGenericParameterName = - // DiagnosticProvider.Create(static (model, diagnostics) => - // { - // var available = model.Symbol.TypeParameters - // .Select(p => p.Name) - // .ToImmutableHashSet(); - // var unknowns = model.Annotations.AllRepresentableTypes - // .Where(a => a.Attribute.RepresentableTypeIsGenericParameter) - // .Where(a => !available.Contains(a.Names.SimpleTypeName)) - // .Select(a => a.Names.SimpleTypeName) - // .ToArray(); - - // if(unknowns.Length == 0) - // return; - - // var location = model.TargetDeclaration.GetLocation(); - - // foreach(var unknown in unknowns) - // { - // var diagnostic = Diagnostics.UnknownGenericParameterName(location, unknown); - // _ = diagnostics.Add(diagnostic); - // } - // }); - public static IDiagnosticProvider ReservedGenericParameterName = - DiagnosticProvider.Create(static (model, diagnostics) => - { - var collisions = model.Signature.TypeArgs - .Select(p => p.Names.Name) - .Where(model.Settings.IsReservedGenericTypeName); - - if(!collisions.Any()) - return; - - var newDiagnostics = model.Locations.Value - .SelectMany(l => collisions - .Select(c => Diagnostics.ReservedGenericParameterName(l, c))); - diagnostics.AddRange(newDiagnostics); - }); - public static IDiagnosticProvider OperatorOmissions = - DiagnosticProvider.Create(static (model, diagnostics) => - { - var omissions = model.RepresentableTypes - .Where(t => t.OmitConversionOperators); - - var interfaceOmissions = omissions.Where(o => o.Signature.IsInterface); - - foreach(var interfaceOmission in interfaceOmissions) - { - Add(model, diagnostics, - l => Diagnostics.RepresentableTypeIsInterface( - l, - interfaceOmission.Signature.Names.FullGenericNullableName)); - } - - var supertypeOmissions = omissions.Where(o => o.IsBaseClassToUnionType); - - foreach(var supertype in supertypeOmissions) - { - Add(model, diagnostics, - l => Diagnostics.RepresentableTypeIsSupertype( - l, - supertype.Signature.Names.FullGenericNullableName)); - } - }); - //public static IDiagnosticProvider UnionTypeSettingsOnNonUnionType = - // DiagnosticProvider.Create(static (model, diagnostics) => - // { - // TODO - // var representableTypes = model.RepresentableTypes; - - // if(representableTypes.Count > 0 || /*settings not located ?*/) - // { - // return; - // } - - // var location = model.TargetDeclaration.GetLocation(); - // var diagnostic = Diagnostics.UnionTypeSettingsOnNonUnionType(location); - // _ = diagnostics.Add(diagnostic); - // }); - public static IDiagnosticProvider ImplicitConversionIfSolitary = - DiagnosticProvider.Create(static (model, diagnostics) => - { - if(model.RepresentableTypes.Count > 1 && - model.RepresentableTypes.Any(a => a.Options.HasFlag(UnionTypeOptions.ImplicitConversionIfSolitary))) - { - Add(model, diagnostics, - Diagnostics.ImplicitConversionOptionOnNonSolitary); - } - }); - public static IDiagnosticProvider UnionTypeCount = - DiagnosticProvider.Create(static (model, diagnostics) => - { - var count = model.RepresentableTypes.Count; - if(count <= Qualifications.MaxRepresentableTypesCount) - return; - - Add(model, diagnostics, - Diagnostics.TooManyTypes); - }); - //public static IDiagnosticProvider Partiality = - // DiagnosticProvider.Create(static (model, diagnostics) => - // { - // if(model.TargetDeclaration.IsPartial()) - // return; - - // var location = model.TargetDeclaration.Identifier.GetLocation(); - // var diagnostic = Diagnostics.NonPartialDeclaration(location); - // _ = diagnostics.Add(diagnostic); - // }); - public static IDiagnosticProvider NonStatic = - DiagnosticProvider.Create(static (model, diagnostics) => - { - if(!model.Signature.IsStatic) - return; - - Add(model, diagnostics, - Diagnostics.StaticTarget); - }); - public static IDiagnosticProvider NonRecord = - DiagnosticProvider.Create(static (model, diagnostics) => - { - if(!model.Signature.IsRecord) - return; - - Add(model, diagnostics, - Diagnostics.RecordTarget); - }); - /* - public static IDiagnosticProvider UnionTypeAttribute = - DiagnosticProvider.Create(static (model, diagnostics) => - { - if(model.Annotations.AllRepresentableTypes.Count > 0) - return; - - var location = model.TargetDeclaration.Identifier.GetLocation(); - var diagnostic = Diagnostics.MissingUnionTypeAttribute(location); - _ = diagnostics.Add(diagnostic); - }); - */ - public static IDiagnosticProvider UniqueUnionTypeAttributes = - DiagnosticProvider.Create(static (model, diagnostics) => - { - var duplicateRepresentableTypeNames = model.RepresentableTypes - .Select(t => t.Signature.Names.FullGenericName) - .GroupBy(n => n) - .Where(g => g.Skip(1).Any()) - .Select(g => g.First()); - - if(!duplicateRepresentableTypeNames.Any()) - return; - - Add(model, diagnostics, - l => duplicateRepresentableTypeNames.Select(t => - Diagnostics.DuplicateUnionTypeAttributes(l, t))); - }); - public static IDiagnosticProvider AliasCollisions = - DiagnosticProvider.Create(static (model, diagnostics) => - { - - var duplicateRepresentableTypeAliae = model.RepresentableTypes - .Select(t => t.Alias) - .GroupBy(n => n) - .Where(g => g.Skip(1).Any()) - .Select(g => g.First()); - - if(!duplicateRepresentableTypeAliae.Any()) - return; - - Add(model, diagnostics, - l => duplicateRepresentableTypeAliae.Select(t => - Diagnostics.AliasCollision(l, t))); - }); - public static IDiagnosticProvider SelfReference = - DiagnosticProvider.Create(static (model, diagnostics) => - { - var hasSelfReference = model.RepresentableTypes - .Any(t => t.Signature.Equals(model.Signature)); - - if(!hasSelfReference) - return; - - Add(model, diagnostics, - Diagnostics.SelfReference); - }); - public static IDiagnosticProvider Inheritance = - DiagnosticProvider.Create(static (model, diagnostics) => - { - if(model.Signature.HasNoBaseClass) - return; - - Add(model, diagnostics, - Diagnostics.Inheritance); - }); - - public static IEnumerable> All = new[] - { - BidirectionalRelation, - DuplicateRelation, - GenericRelation, - StorageSelectionViolations, - SmallGenericUnion, - //UnknownGenericParameterName, - ReservedGenericParameterName, - OperatorOmissions, - //UnionTypeSettingsOnNonUnionType, - ImplicitConversionIfSolitary, - UnionTypeCount, - //Partiality, - NonStatic, - NonRecord, - //UnionTypeAttribute, - UniqueUnionTypeAttributes, - AliasCollisions, - SelfReference, - Inheritance, - NullableOptionOnValueType - }; -} diff --git a/UnionsGenerator/Attributes/RelationAttribute.cs b/UnionsGenerator/Attributes/RelationAttribute.cs deleted file mode 100644 index 90502de..0000000 --- a/UnionsGenerator/Attributes/RelationAttribute.cs +++ /dev/null @@ -1,101 +0,0 @@ -// -// This file was generated by RhoMicro.CodeAnalysis.UnionsGenerator -// The tool used to generate this code may be subject to license terms; -// this generated code is however not subject to those terms, instead it is -// subject to the license (if any) applied to the containing project. -// -#nullable enable -#pragma warning disable - -namespace RhoMicro.CodeAnalysis -{ - using System; - - /// - /// Marks the target type to be related to another union type. - /// - /// The type to register as related to the target union type. - [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = true, Inherited = false)] -#if UNIONS_GENERATOR - [GenerateFactory(OmitTypeCheck = true)] -#endif - sealed partial class RelationAttribute : Attribute - { } - /// - /// Marks the target type to be related to other union types. - /// - /// The first type to register as related to the target union type. - /// The second type to register as related to the target union type. - [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - sealed partial class RelationAttribute : Attribute - { } - /// - /// Marks the target type to be related to other union types. - /// - /// The first type to register as related to the target union type. - /// The second type to register as related to the target union type. - /// The third type to register as related to the target union type. - [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - sealed partial class RelationAttribute : Attribute - { } - /// - /// Marks the target type to be related to other union types. - /// - /// The first type to register as related to the target union type. - /// The second type to register as related to the target union type. - /// The third type to register as related to the target union type. - /// The fourth type to register as related to the target union type. - [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - sealed partial class RelationAttribute : Attribute - { } - /// - /// Marks the target type to be related to other union types. - /// - /// The first type to register as related to the target union type. - /// The second type to register as related to the target union type. - /// The third type to register as related to the target union type. - /// The fourth type to register as related to the target union type. - /// The fifth type to register as related to the target union type. - [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - sealed partial class RelationAttribute : Attribute - { } - /// - /// Marks the target type to be related to other union types. - /// - /// The first type to register as related to the target union type. - /// The second type to register as related to the target union type. - /// The third type to register as related to the target union type. - /// The fourth type to register as related to the target union type. - /// The fifth type to register as related to the target union type. - /// The sixth type to register as related to the target union type. - [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - sealed partial class RelationAttribute : Attribute - { } - /// - /// Marks the target type to be related to other union types. - /// - /// The first type to register as related to the target union type. - /// The second type to register as related to the target union type. - /// The third type to register as related to the target union type. - /// The fourth type to register as related to the target union type. - /// The fifth type to register as related to the target union type. - /// The sixth type to register as related to the target union type. - /// The seventh type to register as related to the target union type. - [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - sealed partial class RelationAttribute : Attribute - { } - /// - /// Marks the target type to be related to other union types. - /// - /// The first type to register as related to the target union type. - /// The second type to register as related to the target union type. - /// The third type to register as related to the target union type. - /// The fourth type to register as related to the target union type. - /// The fifth type to register as related to the target union type. - /// The sixth type to register as related to the target union type. - /// The seventh type to register as related to the target union type. - /// The eighth type to register as related to the target union type. - [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - sealed partial class RelationAttribute : Attribute - { } -} diff --git a/UnionsGenerator/Attributes/UnionTypeAttribute.Internal.cs b/UnionsGenerator/Attributes/UnionTypeAttribute.Internal.cs deleted file mode 100644 index 0976624..0000000 --- a/UnionsGenerator/Attributes/UnionTypeAttribute.Internal.cs +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis; - -using Microsoft.CodeAnalysis; - -using RhoMicro.CodeAnalysis.UnionsGenerator.Models; - -internal partial class AliasedUnionTypeBaseAttribute -{ - internal PartialRepresentableTypeModel GetPartialModel(TypeOrTypeParameterType representableType, INamedTypeSymbol unionType, CancellationToken ct) - { - var result = PartialRepresentableTypeModel.Create( - Alias, - FactoryName, - Options, - Storage, - new(Groups), - representableType, - unionType, - ct); - - return result; - } -} diff --git a/UnionsGenerator/Attributes/UnionTypeAttribute.cs b/UnionsGenerator/Attributes/UnionTypeAttribute.cs deleted file mode 100644 index 37afb08..0000000 --- a/UnionsGenerator/Attributes/UnionTypeAttribute.cs +++ /dev/null @@ -1,292 +0,0 @@ -// -// This file was generated by RhoMicro.CodeAnalysis.UnionsGenerator -// The tool used to generate this code may be subject to license terms; -// this generated code is however not subject to those terms, instead it is -// subject to the license (if any) applied to the containing project. -// -#nullable enable -#pragma warning disable - -namespace RhoMicro.CodeAnalysis -{ - using System; - using System.Collections.Generic; - - /// - /// Defines options for generating union types. - /// - [Flags] - enum UnionTypeOptions - { - /// - /// The default options. - /// - Default = ImplicitConversionIfSolitary, - /// - /// - None = 0x00, - /// - /// Instructs the generator to emit an implicit conversion to the representable type if it is the only one. - /// In effect, this option will enable the union type to act as an alias wrapper for the representable type. - /// - ImplicitConversionIfSolitary = 0x01, - /// - /// Instructs the generator to emit a superset conversion operator implementation even though - /// the representable type is a generic type parameter. By default, it is omitted because of possible - /// unification for certain generic arguments. - /// - //SupersetOfParameter = 0x02, - /// - /// Instructs the generator to treat the representable reference type - /// as nullable, allowing for - /// arguments in factories, conversions etc. - /// If you are trying to use a nullable value type, use the ? annotation or - /// , where T is the value type. - /// - Nullable = 0x04 - } - - /// - /// Defines options for the storage implementation of a representable type. - /// In order for the generator to generate an efficient storage implementation, - /// consumers should communicate whether the representable type is known to - /// be a struct, class or of unknown nature. This is mostly relevant for generic - /// type parameters, however an explicit strategy may be selected for any representable - /// type. Whether or not generic type parameters are known to be reference - /// or value types depends on their constraints. Parameters constrained to - /// will be assumed to be value types. Conversely, - /// parameters constrained to will be assumed to be reference types. - /// - /* - | box |value| auto | field - struct | rc! | vc | vc | cc - class | rc | rc! | rc | cc - none | rc! | vc! | rc! | cc - */ - enum StorageOption - { - /// - /// The generator will automatically decide on a storage strategy. - /// - /// If the representable type is known to be a value type, - /// this will store values of that type inside a shared value type container. - /// Boxing will not occur. - /// - /// - /// If the representable type is known to be a reference type, - /// this will store values of that type inside a shared reference type container. - /// - /// - /// If the representable type is neither known to be a reference type - /// nor a value type, this option will cause values of that type to - /// be stored inside a shared reference type container. - /// If the representable type is a generic type parameter, - /// boxing will occur for value type arguments to that parameter. - /// - /// - Auto, - - /// - /// The generator will always store values of the representable type - /// inside a shared reference type container. - /// - /// If the representable type is known to be a value type, - /// boxing will occur. - /// - /// - /// If the representable type is a generic type parameter, - /// boxing will occur for value type arguments to that parameter. - /// - /// - Reference, - - /// - /// The generator will attempt to store values of the representable type - /// inside a value type container. - /// - /// If the representable type is known to be a value type, - /// this will store values of that type inside a shared value type container. - /// Boxing will not occur. - /// - /// - /// If the representable type is known to be a reference type, - /// this will store values of that type inside a shared reference type container. - /// Boxing will not occur. - /// - /// - /// If the representable type is neither known to be a reference type - /// nor a value type, this option will cause values of that type to - /// be stored inside a shared value type container. - /// If the representable type is a generic type parameter, - /// an exception of type will occur for - /// reference type arguments to that parameter. - /// - /// - Value, - - /// - /// The generator will attempt to store values of the representable type - /// inside a dedicated container for that type. - /// - /// If the representable type is known to be a value type, - /// this will store values of that type inside a dedicated - /// value type container. - /// Boxing will not occur. - /// - /// - /// If the representable type is known to be a reference type, - /// this will store values of that type inside a - /// dedicated reference type container. - /// - /// - /// If the representable type is neither known to be a reference type - /// nor a value type, this option will cause values of that type to - /// be stored inside a dedicated strongly typed container. - /// Boxing will not occur. - /// - /// - Field - } - - /// - /// Marks the target type as a union type being able to represent the type passed to the constructor. - /// - [AttributeUsage(( (AttributeTargets)( -1 ) ))] - partial class UnionTypeBaseAttribute : Attribute - { - /// - /// Gets or sets the alias groups that the representable type is to be a part of. - /// Represnetable types that share a group may be checked for using unified methods - /// and properties like IsGroup where Group is the name of the group - /// that the representable type is a part of. - /// - public virtual String[] Groups { get; set; } = Array.Empty(); - - /// - /// Gets or sets the generator options to use. - /// - public virtual UnionTypeOptions Options { get; set; } = UnionTypeOptions.Default; - - /// - /// Gets or sets the option defining storage generation. - /// - public virtual StorageOption Storage { get; set; } - } - - [AttributeUsage(( (AttributeTargets)( -1 ) ))] -#if UNIONS_GENERATOR - [GenerateFactory(OmitTypeCheck = true)] -#endif - partial class AliasedUnionTypeBaseAttribute : UnionTypeBaseAttribute - { - /// - /// Gets or sets the alias to use for members representing the type represented by the union. - /// For example, the represented type would be represented using names like - /// list_of_T. Setting this property to yourAlias will instruct the generator to use - /// member names like yourAlias instead of list_of_T. Use this property to avoid - /// name collisions in generated code. Since the alias will be used for member names, it will - /// only be taken into account if it is a valid identifier name. - /// - public String? Alias { get; set; } - /// - /// Gets or sets the name of the generated static factory method used to create instances - /// of the union type with the representable type. - /// - public String FactoryName { get; set; } = "Create"; - /// - public override String[] Groups { get => base.Groups; set => base.Groups = value; } - /// - public override UnionTypeOptions Options { get => base.Options; set => base.Options = value; } - /// - public override StorageOption Storage { get => base.Storage; set => base.Storage = value; } - } - /// - /// Marks the target type as a union type being able to represent . - /// - [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - sealed partial class UnionTypeAttribute : AliasedUnionTypeBaseAttribute - { } - /// - /// Marks the target type as a union type being able to represent - /// - /// and . - /// - [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - sealed partial class UnionTypeAttribute : UnionTypeBaseAttribute - { } - /// - /// Marks the target type as a union type being able to represent - /// , - /// - /// and . - /// - [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - sealed partial class UnionTypeAttribute : UnionTypeBaseAttribute - { } - /// - /// Marks the target type as a union type being able to represent - /// , - /// , - /// - /// and . - /// - [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - sealed partial class UnionTypeAttribute : UnionTypeBaseAttribute - { } - /// - /// Marks the target type as a union type being able to represent - /// , - /// , - /// , - /// - /// and . - /// - [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - sealed partial class UnionTypeAttribute : UnionTypeBaseAttribute - { } - /// - /// Marks the target type as a union type being able to represent - /// , - /// , - /// , - /// , - /// - /// and . - /// - [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - sealed partial class UnionTypeAttribute : UnionTypeBaseAttribute - { } - /// - /// Marks the target type as a union type being able to represent - /// , - /// , - /// , - /// , - /// , - /// - /// and . - /// - [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - sealed partial class UnionTypeAttribute : UnionTypeBaseAttribute - { } - /// - /// Marks the target type as a union type being able to represent - /// , - /// , - /// , - /// , - /// , - /// , - /// - /// and . - /// - [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = true, Inherited = false)] - sealed partial class UnionTypeAttribute : UnionTypeBaseAttribute - { } - /// - /// Marks the target type as a union type being able to represent the annotated type parameter. - /// - [AttributeUsage(AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] - sealed partial class UnionTypeAttribute : AliasedUnionTypeBaseAttribute - { } -} \ No newline at end of file diff --git a/UnionsGenerator/Attributes/UnionTypeFactoryAttribute.cs b/UnionsGenerator/Attributes/UnionTypeFactoryAttribute.cs deleted file mode 100644 index 4ed8907..0000000 --- a/UnionsGenerator/Attributes/UnionTypeFactoryAttribute.cs +++ /dev/null @@ -1,27 +0,0 @@ -// -// This file was generated by RhoMicro.CodeAnalysis.UnionsGenerator -// The tool used to generate this code may be subject to license terms; -// this generated code is however not subject to those terms, instead it is -// subject to the license (if any) applied to the containing project. -// -#nullable enable -#pragma warning disable - -namespace RhoMicro.CodeAnalysis -{ - using System; - - /// - /// Marks the target method as the factory method to use when instantiating - /// an instance of the union type representing a value of the annotated parameter. - /// Factory methods must be static, have no type parameters and only have one - /// parameter of a type representable by the union type. - /// Factory polymorphism is not yet supported. - /// - [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] -#if UNIONS_GENERATOR - [GenerateFactory] -#endif - sealed partial class UnionTypeFactoryAttribute : Attribute - { } -} diff --git a/UnionsGenerator/Attributes/UnionTypeSettingsAttribute.cs b/UnionsGenerator/Attributes/UnionTypeSettingsAttribute.cs deleted file mode 100644 index 6d88fa0..0000000 --- a/UnionsGenerator/Attributes/UnionTypeSettingsAttribute.cs +++ /dev/null @@ -1,276 +0,0 @@ -// -// This file was generated by RhoMicro.CodeAnalysis.UnionsGenerator -// The tool used to generate this code may be subject to license terms; -// this generated code is however not subject to those terms, instead it is -// subject to the license (if any) applied to the containing project. -// -#nullable enable -#pragma warning disable - -namespace RhoMicro.CodeAnalysis; - -using System; - -#region Setting Enums -/// -/// Defines settings for generating an implementation of . -/// -enum ToStringSetting -{ - /// - /// The generator will emit an implementation that returns detailed information, including: - /// - /// the name of the union type - /// a list of types representable by the union type - /// an indication of which type is being represented by the instance - /// the value currently being represented by the instance - /// - /// - Detailed, - /// - /// The generator will not generate an implementation of . - /// - None, - /// - /// The generator will generate an implementation that returns the result of calling on the currently represented value. - /// - Simple -} -/// -/// Defines settings for annotating the target with an instance of . -/// -enum LayoutSetting -{ - /// - /// Generate an annotation optimized for size. - /// - Small, - /// - /// Do not generate any annotations. - /// - Auto -} -/// -/// Defines settings for controlling the accessibility of generated constructors. -/// -enum ConstructorAccessibilitySetting -{ - /// - /// Generated constructors should always be private, unless - /// no conversion operators are generated for the type they - /// accept. This would be the case for interface types or - /// supertypes of the target union. - /// - PublicIfInconvertible, - /// - /// Generated constructors should always be private. - /// - Private, - /// - /// Generated constructors should always be public - /// - Public -} -/// -/// Defines settings on how to implement interfaces that all representable -/// types implement. -/// -enum InterfaceMatchSetting -{ - /// - /// Generated interface implementations should be explicit if at least - /// one of the representable types implements the interface explicitly; - /// otherwise, interface implementations should be implicit. - /// - Auto, - /// - /// Generated interface implementations should always be explicit. - /// - Explicit, - /// - /// Generated interface implementations should always be implicit. - /// - Implicit, - /// - /// No interfaces implementations should be generated. - /// - Omit -} -/// -/// Defines settings for the kind of diagnostics to report. -/// -[Flags] -enum DiagnosticsLevelSettings -{ - /// - /// Instructs the analyzer not to emit diagnostics - /// - None = 0x00, - /// - /// Instructs the analyzer to report info diagnostics. - /// - Info = 0x01, - /// - /// Instructs the analyzer to report warning diagnostics. - /// - Warning = 0x02, - /// - /// Instructs the analyzer to report error diagnostics. - /// - Error = 0x04, - /// - /// Instructs the analyzer to report all diagnostics. - /// - All = Info | Warning | Error -} -/// -/// Defines miscellaneous settings. -/// -[Flags] -enum MiscellaneousSettings -{ - /// - /// - None = 0x00, - /// - /// The default settings. - /// - Default = None, - /// - /// Indicates whether the generated source code should be available as a string constant on the union type itself. - /// This setting is generally only useful if the generated implementation should be emitted from another generator. - /// - EmitGeneratedSourceCode = 0x01, - /// - /// Indicates whether to generate a custom converter type - /// for System.Text.Json deserialization. If set, this will also cause - /// the union type to be annotated with an appropriate JsonConverter attribute. - /// - GenerateJsonConverter = 0x02, - /// - /// Indicates that the generator should emit a comment detailing the structure of the union type. - /// - EmitStructuralRepresentation = 0x04 -} - -/// -/// Defines settings pertaining to equality operator implementations. -/// -enum EqualityOperatorsSetting -{ - /// - /// Equality operators will be emitted only if the target union type is a value type. - /// - EmitOperatorsIfValueType, - /// - /// Equality operators will be emitted. - /// - EmitOperators, - /// - /// Equality operators will be omitted. - /// - OmitOperators -} - -#endregion -#region Attribute Declaration -/// -/// Supplies the generator with additional settings on how to generate a targeted union type. -/// If the target member is an assembly, the attribute supplies default values for any union -/// type setting not defined. -/// -[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class | AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)] -#if UNIONS_GENERATOR -[GenerateFactory] -#endif -sealed partial class UnionTypeSettingsAttribute : Attribute -{ - #region Settings - /// - /// Defines how to generate an implementation . - /// - public ToStringSetting ToStringSetting { get; set; } = ToStringSetting.Detailed; - /// - /// Defines whether to generate a size optimizing annotation. - /// - public LayoutSetting Layout { get; set; } = LayoutSetting.Auto; - /// - /// The level of diagnostics to be reported by the analyzer. - /// - public DiagnosticsLevelSettings DiagnosticsLevel { get; set; } = DiagnosticsLevelSettings.All; - /// - /// The desired accessibility of generated constructors. - /// - public ConstructorAccessibilitySetting ConstructorAccessibility { get; set; } = ConstructorAccessibilitySetting.Private; - /// - /// Indicates how to generate implementations for - /// interfaces implemented by all representable types. Implementations will - /// map calls to interface instance methods and properties onto the represented - /// value. - /// - /// Please note that currently, only fully bound and constructed interface implementations are supported. - /// - /// - public InterfaceMatchSetting InterfaceMatchSetting { get; set; } = InterfaceMatchSetting.Auto; - /// - /// Indicates how to generate equality operators. - /// By default, equality operators will only be emitted for value types, so as to preserve - /// reference equality for comparing reference union types via == or !=. - /// - public EqualityOperatorsSetting EqualityOperatorsSetting { get; set; } = EqualityOperatorsSetting.EmitOperatorsIfValueType; - /// - /// Gets or sets miscellaneous settings. - /// - public MiscellaneousSettings Miscellaneous { get; set; } = MiscellaneousSettings.Default; - #endregion - #region Strings - /// - /// A raw code preface to prepend before the generated type declaration. - /// - public String TypeDeclarationPreface { get; set; } = ""; - /// - /// The name of the generic parameter for generic Is, As and factory methods. - /// Set this property in order to avoid name collisions with generic union type parameters - /// - public String GenericTValueName { get; set; } = "TValue"; - /// - /// The name of the generic parameter for the TryConvert method. - /// Set this property in order to avoid name collisions with generic union type parameters - /// - public String TryConvertTypeName { get; set; } = "TUnion"; - /// - /// The name of the generic parameter for the Match method. - /// Set this property in order to avoid name collisions with generic union type parameters - /// - public String MatchTypeName { get; set; } = "TMatchResult"; - /// - /// The name to use for the discriminating tag type. - /// - public String TagTypeName { get; set; } = "__Tag"; - /// - /// The name to use for the container type containing value types. - /// - public String ValueTypeContainerTypeName { get; set; } = "__ValueTypeContainer"; - /// - /// The name to use for the field containing value types. - /// - public String ValueTypeContainerName { get; set; } = "__value"; - /// - /// The name to use for the field containing reference types. - /// - public String ReferenceTypeContainerName { get; set; } = "__reference"; - /// - /// The name to use for the field containing the discriminating tag. - /// - public String TagFieldName { get; set; } = "__tag"; - /// - /// The name to use for the default (uninitialized) tag value. - /// - public String TagNoneName { get; set; } = "__None"; - /// - /// The name of the generated json converter type. - /// - public String JsonConverterTypeName { get; set; } = "JsonConverter"; - #endregion -} -#endregion \ No newline at end of file diff --git a/UnionsGenerator/Generators/SettingsAttributeData.cs b/UnionsGenerator/Generators/SettingsAttributeData.cs deleted file mode 100644 index 02b5411..0000000 --- a/UnionsGenerator/Generators/SettingsAttributeData.cs +++ /dev/null @@ -1,149 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Generators; - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.CSharp; - -using RhoMicro.CodeAnalysis; -using RhoMicro.CodeAnalysis.UnionsGenerator.Models; - -using System; -using System.Collections.Immutable; -using System.Reflection; - -internal sealed class SettingsAttributeData : AttributeData, IEquatable -{ - #region Constructors - private SettingsAttributeData( - AttributeData declaredSettings, - ImmutableArray> namedArguments, - Dictionary namedArgumentsMap) - { - _namedArgumentsMap = namedArgumentsMap; - - CommonAttributeClass = declaredSettings.AttributeClass; - CommonAttributeConstructor = declaredSettings.AttributeConstructor; - CommonApplicationSyntaxReference = declaredSettings.ApplicationSyntaxReference; - CommonConstructorArguments = declaredSettings.ConstructorArguments; - CommonNamedArguments = namedArguments; - } - private SettingsAttributeData( - AttributeData declaredSettings, - Dictionary namedArgumentsMap) - : this(declaredSettings, declaredSettings.NamedArguments, namedArgumentsMap) - { } - private SettingsAttributeData() - { - _namedArgumentsMap = []; - CommonConstructorArguments = ImmutableArray.Create(); - CommonNamedArguments = ImmutableArray.Create>(); - } - #endregion - - public static SettingsAttributeData Default { get; } = new(); - - private readonly Dictionary _namedArgumentsMap; - - protected override INamedTypeSymbol? CommonAttributeClass { get; } - protected override IMethodSymbol? CommonAttributeConstructor { get; } - protected override SyntaxReference? CommonApplicationSyntaxReference { get; } - protected override ImmutableArray CommonConstructorArguments { get; } - protected override ImmutableArray> CommonNamedArguments { get; } - - public static SettingsAttributeData CreateAssemblySettingsWithDefaults(AttributeData assemblySettings) - => CreateWithDefaults(assemblySettings); - public static SettingsAttributeData CreateDeclaredSettings(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var declaredSettings = context.Attributes[0]; - - var declaredPropsMap = MapProperties(declaredSettings); - - var result = new SettingsAttributeData(declaredSettings, declaredPropsMap); - - return result; - } - public static SettingsAttributeData CreateDeclaredSettingsWithDefaults( - AttributeData declaredSettings, - SettingsAttributeData assemblySettings) - => CreateWithDefaults(declaredSettings, assemblySettings); - - private static SettingsAttributeData CreateWithDefaults( - AttributeData declaredSettings, - SettingsAttributeData? fallbackSettings = null) - { - var declaredPropsMap = MapProperties(declaredSettings); - var assemblyPropsMap = fallbackSettings?._namedArgumentsMap ?? []; - var namedArgsMap = SettingsModel.PropertyNames - .Select(n => - declaredPropsMap.TryGetValue(n, out var v) ? (n, v) : - assemblyPropsMap.TryGetValue(n, out v) ? (n, v) : null) - .Where(c => c.HasValue) - .ToDictionary(t => t!.Value.name, t => t!.Value.value); - var namedArgs = namedArgsMap.ToImmutableArray(); - var result = new SettingsAttributeData(declaredSettings, namedArgs, namedArgsMap); - - return result; - } - - private static Dictionary MapProperties(AttributeData data) => - data.NamedArguments - .Where(kvp => kvp.Key != null) - .ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - - public override Boolean Equals(Object? obj) => Equals(obj as SettingsAttributeData); - public Boolean Equals(SettingsAttributeData? other) - { - if(_namedArgumentsMap.Count != other?._namedArgumentsMap.Count) - return false; - - foreach(var (name, constant) in _namedArgumentsMap) - { - if(!other._namedArgumentsMap.TryGetValue(name, out var otherConstant) || - !Equals(constant, otherConstant)) - { - return false; - } - } - - return true; - } - - private static Boolean Equals(TypedConstant a, TypedConstant b) - { - if(a.Kind != b.Kind || a.IsNull != b.IsNull) - return false; - - switch(a.Kind) - { - case TypedConstantKind.Primitive or TypedConstantKind.Enum - when !EqualityComparer.Default.Equals(a.Value, b.Value): - return false; - case TypedConstantKind.Type - when !SymbolEqualityComparer.IncludeNullability.Equals(a.Type, b.Type): - return false; - case TypedConstantKind.Array: - { - if(a.Values.Length != b.Values.Length) - return false; - - for(var i = 0; i < a.Values.Length; i++) - { - var aValue = a.Values[i]; - var bValue = b.Values[i]; - if(!Equals(aValue, bValue)) - return false; - } - - break; - } - } - - return true; - } - - public override Int32 GetHashCode() => throw new NotSupportedException("GetHashCode is not supported on settings attribute data."); -} diff --git a/UnionsGenerator/Generators/UnionsGenerator.cs b/UnionsGenerator/Generators/UnionsGenerator.cs deleted file mode 100644 index 21c1cf7..0000000 --- a/UnionsGenerator/Generators/UnionsGenerator.cs +++ /dev/null @@ -1,525 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Generators; - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -using RhoMicro.CodeAnalysis; -using RhoMicro.CodeAnalysis.Library; -using RhoMicro.CodeAnalysis.Library.Text; -using RhoMicro.CodeAnalysis.UnionsGenerator.Models; -using RhoMicro.CodeAnalysis.UnionsGenerator.Transformation.Visitors; -using RhoMicro.CodeAnalysis.UnionsGenerator.Utils; - -using System; - -using FactoryMapProvider = Microsoft.CodeAnalysis.IncrementalValueProvider>>; -using PartialUnionTypeModelsProvider = Microsoft.CodeAnalysis.IncrementalValueProvider>; -using PartialTargetTypeModelUnionProvider = Microsoft.CodeAnalysis.IncrementalValueProvider representableTypes, Boolean isEqualsRequired, Boolean doesNotImplementToString)>>; -using RelationsProvider = Microsoft.CodeAnalysis.IncrementalValueProvider>>; -using SettingsMapProvider = Microsoft.CodeAnalysis.IncrementalValueProvider<(Models.SettingsModel fallbackSettings, EquatableDictionary definedSettings)>; -using SourceTextProvider = Microsoft.CodeAnalysis.IncrementalValuesProvider<(String hintName, String source)>; -using System.Linq; -using System.Collections.Immutable; -using RhoMicro.CodeAnalysis.UnionsGenerator.Analyzers; -using Microsoft.CodeAnalysis.CSharp; - -/// -/// Generates partial union type implementations. -/// -[Generator(LanguageNames.CSharp)] -public class UnionsGenerator : IIncrementalGenerator -{ - /// - /// Gets constant source texts to be generated into target projects. - /// - public static IReadOnlyList ConstantSourceTexts { get; } = - [ - AliasedUnionTypeBaseAttribute.SourceText, - RelationAttribute.SourceText, - UnionTypeFactoryAttribute.SourceText, - UnionTypeSettingsAttribute.SourceText, - ConstantSources.Util - ]; - /// - public void Initialize(IncrementalGeneratorInitializationContext context) - { - var factoryMapProvider = CreateFactoryMapProvider(context); - var settingsProvider = CreateSettingsProvider(context); - var relationsProvider = CreateRelationsProvider(context); - - var typeTargetProvider = CreateUnionTypeProvider(context); - var partialTargetProvider = UnionPartialTypeModelProviders(typeTargetProvider); - - var targetTypeModelsProvider = UnifyPartialModels(partialTargetProvider, settingsProvider, factoryMapProvider, relationsProvider); - var sourceTextProvider = CreateSourceTextProvider(targetTypeModelsProvider); - - context.RegisterSourceOutput(sourceTextProvider, (ctx, sourceData) => - { - ctx.CancellationToken.ThrowIfCancellationRequested(); - ctx.AddSource(sourceData.hintName, sourceData.source); - }); - context.RegisterPostInitializationOutput(RegisterConstantSources); - } - #region Transformations - private static SourceTextProvider CreateSourceTextProvider(IncrementalValuesProvider provider) => - provider - .Select((model, ct) => - { - var containsErrors = DiagnosticsAccumulator.Create(model) - .DiagnoseNonHiddenSeverities() - .Receive(Providers.All, ct) - .ContainsErrors; - - return (model, containsErrors); - }) - .Where(t => !t.containsErrors) - .Select((tuple, ct) => - { - ct.ThrowIfCancellationRequested(); - - var (model, _) = tuple; - - var resultBuilder = new IndentedStringBuilder( - IndentedStringBuilderOptions.GeneratedFile with - { - AmbientCancellationToken = ct, - GeneratorName = "RhoMicro.CodeAnalysis.UnionsGenerator", - PrependWarningDisablePragma = true - }); - - _ = resultBuilder.OpenRegionBlock($"Implementation of {model.Signature.Names.FullGenericName}"); - - if(model.Settings.Miscellaneous.HasFlag(MiscellaneousSettings.EmitStructuralRepresentation)) - { - _ = resultBuilder.OpenRegionBlock("Structural Representation").OpenBlock(new(Indentation: "// ")); - - new StructuralRepresentationVisitor(resultBuilder).Visit(model); - - resultBuilder.CloseBlock().CloseBlockCore(); - } - - new SourceTextVisitor(resultBuilder).Visit(model); - - var source = resultBuilder.CloseBlock().ToString(); - var hint = model.Signature.Names.FullIdentifierOrHintName; - var hintName = $"{hint}.g.cs"; - - return (hintName, source); - }); - #endregion - #region Combinations - private static IncrementalValuesProvider UnifyPartialModels( - PartialTargetTypeModelUnionProvider partialsProvider, - SettingsMapProvider settingsProvider, - FactoryMapProvider factoriesProvider, - RelationsProvider relationsProvider) => - partialsProvider - .Combine(settingsProvider) - .Combine(factoriesProvider) - .Combine(relationsProvider) - .Select((tuple, ct) => - { - ct.ThrowIfCancellationRequested(); - - var relations = tuple.Right; - var factoriesMap = tuple.Left.Right; - var (fallbackSettings, definedSettings) = tuple.Left.Left.Right; - var partials = tuple.Left.Left.Left; - - var targetTypeModels = new List(); - - foreach(var (targetTypeSig, data) in partials) - { - var (representableTypes, isEqualsRequired, doesNotImplementToString) = data; - ct.ThrowIfCancellationRequested(); - - var settings = getSettings(targetTypeSig); - var relationAttributes = getRelations(targetTypeSig); - var factories = getFactories(targetTypeSig); - - var targetModel = UnionTypeModel.Create( - targetTypeSig, - representableTypes, - factories, - relationAttributes, - settings, - isEqualsRequired, - doesNotImplementToString, - ct); - - targetTypeModels.Add(targetModel); - } - - return new EquatableList(targetTypeModels); - - EquatableList getRelations(TypeSignatureModel target) => - relations!.TryGetValue(target, out var r) ? - r : - EquatableList.Empty; - SettingsModel getSettings(TypeSignatureModel targetTypeSig) => - definedSettings.TryGetValue(targetTypeSig, out var settings) ? - settings : - fallbackSettings; - EquatableList getFactories(TypeSignatureModel targetTypeSig) => - factoriesMap!.TryGetValue(targetTypeSig, out var factories) - ? factories - : EquatableList.Empty; - }).SelectMany((models, ct) => - { - ct.ThrowIfCancellationRequested(); - return models; - }); - private static PartialTargetTypeModelUnionProvider UnionPartialTypeModelProviders(PartialUnionTypeModelsProvider provider) => - provider - .Select((keyValuePairs, ct) => - { - ct.ThrowIfCancellationRequested(); - - var mutableResult = new Dictionary representableTypes, HashSet mappedRepresentableTypes)>(); - var result = new Dictionary representableTypes, Boolean isEqualsRequired, Boolean doesNotImplementToString)>(); - var isEqualsRequiredAccumulators = new Dictionary(); - var doesNotImplementToStringAccumulators = new Dictionary(); - - foreach(var (key, value, isEqualsRequired, doesNotImplementToString, _) in keyValuePairs) - { - if(!isEqualsRequiredAccumulators.ContainsKey(key)) - isEqualsRequiredAccumulators[key] = true; - - if(!doesNotImplementToStringAccumulators.ContainsKey(key)) - doesNotImplementToStringAccumulators[key] = true; - - isEqualsRequiredAccumulators[key] &= isEqualsRequired; - doesNotImplementToStringAccumulators[key] &= doesNotImplementToString; - - ct.ThrowIfCancellationRequested(); - if(!mutableResult.TryGetValue(key, out var data)) - { - data = ([], []); - mutableResult.Add(key, data); - result.Add(key, (new(data.representableTypes), isEqualsRequiredAccumulators[key], doesNotImplementToStringAccumulators[key])); - } else - { - result[key] = (result[key].representableTypes, isEqualsRequiredAccumulators[key], doesNotImplementToStringAccumulators[key]); - } - - if(data.mappedRepresentableTypes.Add(value.Signature)) - data.representableTypes.Add(value); - } - - return new EquatableDictionary representableTypes, Boolean isEqualsRequired, Boolean doesNotImplementToString)>(result); - }); - #endregion - #region Relations FAWMN - private static RelationsProvider CreateRelationsProvider(IncrementalGeneratorInitializationContext context) - { - var result = Enumerable.Range(2, 8) - .Select(i => $"RhoMicro.CodeAnalysis.RelationAttribute`{i}") - .Select(createProvider) - .Aggregate( - createProvider("RhoMicro.CodeAnalysis.RelationAttribute`1"), - (leftProvider, rightProvider) => - leftProvider.Combine(rightProvider) - .Select((tuple, ct) => - { - ct.ThrowIfCancellationRequested(); - var (leftList, rightList) = tuple; - var result = leftList.Concat(rightList) - .GroupBy(t => t.targetSignature) - .Select(g => (targetSignature: g.Key, relations: g.SelectMany(t => t.relations).ToEquatableList(ct))) - .ToEquatableList(ct); - - return result; - })) - .Select((tuples, ct) => - { - ct.ThrowIfCancellationRequested(); - - var mutableResult = new Dictionary>(); - var result = new Dictionary>(); - for(var i = 0; i < tuples.Count; i++) - { - ct.ThrowIfCancellationRequested(); - - var (targetSignature, relations) = tuples[i]; - if(!mutableResult.TryGetValue(targetSignature, out var models)) - { - models = []; - mutableResult.Add(targetSignature, models); - result.Add(targetSignature, new(models)); - } - - models.AddRange(relations); - } - - //Map(UnionTypeSignature, List(Relation)) - return result.AsEquatable(); - }); - - return result; - - IncrementalValueProvider relations)>> createProvider(String name) => - context.SyntaxProvider.ForAttributeWithMetadataName< - (TypeSignatureModel targetSignature, EquatableList relations)?>( - name, - Qualifications.IsUnionTypeDeclarationSyntax, - (ctx, ct) => - { - ct.ThrowIfCancellationRequested(); - //check if target of [Relation] is regular struct or class - if(ctx.TargetSymbol is not INamedTypeSymbol target || - target.TypeKind is not TypeKind.Struct and not TypeKind.Class || - target.IsRecord) - { - return null; - } - - ct.ThrowIfCancellationRequested(); - var targetSignature = TypeSignatureModel.Create(target, ct); - var relations = new List(); - //check if T in [Relation] is named type & is actual union type (has attribute on self or type param) <== consider marker attribute on generated impl? - for(var i = 0; i < ctx.Attributes.Length; i++) - { - ct.ThrowIfCancellationRequested(); - if(ctx.Attributes[i].AttributeClass?.TypeArguments.Length is null or < 1) - continue; - - foreach(var potentialRelationSymbol in ctx.Attributes[i].AttributeClass!.TypeArguments) - { - ct.ThrowIfCancellationRequested(); - if(potentialRelationSymbol is INamedTypeSymbol relationSymbol && - relationSymbol.GetAttributes() - .OfAliasedUnionTypeBaseAttribute() - .Concat(relationSymbol.TypeParameters - .SelectMany(p => p.GetAttributes().OfAliasedUnionTypeBaseAttribute())) - .Any()) - { - var model = RelationModel.Create(target, relationSymbol, ct); - relations.Add(model); - } - } - } - - return (targetSignature, relations.AsEquatable()); - }).Where(m => m.HasValue && m.Value.relations.Count > 0) - .Select((t, ct) => - { - ct.ThrowIfCancellationRequested(); - return t!.Value; - }) - .Collect() - .WithCollectionComparer() - .Select((tuples, ct) => - { - ct.ThrowIfCancellationRequested(); - return tuples.AsEquatable(); - }); - } - #endregion - #region Settings FAWMN - private static IncrementalValueProvider<(SettingsAttributeData data, SettingsModel parsed)> CreateAssemblySettingsProvider(IncrementalGeneratorInitializationContext context) => - context.SyntaxProvider.ForAttributeWithMetadataName( - UnionTypeSettingsAttribute.MetadataName, - (node, ct) => node is CompilationUnitSyntax, - (ctx, ct) => - { - ct.ThrowIfCancellationRequested(); - var result = SettingsAttributeData.CreateAssemblySettingsWithDefaults(ctx.Attributes[0]); - return result; - }) - .Collect() - .WithCollectionComparer() - .Select((attributes, ct) => - { - ct.ThrowIfCancellationRequested(); - var result = attributes.Length > 0 ? - attributes[0] : - SettingsAttributeData.Default; - - return result; - }).Select((data, ct) => - { - ct.ThrowIfCancellationRequested(); - var attribute = UnionTypeSettingsAttribute.TryCreate(data, out var a) ? - a : - new(); - - ct.ThrowIfCancellationRequested(); - var settings = SettingsModel.Create(attribute!); - - return (data, settings); - }); - private static SettingsMapProvider CreateSettingsProvider(IncrementalGeneratorInitializationContext context) => - context.SyntaxProvider.ForAttributeWithMetadataName< - (TypeSignatureModel? targetSignature, SettingsAttributeData Settings)>( - UnionTypeSettingsAttribute.MetadataName, - Qualifications.IsUnionTypeDeclarationSyntax, - (ctx, ct) => - { - ct.ThrowIfCancellationRequested(); - var settings = SettingsAttributeData.CreateDeclaredSettings(ctx, ct); - var targetSignature = ctx.TargetSymbol is ITypeSymbol type ? - TypeSignatureModel.Create(type, ct) : - null; - - var result = (targetSignature, settings); - - return result; - }).Where(t => t.targetSignature != null) - .Collect() - .WithCollectionComparer() - .Select((tuples, ct) => - { - //convert to equatable for combine result to be equatable - ct.ThrowIfCancellationRequested(); - return tuples.AsEquatable(); - }) - .Combine(CreateAssemblySettingsProvider(context)) - .Select((tuple, ct) => - { - ct.ThrowIfCancellationRequested(); - var (declaredSettingsTuples, assemblySettingsTuple) = tuple; - var (assemblySettingsData, assemblySettings) = assemblySettingsTuple; - - var definedSettings = new Dictionary(); - foreach(var (target, declaredSettings) in declaredSettingsTuples) - { - ct.ThrowIfCancellationRequested(); - - if(target == null || definedSettings.ContainsKey(target)) - continue; - - var defaultsAppliedSettings = SettingsAttributeData.CreateDeclaredSettingsWithDefaults( - declaredSettings, - assemblySettingsData); - - if(!UnionTypeSettingsAttribute.TryCreate(defaultsAppliedSettings, out var parsedSettings)) - continue; - - var settings = SettingsModel.Create(parsedSettings!); - - definedSettings.Add(target, settings); - } - - return (assemblySettings, definedSettings.AsEquatable()); - }); - #endregion - #region Factory FAWMN - private static FactoryMapProvider CreateFactoryMapProvider(IncrementalGeneratorInitializationContext context) => - context.SyntaxProvider.ForAttributeWithMetadataName< - (TypeSignatureModel targetSignature, TypeSignatureModel representableTypeSignature, FactoryModel factory)?>( - UnionTypeFactoryAttribute.MetadataName, - Qualifications.IsUnionTypeFactoryDeclarationSyntax, - (ctx, ct) => - { - //transform factory to (containing union type, representable type to create union with, factory model) - - ct.ThrowIfCancellationRequested(); - //check target is valid parameter in method symbol - if(ctx.TargetSymbol is not IParameterSymbol - { - ContainingSymbol: IMethodSymbol - { - ReturnType.NullableAnnotation: NullableAnnotation.NotAnnotated or NullableAnnotation.None - } - }) - { - return null; - } - - ct.ThrowIfCancellationRequested(); - //check method returns non nullable union type - var method = (IMethodSymbol)ctx.TargetSymbol.ContainingSymbol; - if(!method.ReturnType.Equals(method.ContainingType, SymbolEqualityComparer.Default)) - { - return null; - } - - ct.ThrowIfCancellationRequested(); - var targetSignature = TypeSignatureModel.Create(method.ContainingType, ct); - var representableType = ( (IParameterSymbol)ctx.TargetSymbol ).Type; - var representableTypeSignature = TypeSignatureModel.Create(representableType, ct); - var factory = FactoryModel.CreateCustom(method.Name, representableType, ct); - - return (targetSignature, representableTypeSignature, factory); - }).Where(t => t.HasValue) - .Collect() - .WithCollectionComparer() - .Select((tuples, ct) => - { - ct.ThrowIfCancellationRequested(); - - var mutableResult = new Dictionary set, List list)>(); - var result = new Dictionary>(); - for(var i = 0; i < tuples.Length; i++) - { - var (targetSignature, representableTypeSignature, factory) = tuples[i]!.Value; - if(!mutableResult.TryGetValue(targetSignature, out var mutableItem)) - { - mutableItem = ([], []); - mutableResult.Add(targetSignature, mutableItem); - result.Add(targetSignature, new(mutableItem.list)); - } - - if(mutableItem.set.Add(representableTypeSignature)) - mutableItem.list.Add(factory); - } - - //Map(UnionTypeSignature, Map(RepresentableTypeSignature, Factory)) - return result.AsEquatable(); - }); - #endregion - #region UnionType FAWMN - private static PartialUnionTypeModelsProvider CreateTypeDeclarationTargetProvider(IncrementalGeneratorInitializationContext context, String name) => - context.SyntaxProvider.ForAttributeWithMetadataName( - name, - Qualifications.IsUnionTypeDeclarationSyntax, - PartialUnionTypeModel.CreateFromTypeDeclaration) - .Collect() - .WithCollectionComparer() - .Select((models, ct) => - { - ct.ThrowIfCancellationRequested(); - var result = models.SelectMany(m => m).ToEquatableList(ct); - return result; - }); - private static PartialUnionTypeModelsProvider CreateTypeParameterTargetProvider(IncrementalGeneratorInitializationContext context) => - context.SyntaxProvider.ForAttributeWithMetadataName( - Qualifications.NonGenericFullMetadataName, - Qualifications.IsUnionTypeParameterSyntax, - PartialUnionTypeModel.CreateFromTypeParameter) - .Where(m => m != null) - .Collect() - .WithCollectionComparer() - .Select((models, ct) => - { - ct.ThrowIfCancellationRequested(); - return models.AsEquatable(); - })!; - private static PartialUnionTypeModelsProvider CreateUnionTypeProvider(IncrementalGeneratorInitializationContext context) => - Qualifications.GenericMetadataNames - .Select(name => CreateTypeDeclarationTargetProvider(context, name)) - .Aggregate( - CreateTypeParameterTargetProvider(context), - (leftProvider, rightProvider) => - leftProvider.Combine(rightProvider) - .Select((models, ct) => - { - ct.ThrowIfCancellationRequested(); - var result = models.Left.Concat(models.Right).ToEquatableList(ct); - return result; - })); - #endregion - #region Constant Sources - private static void RegisterConstantSources(IncrementalGeneratorPostInitializationContext context) - { - context.CancellationToken.ThrowIfCancellationRequested(); - context.AddSource("RhoMicro_CodeAnalysis_UnionTypeAttribute.g.cs", AliasedUnionTypeBaseAttribute.SourceText); - context.AddSource("RhoMicro_CodeAnalysis_RelationTypeAttribute.g.cs", RelationAttribute.SourceText); - context.AddSource("RhoMicro_CodeAnalysis_UnionFactoryAttribute.g.cs", UnionTypeFactoryAttribute.SourceText); - context.AddSource("RhoMicro_CodeAnalysis_UnionTypeSettingsAttribute.g.cs", UnionTypeSettingsAttribute.SourceText); - context.AddSource("RhoMicro_CodeAnalysis_UnionsGenerator_Generated_Util.g.cs", ConstantSources.Util); - } - #endregion -} diff --git a/UnionsGenerator/Models/FactoryModel.cs b/UnionsGenerator/Models/FactoryModel.cs deleted file mode 100644 index fd221d5..0000000 --- a/UnionsGenerator/Models/FactoryModel.cs +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Models; - -using System.Collections.Immutable; - -using Microsoft.CodeAnalysis; - -using RhoMicro.CodeAnalysis.UnionsGenerator.Transformation.Visitors; -using RhoMicro.CodeAnalysis.UnionsGenerator.Utils; - -internal sealed record FactoryModel( - Boolean RequiresGeneration, - String Name, - TypeSignatureModel Parameter, - EquatedData> Locations) : IModel -{ - public static EquatableList Create(INamedTypeSymbol targetSymbol, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var result = targetSymbol.GetMembers() - .OfType() - .Where(Qualifications.IsUnionTypeFactorySymbol) - .Select(m => CreateCustom(m.Name, m.Parameters[0].Type, cancellationToken)) - .ToEquatableList(cancellationToken); - - return result; - } - - private static FactoryModel CreateCustom(String name, ITypeSymbol parameterType, ImmutableArray location, CancellationToken cancellationToken) => - new(RequiresGeneration: false, name, TypeSignatureModel.Create(parameterType, cancellationToken), location); - public static FactoryModel CreateCustom(String name, ITypeSymbol parameterType, CancellationToken cancellationToken) => - CreateCustom(name, parameterType, ImmutableArray.Empty, cancellationToken); - public static FactoryModel CreateCustom(IMethodSymbol parameterType, CancellationToken cancellationToken) => - CreateCustom(parameterType.Name, parameterType.ContainingType ?? throw new ArgumentException($"Containing type of {nameof(parameterType)} must not be null.", nameof(parameterType)), parameterType.Locations, cancellationToken); - public static FactoryModel CreateGenerated(PartialRepresentableTypeModel model) => - new(RequiresGeneration: true, model.FactoryName , model.Signature, ImmutableArray.Empty); - public void Receive(TVisitor visitor) - where TVisitor : IVisitor - => visitor.Visit(this); -} diff --git a/UnionsGenerator/Models/GroupModel.cs b/UnionsGenerator/Models/GroupModel.cs deleted file mode 100644 index 58dc5a6..0000000 --- a/UnionsGenerator/Models/GroupModel.cs +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Models; - -using System; - -using RhoMicro.CodeAnalysis.UnionsGenerator.Transformation.Visitors; - -internal sealed record GroupModel(String Name, EquatableSet Members) : IModel -{ - public void Receive(TVisitor visitor) - where TVisitor : IVisitor - => visitor.Visit(this); -} diff --git a/UnionsGenerator/Models/GroupsModel.cs b/UnionsGenerator/Models/GroupsModel.cs deleted file mode 100644 index 65caac6..0000000 --- a/UnionsGenerator/Models/GroupsModel.cs +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Models; -using System; -using System.Collections.Generic; -using System.Collections.Immutable; - -using RhoMicro.CodeAnalysis.UnionsGenerator.Transformation.Visitors; - -internal sealed class GroupsModel : IModel -{ - private GroupsModel(EquatableDictionary map) => _map = map; - private readonly EquatableDictionary _map; - - public static GroupsModel Create(EquatableList representableTypes) - { - var map = new Dictionary.Builder>(); - - foreach(var (group, representableType) in representableTypes.SelectMany(t => t.Groups.Select(g => (g, t)))) - { - if(!map.TryGetValue(group, out var signatures)) - { - signatures = ImmutableHashSet.CreateBuilder(); - map.Add(group, signatures); - } - - _ = signatures.Add(representableType); - } - - var resultMap = map - .Select(kvp => - ( - name: kvp.Key, - model: new GroupModel(kvp.Key, kvp.Value.ToImmutable().AsEquatable() - ))) - .ToDictionary(t => t.name, t => t.model) - .AsEquatable(); - - var result = new GroupsModel(resultMap); - - return result; - } - - public GroupModel this[String name] => _map[name]; - public GroupModel this[GroupModel group] => this[group.Name]; - public IEnumerable Groups => _map.Values; - public IEnumerable Names => _map.Keys; - - public override Boolean Equals(Object? obj) => - ReferenceEquals(obj, this) - || obj is GroupsModel model - && EqualityComparer>.Default.Equals(_map, model._map); - public override Int32 GetHashCode() => - -2013957080 + EqualityComparer>.Default.GetHashCode(_map); - public void Receive(TVisitor visitor) - where TVisitor : IVisitor - => visitor.Visit(this); - -} diff --git a/UnionsGenerator/Models/IModel.cs b/UnionsGenerator/Models/IModel.cs deleted file mode 100644 index 9c5bc40..0000000 --- a/UnionsGenerator/Models/IModel.cs +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Models; - -using RhoMicro.CodeAnalysis.UnionsGenerator.Transformation.Visitors; - -/// -/// Represents a base model, capable of receiving visitors for Open/Closed-compliant extension of functionality. -/// -/// The model type itself (CRTP). -public interface IModel -{ - /// - /// Receives a visitor. - /// - /// - /// Implementations should simply call the visitors - /// method, where T is .
- /// For example:
- /// - /// class MyModel : IModel<MyModel> - /// { - /// public void Receive<TVisitor>(TVisitor visitor) - /// where TVisitor : IVisitor<MyModel> - /// => visitor.Receive(this); - /// } - /// - ///
- /// The type of visitor to receive. - /// The visitor to receive. - void Receive(TVisitor visitor) - where TVisitor : IVisitor; -} diff --git a/UnionsGenerator/Models/NamedOrTypeParameterType.cs b/UnionsGenerator/Models/NamedOrTypeParameterType.cs deleted file mode 100644 index b12d6ec..0000000 --- a/UnionsGenerator/Models/NamedOrTypeParameterType.cs +++ /dev/null @@ -1,93 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Models; - -using Microsoft.CodeAnalysis; - -internal readonly struct TypeOrTypeParameterType : IEquatable -{ - private readonly ITypeSymbol? _type; - private readonly ITypeParameterSymbol? _typeParameter; - private readonly Byte _tag; - private const Int32 _namedTag = 1; - private const Int32 _typeParameterTag = 2; - - public TypeOrTypeParameterType(ITypeSymbol named) - { - _type = named; - _tag = _namedTag; - } - public TypeOrTypeParameterType(ITypeParameterSymbol typeParameter) - { - _typeParameter = typeParameter; - _tag = _typeParameterTag; - } - - public Boolean IsNamed => _tag == _namedTag; - public Boolean Is(out ITypeSymbol? named) - { - var result = _tag == _namedTag; - named = result ? _type! : default; - - return result; - } - public Boolean IsTypeParameter => _tag == _typeParameterTag; - public Boolean Is(out ITypeParameterSymbol? typeParameter) - { - var result = _tag == _typeParameterTag; - typeParameter = result ? _typeParameter! : default; - - return result; - } - - public T Match(Func onType, Func onTypeParameter) => - _tag switch - { - _namedTag => onType(_type!), - _typeParameterTag => onTypeParameter(_typeParameter!), - _ => throw new InvalidOperationException("The union type was not initialized correctly.") - }; - public ITypeSymbol UnifiedType => Match(n => n, p => p); - public void Switch(Action onNamed, Action onTypeParameter) - { - switch(_tag) - { - case _namedTag: - onNamed(_type!); - return; - case _typeParameterTag: - onTypeParameter(_typeParameter!); - return; - default: - throw new InvalidOperationException("The union type was not initialized correctly."); - } - } - - public override Boolean Equals(Object? obj) => - obj is TypeOrTypeParameterType type && Equals(type); - public Boolean Equals(TypeOrTypeParameterType other) => - _tag == other._tag && - _tag switch - { - _namedTag => SymbolEqualityComparer.Default.Equals(_type, other._type), - _typeParameterTag => SymbolEqualityComparer.Default.Equals(_typeParameter, other._typeParameter), - _ => throw new InvalidOperationException("The union type was not initialized correctly.") - }; - - public override Int32 GetHashCode() - { - var hashCode = -903911270; - hashCode = hashCode * -1521134295 + _tag.GetHashCode(); - hashCode = hashCode * -1521134295 + _tag switch - { - _namedTag => SymbolEqualityComparer.Default.GetHashCode(_type), - _typeParameterTag => SymbolEqualityComparer.Default.GetHashCode(_typeParameter), - _ => throw new InvalidOperationException("The union type was not initialized correctly.") - }; - ; - return hashCode; - } - - public static Boolean operator ==(TypeOrTypeParameterType left, TypeOrTypeParameterType right) => left.Equals(right); - public static Boolean operator !=(TypeOrTypeParameterType left, TypeOrTypeParameterType right) => !(left == right); -} diff --git a/UnionsGenerator/Models/PartialRepresentableTypeModel.cs b/UnionsGenerator/Models/PartialRepresentableTypeModel.cs deleted file mode 100644 index b5b16be..0000000 --- a/UnionsGenerator/Models/PartialRepresentableTypeModel.cs +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Models; - -using Microsoft.CodeAnalysis; - -using RhoMicro.CodeAnalysis.UnionsGenerator.Transformation.Visitors; -using RhoMicro.CodeAnalysis.UnionsGenerator.Utils; - -using System.Collections.Immutable; -using System.Threading; - -/// -/// Models a partial model of a union types representable type. -/// -/// -/// -/// -/// -/// -/// -/// -/// -internal record PartialRepresentableTypeModel( - String Alias, - String FactoryName, - UnionTypeOptions Options, - StorageOption Storage, - EquatableList Groups, - TypeSignatureModel Signature, - Boolean OmitConversionOperators, - Boolean IsBaseClassToUnionType) : - IModel -{ - public static PartialRepresentableTypeModel Create( - String? alias, - String factoryName, - UnionTypeOptions options, - StorageOption storage, - EquatableList groups, - TypeOrTypeParameterType representableType, - INamedTypeSymbol unionType, - CancellationToken cancellationToken) - { - Throw.ArgumentNull(groups, nameof(groups)); - - cancellationToken.ThrowIfCancellationRequested(); - var isNullableAnnotated = options.HasFlag(UnionTypeOptions.Nullable); - var typeModel = TypeSignatureModel.Create(representableType.UnifiedType, isNullableAnnotated, cancellationToken); - - var nonNullAlias = alias ?? typeModel.Names.IdentifierOrHintName; - - var omitConversionOperators = IsOperatorConversionsOmitted(representableType, unionType, out var isBaseClassToUnionType, cancellationToken); - - var result = new PartialRepresentableTypeModel( - nonNullAlias, - factoryName, - options, - storage, - groups, - typeModel, - omitConversionOperators, - isBaseClassToUnionType); - - return result; - } - private static Boolean IsOperatorConversionsOmitted(TypeOrTypeParameterType representableType, INamedTypeSymbol unionType, out Boolean unionTypeInheritsRepresentableType, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - unionTypeInheritsRepresentableType = InheritsFrom(unionType, representableType.UnifiedType, cancellationToken); - if(unionTypeInheritsRepresentableType) - return true; - - //cancellationToken.ThrowIfCancellationRequested(); - //if(representableType.IsTypeParameter) - // return true; - - cancellationToken.ThrowIfCancellationRequested(); - if(representableType.UnifiedType.TypeKind == TypeKind.Interface) - return true; - - return false; - } - private static Boolean InheritsFrom(ITypeSymbol subtype, ITypeSymbol supertype, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - var baseTypes = getBaseTypes(subtype); - if(baseTypes.Contains(supertype, SymbolEqualityComparer.Default)) - return true; - - var interfaces = subtype.AllInterfaces; - return interfaces.Contains(supertype, SymbolEqualityComparer.Default); - - IEnumerable getBaseTypes(ITypeSymbol symbol) - { - cancellationToken.ThrowIfCancellationRequested(); - - var baseType = symbol.BaseType; - while(baseType != null) - { - cancellationToken.ThrowIfCancellationRequested(); - - yield return baseType; - - baseType = baseType.BaseType; - } - } - } - /// - public virtual void Receive(TVisitor visitor) - where TVisitor : IVisitor - => visitor.Visit(this); -} diff --git a/UnionsGenerator/Models/PartialUnionTypeModel.cs b/UnionsGenerator/Models/PartialUnionTypeModel.cs deleted file mode 100644 index 710a4a6..0000000 --- a/UnionsGenerator/Models/PartialUnionTypeModel.cs +++ /dev/null @@ -1,185 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Models; - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -using RhoMicro.CodeAnalysis.UnionsGenerator.Transformation.Visitors; -using RhoMicro.CodeAnalysis.UnionsGenerator.Utils; - -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using System.Threading; - -internal record PartialUnionTypeModel( - TypeSignatureModel Signature, - PartialRepresentableTypeModel RepresentableType, - Boolean IsEqualsRequired, - Boolean DoesNotImplementToString, - EquatedData> Locations) - : IModel -{ - public static EquatableList Create(INamedTypeSymbol unionType, CancellationToken cancellationToken) => - CreateFromTypeDeclaration(unionType, cancellationToken) - .Concat(unionType.TypeParameters.Select(p => - CreateFromTypeParameter(p, cancellationToken)) - .Where(m => m != null)) - .ToEquatableList(cancellationToken)!; - public static PartialUnionTypeModel? CreateFromTypeParameter(ITypeParameterSymbol target, CancellationToken cancellationToken) => - CreateFromTypeParameter( - target, - target.GetAttributes() - .Where(Qualifications.IsUnionTypeParameterAttribute) - .ToImmutableArray(), - target.ContainingType.Locations, - cancellationToken); - public static PartialUnionTypeModel? CreateFromTypeParameter(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) - { - //transform parent declaration of type parameter target of [UnionType] - - cancellationToken.ThrowIfCancellationRequested(); - //check that target is type param - if(context.TargetSymbol is not ITypeParameterSymbol typeParam) - return null; - - var locations = ImmutableArray.Empty; - var parent = context.TargetNode; - while(parent?.Parent is not null) - { - if(parent.Parent is TypeDeclarationSyntax tds) - { - locations = ImmutableArray.Create(tds.Identifier.GetLocation()); - break; - } - - parent = parent.Parent; - } - - var result = CreateFromTypeParameter( - typeParam, - context.Attributes, - locations, - cancellationToken); - - return result; - } - private static PartialUnionTypeModel? CreateFromTypeParameter(ITypeParameterSymbol target, ImmutableArray attributes, ImmutableArray targetLocations, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - if(attributes.Length < 1) - return null; - - cancellationToken.ThrowIfCancellationRequested(); - if(!AliasedUnionTypeBaseAttribute.TryCreate(attributes[0], out var attribute)) - return null; - - cancellationToken.ThrowIfCancellationRequested(); - var signature = TypeSignatureModel.Create(target.ContainingType, cancellationToken); - var representableType = attribute!.GetPartialModel(new(target), target.ContainingType, cancellationToken); - var containingType = target.ContainingType; - var isEqualsRequired = IsEqualsRequiredForTarget(containingType); - var doesNotImplementToString = TargetDoesNotImplementToString(containingType); - - var result = new PartialUnionTypeModel(signature, representableType, isEqualsRequired, doesNotImplementToString, targetLocations); - - return result; - } - - public static Boolean IsEqualsRequiredForTarget(INamedTypeSymbol target) => - !target - .GetMembers(nameof(Equals)) - .OfType() - .Any(m => m.Parameters.Length == 1 && - SymbolEqualityComparer.IncludeNullability.Equals( - target.WithNullableAnnotation(NullableAnnotation.Annotated), - m.Parameters[0].Type.WithNullableAnnotation(NullableAnnotation.Annotated))); - public static Boolean TargetDoesNotImplementToString(INamedTypeSymbol target) => - !target.GetMembers(nameof(ToString)) - .OfType() - .Any(m => m.Parameters.Length == 0); - - public static EquatableList CreateFromTypeDeclaration( - INamedTypeSymbol target, - CancellationToken cancellationToken) => - CreateFromTypeDeclaration( - target, - target.GetAttributes() - .Where(Qualifications.IsUnionTypeDeclarationAttribute) - .ToImmutableArray(), - cancellationToken); - public static EquatableList CreateFromTypeDeclaration(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) - { - //transform target of [UnionType] - - cancellationToken.ThrowIfCancellationRequested(); - //check that target of [UnionType] is regular struct or class - if(context.TargetSymbol is not INamedTypeSymbol target) - return EquatableList.Empty; - - var result = CreateFromTypeDeclaration(target, context.Attributes, cancellationToken); - - return result; - } - private static EquatableList CreateFromTypeDeclaration( - INamedTypeSymbol target, - ImmutableArray attributes, - CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - if(target.TypeKind is not TypeKind.Struct and not TypeKind.Class || - target.IsRecord) - { - return EquatableList.Empty; - } - - var result = attributes - .SelectMany(data => CreatePartials(data, target, cancellationToken)) - .ToEquatableList(cancellationToken); - - return result!; - } - - private static IEnumerable CreatePartials( - AttributeData attributeData, - INamedTypeSymbol target, - CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - cancellationToken.ThrowIfCancellationRequested(); - if(!AliasedUnionTypeBaseAttribute.TryCreate(attributeData, out var attribute)) - yield break; - - cancellationToken.ThrowIfCancellationRequested(); - if(attributeData.AttributeClass?.TypeArguments.Length is 0 or null) - yield break; - - var args = attributeData.AttributeClass.TypeArguments; - if(args.Length > 1) - { - //ignore invalid aliae for non-alializable attribute usages - attribute!.Alias = null; - } - - var signature = TypeSignatureModel.Create(target, cancellationToken); - var isEqualsRequired = IsEqualsRequiredForTarget(target); - var doesNotImplementToString = TargetDoesNotImplementToString(target); - - for(var i = 0; i < args.Length; i++) - { - cancellationToken.ThrowIfCancellationRequested(); - //check that T in [UnionType] is valid representable type - if(args[i] is INamedTypeSymbol named) - { - var representableType = attribute!.GetPartialModel(new(named), target, cancellationToken); - var locations = target.Locations; - yield return new(signature, representableType, isEqualsRequired, doesNotImplementToString, locations); - } - } - } - public void Receive(TVisitor visitor) - where TVisitor : IVisitor - => visitor.Visit(this); -} diff --git a/UnionsGenerator/Models/RelatedTypeModel.cs b/UnionsGenerator/Models/RelatedTypeModel.cs deleted file mode 100644 index 418a888..0000000 --- a/UnionsGenerator/Models/RelatedTypeModel.cs +++ /dev/null @@ -1,44 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Models; - -using Microsoft.CodeAnalysis; - -using RhoMicro.CodeAnalysis.UnionsGenerator.Models.Storage; -using RhoMicro.CodeAnalysis.UnionsGenerator.Transformation.Visitors; -using RhoMicro.CodeAnalysis.UnionsGenerator.Utils; - -internal sealed record RelatedTypeModel(TypeSignatureModel Signature, EquatableSet RepresentableTypeSignatures) : IModel -{ - public static RelatedTypeModel Create(INamedTypeSymbol relationSymbol, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - var representableTypeSignatures = GetRepresentableTypeSignatures(relationSymbol, cancellationToken); - var signature = TypeSignatureModel.Create(relationSymbol, cancellationToken); - var result = new RelatedTypeModel(signature, representableTypeSignatures); - - return result; - } - private static EquatableSet GetRepresentableTypeSignatures(INamedTypeSymbol relationSymbol, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - var declarationSignatures = PartialUnionTypeModel.CreateFromTypeDeclaration(relationSymbol, cancellationToken); - var typeParamSignatures = relationSymbol.TypeParameters - .Select(p => PartialUnionTypeModel.CreateFromTypeParameter(p, cancellationToken)) - .Where(m => m != null); - - var relationTypeSignature = TypeSignatureModel.Create(relationSymbol, cancellationToken); - - var result = declarationSignatures.Concat(typeParamSignatures) - .Where(m => m!.Signature.Equals(relationTypeSignature)) - .Where(p => p != null) - .Select(p => p!.RepresentableType.Signature) - .Distinct() - .ToEquatableSet(cancellationToken); - - return result; - } - public void Receive(TVisitor visitor) - where TVisitor : IVisitor - => visitor.Visit(this); -} diff --git a/UnionsGenerator/Models/RelationModel.cs b/UnionsGenerator/Models/RelationModel.cs deleted file mode 100644 index ab40d68..0000000 --- a/UnionsGenerator/Models/RelationModel.cs +++ /dev/null @@ -1,82 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Models; - -using Microsoft.CodeAnalysis; - -using RhoMicro.CodeAnalysis.UnionsGenerator.Transformation.Visitors; -using RhoMicro.CodeAnalysis.UnionsGenerator.Utils; - -using System; -using System.Collections.Immutable; - -internal readonly record struct RelationModel(RelatedTypeModel RelatedType, RelationType RelationType) : IModel -{ - public static EquatableList Create(INamedTypeSymbol targetSymbol, CancellationToken cancellationToken) => - targetSymbol.GetAttributes() - .Where(Qualifications.IsRelationAttribute) - .Where(a => a.AttributeClass != null) - .SelectMany(a => a.AttributeClass!.TypeArguments) - .OfType() - .Select(t => Create(targetSymbol, t, cancellationToken)) - .ToEquatableList(cancellationToken); - public static RelationModel Create( - INamedTypeSymbol targetSymbol, - INamedTypeSymbol relationSymbol, - CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - var relatedType = RelatedTypeModel.Create(relationSymbol, cancellationToken); - var relationType = GetRelationType(targetSymbol, relationSymbol, relatedType, cancellationToken); - - var result = new RelationModel(relatedType, relationType); - - return result; - } - private static RelationType GetRelationType( - INamedTypeSymbol targetType, - INamedTypeSymbol relatedType, - RelatedTypeModel relatedTypeModel, - CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var isBidirectional = relatedType - .GetAttributes() - .Where(a => - a.AttributeClass?.MetadataName == RelationAttribute.MetadataName && - a.AttributeClass.TypeArguments.Length == 1 && - SymbolEqualityComparer.Default.Equals( - a.AttributeClass.TypeArguments[0], - targetType)) - .Any(); - if(isBidirectional) - return RelationType.BidirectionalRelation; - - var relationTypes = relatedTypeModel.RepresentableTypeSignatures - .Select(s => s.Names.FullGenericNullableName) - .ToList(); - var targetTypes = RelatedTypeModel.Create(targetType, cancellationToken).RepresentableTypeSignatures - .Select(s => s.Names.FullGenericNullableName) - .ToList(); - - //is target subset of relation? - if(targetTypes.All(relationTypes.Contains)) - { - //is target congruent to relation? - return relationTypes.Count == targetTypes.Count ? - RelationType.Congruent : - RelationType.Subset; - } - - //is target superset of relation? - if(relationTypes.All(targetTypes.Contains)) - return RelationType.Superset; - - //is relation intersection of target - return relationTypes.Any(targetTypes.Contains) ? RelationType.Intersection : RelationType.Disjunct; - } - public void Receive(TVisitor visitor) - where TVisitor : IVisitor - => visitor.Visit(this); -} diff --git a/UnionsGenerator/Models/RelationType.cs b/UnionsGenerator/Models/RelationType.cs deleted file mode 100644 index aa41aa6..0000000 --- a/UnionsGenerator/Models/RelationType.cs +++ /dev/null @@ -1,81 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Models; - -/// -/// Defines the type of relations that can be defined between two union types. -/// -internal enum RelationType -{ - /// - /// There is no relation between the provided type and target type. - /// They do not share any representable types. - /// No conversion operators will be generated. - /// - Disjunct, - /// - /// The relation is defined on both the target type as well as the relation type. - /// Only for one of the two union types will conversion operators be generated. - /// - BidirectionalRelation, - /// - /// The target type is a superset of the provided type. - /// The target type may represent all of the provided types representable types. - /// This means that two conversion operations will be generated: - /// - /// - /// an implicit conversion operator from the provided type to the target type - /// - /// - /// an explicit conversion operator from the target type to the provided type - /// - /// - /// This option is not available if the provided type has already defined a relation to the target type. - /// - Superset, - /// - /// The target type is a subset of the provided type. - /// The provided type may represent all of the target types representable types. - /// This means that two conversion operations will be generated: - /// - /// - /// an implicit conversion operator from the target type to the provided type - /// - /// - /// an explicit conversion operator from the provided type to the target type - /// - /// - /// This option is not available if the provided type has already defined a relation to the target type. - /// - Subset, - /// - /// The target type intersects the provided type. - /// The target type may represent some, but not all of the provided types representable types; and vice-versa. - /// This means that two conversion operations will be generated: - /// - /// - /// an explicit conversion operator from the target type to the provided type - /// - /// - /// an explicit conversion operator from the provided type to the target type - /// - /// - /// This option is not available if the provided type has already defined a relation to the target type. - /// - Intersection, - /// - /// The target type is congruent to the provided type. - /// The target type may represent all of the provided types representable types; and vice-versa. - /// This means that two conversion operations will be generated: - /// - /// - /// an implicit conversion operator from the target type to the provided type - /// - /// - /// an implicit conversion operator from the provided type to the target type - /// - /// - /// This option is not available if the provided type has already defined a relation to the target type. - /// - Congruent -} diff --git a/UnionsGenerator/Models/RepresentableTypeModel.cs b/UnionsGenerator/Models/RepresentableTypeModel.cs deleted file mode 100644 index 52d7eeb..0000000 --- a/UnionsGenerator/Models/RepresentableTypeModel.cs +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Models; - -using System.Collections.Immutable; -using System.Threading; - -using Microsoft.CodeAnalysis; - -using RhoMicro.CodeAnalysis.UnionsGenerator.Models.Storage; -using RhoMicro.CodeAnalysis.UnionsGenerator.Utils; - -/// -/// Models a representable type. -/// -/// -/// -/// -/// -/// -/// -/// -/// -/// -/// -internal sealed record RepresentableTypeModel( - String Alias, - String FactoryName, - UnionTypeOptions Options, - StorageOption Storage, - EquatableList Groups, - TypeSignatureModel Signature, - Boolean OmitConversionOperators, - Boolean IsBaseClassToUnionType, - FactoryModel Factory, - EquatedData StorageStrategy) : - PartialRepresentableTypeModel(Alias, FactoryName, Options, Storage, Groups, Signature, OmitConversionOperators, IsBaseClassToUnionType) -{ - /// - /// Creates a new representable type model. - /// - /// - /// - /// - /// - /// - public static RepresentableTypeModel Create( - PartialRepresentableTypeModel partialModel, - FactoryModel factory, - StorageStrategy strategy, - CancellationToken cancellationToken) - { - Throw.ArgumentNull(partialModel, nameof(partialModel)); - - cancellationToken.ThrowIfCancellationRequested(); - var result = new RepresentableTypeModel( - partialModel.Alias, - partialModel.FactoryName, - partialModel.Options, - partialModel.Storage, - partialModel.Groups, - partialModel.Signature, - partialModel.OmitConversionOperators, - partialModel.IsBaseClassToUnionType, - factory, - strategy); - - return result; - } - /// - public override void Receive(TVisitor visitor) - => visitor.Visit(this); -} diff --git a/UnionsGenerator/Models/SettingsModel.cs b/UnionsGenerator/Models/SettingsModel.cs deleted file mode 100644 index 6caacbe..0000000 --- a/UnionsGenerator/Models/SettingsModel.cs +++ /dev/null @@ -1,118 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Models; - -using System; -using System.Collections.Immutable; - -using Microsoft.CodeAnalysis; - -using RhoMicro.CodeAnalysis.UnionsGenerator.Generators; -using RhoMicro.CodeAnalysis.UnionsGenerator.Transformation.Visitors; -using RhoMicro.CodeAnalysis.UnionsGenerator.Utils; - -internal sealed record SettingsModel( -#region Settings - ToStringSetting ToStringSetting, - LayoutSetting Layout, - DiagnosticsLevelSettings DiagnosticsLevel, - ConstructorAccessibilitySetting ConstructorAccessibility, - InterfaceMatchSetting InterfaceMatchSetting, - EqualityOperatorsSetting EqualityOperatorsSetting, - MiscellaneousSettings Miscellaneous, -#endregion -#region Strings - String TypeDeclarationPreface, - String GenericTValueName, - String TryConvertTypeName, - String MatchTypeName, - String TagTypeName, - String ValueTypeContainerTypeName, - String ValueTypeContainerName, - String ReferenceTypeContainerName, - String TagFieldName, - String TagNoneName, - String JsonConverterTypeName -#endregion - ) : IModel -{ - private EquatedData>? _reservedNames; - public Boolean IsReservedGenericTypeName(String name) => - ( _reservedNames ??= new([GenericTValueName, TryConvertTypeName, MatchTypeName]) ).Value.Contains(name); - - public static ImmutableHashSet PropertyNames { get; } = new[] - { -#region Settings - nameof(ToStringSetting), - nameof(Layout), - nameof(DiagnosticsLevel), - nameof(ConstructorAccessibility), - nameof(InterfaceMatchSetting), - nameof(EqualityOperatorsSetting), - nameof(Miscellaneous), -#endregion -#region Strings - nameof(TypeDeclarationPreface), - nameof(GenericTValueName), - nameof(TryConvertTypeName), - nameof(MatchTypeName), - nameof(TagTypeName), - nameof(ValueTypeContainerTypeName), - nameof(ValueTypeContainerName), - nameof(ReferenceTypeContainerName), - nameof(TagFieldName), - nameof(TagNoneName), - nameof(JsonConverterTypeName), -#endregion - }.ToImmutableHashSet(); - public static SettingsModel Create(INamedTypeSymbol type, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var assemblySettings = type.ContainingAssembly.GetAttributes() - .Where(Qualifications.IsUnionTypeSettingsAttribute) - .Select(SettingsAttributeData.CreateAssemblySettingsWithDefaults) - .FirstOrDefault() - ?? SettingsAttributeData.Default; - - cancellationToken.ThrowIfCancellationRequested(); - var settings = type.GetAttributes() - .Where(Qualifications.IsUnionTypeSettingsAttribute) - .Select(a => SettingsAttributeData.CreateDeclaredSettingsWithDefaults(a, assemblySettings)) - .OfUnionTypeSettingsAttribute() - .SingleOrDefault() - ?? new(); - - cancellationToken.ThrowIfCancellationRequested(); - var result = Create(settings); - - return result; - } - public static SettingsModel Create(UnionTypeSettingsAttribute attribute) => new( - #region Settings - ToStringSetting: attribute.ToStringSetting, - Layout: attribute.Layout, - DiagnosticsLevel: attribute.DiagnosticsLevel, - ConstructorAccessibility: attribute.ConstructorAccessibility, - InterfaceMatchSetting: attribute.InterfaceMatchSetting, - EqualityOperatorsSetting: attribute.EqualityOperatorsSetting, - Miscellaneous: attribute.Miscellaneous, - #endregion - #region Strings - TypeDeclarationPreface: attribute.TypeDeclarationPreface, - GenericTValueName: attribute.GenericTValueName, - TryConvertTypeName: attribute.TryConvertTypeName, - MatchTypeName: attribute.MatchTypeName, - TagTypeName: attribute.TagTypeName, - ValueTypeContainerTypeName: attribute.ValueTypeContainerTypeName, - ValueTypeContainerName: attribute.ValueTypeContainerName, - ReferenceTypeContainerName: attribute.ReferenceTypeContainerName, - TagFieldName: attribute.TagFieldName, - TagNoneName: attribute.TagNoneName, - JsonConverterTypeName: attribute.JsonConverterTypeName - #endregion - ); - public void Receive(TVisitor visitor) - where TVisitor : IVisitor - => visitor.Visit(this); -} diff --git a/UnionsGenerator/Models/SymbolDisplayFormats.cs b/UnionsGenerator/Models/SymbolDisplayFormats.cs deleted file mode 100644 index db6c771..0000000 --- a/UnionsGenerator/Models/SymbolDisplayFormats.cs +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Models; - -using Microsoft.CodeAnalysis; - -internal static class SymbolDisplayFormats -{ - public static SymbolDisplayFormat FullNullableNoContainingTypesOrNamespacesVariant { get; } = - new(SymbolDisplayGlobalNamespaceStyle.Omitted, - SymbolDisplayTypeQualificationStyle.NameOnly, - SymbolDisplayGenericsOptions.IncludeTypeParameters | SymbolDisplayGenericsOptions.IncludeVariance, - SymbolDisplayMemberOptions.None, - SymbolDisplayDelegateStyle.NameOnly, - SymbolDisplayExtensionMethodStyle.Default, - SymbolDisplayParameterOptions.None, - SymbolDisplayPropertyStyle.NameOnly, - SymbolDisplayLocalOptions.None, - SymbolDisplayKindOptions.None, - SymbolDisplayMiscellaneousOptions.ExpandNullable | - SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier | - SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers); - public static SymbolDisplayFormat FullNullableNoContainingTypesOrNamespaces { get; } = - new(SymbolDisplayGlobalNamespaceStyle.Omitted, - SymbolDisplayTypeQualificationStyle.NameOnly, - SymbolDisplayGenericsOptions.IncludeTypeParameters, - SymbolDisplayMemberOptions.None, - SymbolDisplayDelegateStyle.NameOnly, - SymbolDisplayExtensionMethodStyle.Default, - SymbolDisplayParameterOptions.None, - SymbolDisplayPropertyStyle.NameOnly, - SymbolDisplayLocalOptions.None, - SymbolDisplayKindOptions.None, - SymbolDisplayMiscellaneousOptions.ExpandNullable | SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier); - public static SymbolDisplayFormat FullNullable { get; } = - SymbolDisplayFormat.FullyQualifiedFormat - .WithMiscellaneousOptions( - /* - get rid of special types - - 10110 - NAND 00100 - => 10010 - - 10110 - &! 00100 - => 10010 - - 00100 - ^ 11111 - => 11011 - - 10110 - & 11011 - => 10010 - */ - ( SymbolDisplayFormat.FullyQualifiedFormat.MiscellaneousOptions & - ( SymbolDisplayMiscellaneousOptions.UseSpecialTypes ^ (SymbolDisplayMiscellaneousOptions)Int32.MaxValue ) ) | - SymbolDisplayMiscellaneousOptions.ExpandNullable) - .WithGenericsOptions(SymbolDisplayGenericsOptions.IncludeTypeParameters) - .WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted); -} diff --git a/UnionsGenerator/Models/TypeNamesModel.cs b/UnionsGenerator/Models/TypeNamesModel.cs deleted file mode 100644 index b6cd430..0000000 --- a/UnionsGenerator/Models/TypeNamesModel.cs +++ /dev/null @@ -1,236 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Models; - -using System; -using System.Data.SqlTypes; -using System.Security.AccessControl; - -using Microsoft.CodeAnalysis; - -using RhoMicro.CodeAnalysis.UnionsGenerator.Transformation.Visitors; -using RhoMicro.CodeAnalysis.UnionsGenerator.Utils; - -/// -/// Provides access to various required type name strings. -/// -/// -/// -/// -/// -/// -/// -/// -/// -/// -/// -/// -/// -/// -internal sealed class TypeNamesModel( - Lazy containingTypesStringLazy, - Lazy fullGenericNameLazy, - Lazy fullGenericNullableNameLazy, - Lazy fullMetadataNameLazy, - Lazy fullOpenGenericNameLazy, - Lazy genericNameLazy, - Lazy identifierOrHintNameLazy, - Lazy fullIdentifierOrHintNameLazy, - Lazy openGenericNameLazy, - Lazy typeArgsStringLazy, - Lazy commentRefStringLazy, - String name, - String @namespace) : IModel, IEquatable -{ - /// - /// Gets the string required for cref comment use. - /// - public String CommentRefString => commentRefStringLazy.Value; - /// - /// Gets the chain of containing type strings. - /// - public String ContainingTypesString => containingTypesStringLazy.Value; - /// - /// Gets the fully qualified, generic name. - /// - public String FullGenericName => fullGenericNameLazy.Value; - /// - /// Gets the fully qualified, generic name, appending a nullable ?, if the type has been declared as a nullable reference type. - /// - public String FullGenericNullableName => fullGenericNullableNameLazy.Value; - /// - /// Gets the fully qualified metadata name. - /// - public String FullMetadataName => fullMetadataNameLazy.Value; - /// - /// Gets the fully qualified, open generic name. - /// - public String FullOpenGenericName => fullOpenGenericNameLazy.Value; - /// - /// Gets the generic name. - /// - public String GenericName => genericNameLazy.Value; - /// - /// Gets a name suitable for identifiers or hints. - /// - public String IdentifierOrHintName => identifierOrHintNameLazy.Value; - /// - /// Gets a fully qualified name suitable for identifiers or hints. - /// - public String FullIdentifierOrHintName => fullIdentifierOrHintNameLazy.Value; - /// - /// Gets the open generic name. - /// - public String OpenGenericName => openGenericNameLazy.Value; - /// - /// Gets a comma-delimited list of generic arguments, enclosed in angled brackets. - /// - public String TypeArgsString => typeArgsStringLazy.Value; - - /// - /// Gets the type name. - /// - public String Name { get; } = name; - /// - /// Gets the types full enclosing namespace. - /// - public String Namespace { get; } = @namespace; - - internal void Reify() - { - _ = CommentRefString; - _ = ContainingTypesString; - _ = FullGenericName; - _ = FullMetadataName; - _ = FullOpenGenericName; - _ = GenericName; - _ = IdentifierOrHintName; - _ = FullIdentifierOrHintName; - _ = OpenGenericName; - _ = TypeArgsString; - } - - /// - /// Creates a new type names model. - /// - /// - /// - /// - /// - /// - /// - public static TypeNamesModel Create( - ITypeSymbol symbol, - EquatableList containingTypes, - EquatableList typeArgs, - Boolean isNullableAnnotated, - CancellationToken cancellationToken) - { - Throw.ArgumentNull(symbol, nameof(symbol)); - Throw.ArgumentNull(containingTypes, nameof(containingTypes)); - Throw.ArgumentNull(typeArgs, nameof(typeArgs)); - - cancellationToken.ThrowIfCancellationRequested(); - - var name = symbol.Name; - var @namespace = symbol.ContainingNamespace.IsGlobalNamespace ? - String.Empty : - symbol.ContainingNamespace.ToDisplayString(SymbolDisplayFormats.FullNullable); - - var namespacePeriod = String.IsNullOrEmpty(@namespace) ? - String.Empty : $"{@namespace}."; - - Lazy containingTypesString, typeArgsString, - fullMetadataName, fullOpenGenericName, - genericName, openGenericName, - fullGenericName, commentRefString, - identifierOrHintName, fullIdentifierOrHintName, - - containingTypesPeriod, openTypesParamString, - - fullGenericNullableName; - - containingTypesString = new(() => String.Join(".", containingTypes.Select(t => t.Names.GenericName))); - typeArgsString = new(() => typeArgs.Count > 0 ? - $"<{String.Join(", ", typeArgs.Select(a => a.IsTypeParameter ? a.Names.Name : a.Names.FullGenericName))}>" : - String.Empty); - - containingTypesPeriod = new(() => containingTypes.Count != 0 ? $"{containingTypesString.Value}." : String.Empty); - - genericName = new(() => symbol is ITypeParameterSymbol ? symbol.Name : $"{name}{typeArgsString.Value}"); - fullGenericName = new(() => symbol is ITypeParameterSymbol ? symbol.Name : $"{namespacePeriod}{containingTypesPeriod.Value}{genericName.Value}"); - - openTypesParamString = new(() => typeArgs.Count != 0 ? - $"<{String.Concat(Enumerable.Repeat(',', typeArgs.Count - 1))}>" : - String.Empty); - - openGenericName = new(() => symbol is ITypeParameterSymbol ? String.Empty : $"{name}{openTypesParamString.Value}"); - fullOpenGenericName = new(() => symbol is ITypeParameterSymbol ? String.Empty : $"{namespacePeriod}{containingTypesPeriod.Value}{openGenericName.Value}"); - - fullMetadataName = new(() => - { - var typeParamIndex = symbol is ITypeParameterSymbol p ? - $"!{p.ContainingType.TypeParameters.IndexOf(p, 0, SymbolEqualityComparer.IncludeNullability)}" : - String.Empty; - return containingTypes.Count > 0 ? - $"{containingTypes[^1].Names.FullMetadataName}{( symbol is ITypeParameterSymbol ? typeParamIndex : $"+{symbol.MetadataName}" )}" : - $"{( symbol is ITypeParameterSymbol ? String.Empty : namespacePeriod )}{( symbol is ITypeParameterSymbol ? typeParamIndex : symbol.MetadataName )}"; - }); - identifierOrHintName = new(() => symbol.ToDisplayString( - SymbolDisplayFormats.FullNullableNoContainingTypesOrNamespaces - .WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.UseAsterisksInMultiDimensionalArrays | SymbolDisplayMiscellaneousOptions.ExpandNullable)) - .Replace(" ", String.Empty) - .Replace("<", "_of_") - .Replace(">", String.Empty) - .Replace("*,", String.Empty) - .Replace("[", "_array") - .Replace("]", String.Empty) - .Replace("*", String.Empty) - .Replace(",", "_and_")); - fullIdentifierOrHintName = new(() => $"{namespacePeriod.Replace(".", "_")}{( containingTypes.Count != 0 ? $"{String.Join("_", containingTypes.Select(t => t.Names.IdentifierOrHintName))}_" : String.Empty )}{identifierOrHintName.Value}"); - - fullGenericNullableName = - isNullableAnnotated && symbol.IsReferenceType - ? new(() => $"{fullGenericName.Value}?") - : fullGenericName; - - commentRefString = new(() => fullGenericName.Value.Replace("<", "{").Replace(">", "}")); - - var result = new TypeNamesModel( - containingTypesStringLazy: containingTypesString, - fullGenericNameLazy: fullGenericName, - fullGenericNullableNameLazy: fullGenericNullableName, - fullMetadataNameLazy: fullMetadataName, - fullOpenGenericNameLazy: fullOpenGenericName, - genericNameLazy: genericName, - identifierOrHintNameLazy: identifierOrHintName, - fullIdentifierOrHintNameLazy: fullIdentifierOrHintName, - openGenericNameLazy: openGenericName, - typeArgsStringLazy: typeArgsString, - commentRefStringLazy: commentRefString, - name: name, - @namespace: @namespace); - - return result; - } - /// - public override String ToString() => FullGenericName; - /// - public override Boolean Equals(Object? obj) => Equals(obj as TypeNamesModel); - /// - public Boolean Equals(TypeNamesModel? other) => - ReferenceEquals(this, other) || - other is not null - && FullGenericName == other.FullGenericNullableName; - /// - public override Int32 GetHashCode() - { - var hashCode = -43426669; - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(FullGenericNullableName); - return hashCode; - } - /// - public void Receive(TVisitor visitor) - where TVisitor : IVisitor - => visitor.Visit(this); -} diff --git a/UnionsGenerator/Models/TypeNature.cs b/UnionsGenerator/Models/TypeNature.cs deleted file mode 100644 index 5d3a600..0000000 --- a/UnionsGenerator/Models/TypeNature.cs +++ /dev/null @@ -1,125 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Models; - -using Microsoft.CodeAnalysis; - -using System.Collections.Concurrent; - -/// -/// Enumerates atypes structural nature. -/// -// ATTENTION: order influences type order => changes are breaking -public enum TypeNature -{ - /// - /// The types nature is unknown. - /// - UnknownType, - /// - /// The type is known to be a reference type. - /// - ReferenceType, - /// - /// The type is known to be a value type that contains reference or impure value types. - /// - ImpureValueType, - /// - /// The type is known to contain only pure value types, and no reference types. - /// - PureValueType -} - -internal static class TypeNatures -{ - public static TypeNature Create(TypeOrTypeParameterType type, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - if(type.UnifiedType.IsPureValueType(cancellationToken)) - return TypeNature.PureValueType; - - return type.UnifiedType.IsValueType - ? TypeNature.ImpureValueType - : type.UnifiedType.IsReferenceType - ? TypeNature.ReferenceType - : TypeNature.UnknownType; - } - - private static readonly ConcurrentDictionary _valueTypeCache = new(SymbolEqualityComparer.Default); - - private static Boolean IsPureValueType(this ITypeSymbol symbol, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - evaluate(symbol, cancellationToken); - - if(!_valueTypeCache[symbol].HasValue) - throw new Exception($"Unable to determine whether {symbol.Name} is value type."); - - var result = _valueTypeCache[symbol]!.Value; - - return result; - - static void evaluate(ITypeSymbol symbol, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - if(_valueTypeCache.TryGetValue(symbol, out var currentResult)) - { - //cache could be initialized but undefined (null) - if(currentResult.HasValue) - //cache was not null - return; - } else - { - //initialize cache for type - _valueTypeCache[symbol] = null; - } - - if(symbol is ITypeParameterSymbol typeParam) - { - //The enum constraint guarantees an underlying integral type. - //Therefore, enum constrained type params will be pure. - //Otherwise, there is no way to determine purity. - _valueTypeCache[symbol] = - typeParam.ConstraintTypes.Any(t => - t.MetadataName == "Enum" && - t.ContainingNamespace.Name == "System"); - return; - } - - if(!symbol.IsValueType) - { - _valueTypeCache[symbol] = false; - return; - } - - var members = symbol.GetMembers(); - foreach(var member in members) - { - cancellationToken.ThrowIfCancellationRequested(); - - if(member is IFieldSymbol field && !field.IsStatic) - { - //is field type uninitialized in cache? - if(!_valueTypeCache.ContainsKey(field.Type)) - //initialize & define - evaluate(field.Type, cancellationToken); - - var fieldTypeIsValueType = _valueTypeCache[field.Type]; - if(fieldTypeIsValueType.HasValue && !fieldTypeIsValueType.Value) - { - //field type was initialized but found not to be value type - //apply transitive property - _valueTypeCache[symbol] = false; - return; - } - } - } - - //no issues found :) - _valueTypeCache[symbol] = true; - } - } -} diff --git a/UnionsGenerator/Models/TypeSignatureModel.cs b/UnionsGenerator/Models/TypeSignatureModel.cs deleted file mode 100644 index 973a154..0000000 --- a/UnionsGenerator/Models/TypeSignatureModel.cs +++ /dev/null @@ -1,208 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Models; - -using Microsoft.CodeAnalysis; - -using RhoMicro.CodeAnalysis.UnionsGenerator.Transformation.Visitors; -using RhoMicro.CodeAnalysis.Library; - -using System.Threading; -using System.Reflection; - -/// -/// Represents a types signature. -/// -/// -/// -/// -/// -/// -/// -/// -/// -/// -/// -/// -/// -internal sealed record TypeSignatureModel( - EquatableList ContainingTypes, - EquatableList TypeArgs, - String DeclarationKeyword, - Boolean IsTypeParameter, - Boolean IsGenericType, - Boolean IsInterface, - Boolean IsStatic, - Boolean IsRecord, - Boolean HasNoBaseClass, - TypeNature Nature, - Boolean IsNullableAnnotated, - TypeNamesModel Names) -{ - private Int32 _reifiedState = 0; - /// - /// Creates a new type signature model from a type symbol. - /// - /// - /// - /// - public static TypeSignatureModel Create(ITypeSymbol type, CancellationToken cancellationToken) - => Create(type, isNullableAnnotated: type is { IsReferenceType: true, NullableAnnotation: NullableAnnotation.Annotated }, cancellationToken); - /// - /// Creates a new type signature model from a type symbol. - /// - /// - /// - /// - /// - public static TypeSignatureModel Create(ITypeSymbol type, Boolean isNullableAnnotated, CancellationToken cancellationToken) - { - var result = Create(type, new(SymbolEqualityComparer.IncludeNullability), isNullableAnnotated, cancellationToken); - - result.Reify(); - - return result; - } - private static TypeSignatureModel Create( - ITypeSymbol type, - Dictionary cache, - Boolean isNullableAnnotated, - CancellationToken cancellationToken) - { - if(cache.TryGetValue(type, out var result)) - return result; - - var declarationKeyword = type switch - { - { IsRecord: true, TypeKind: TypeKind.Class } => "record class", - { IsRecord: true, TypeKind: TypeKind.Struct } => "record struct", - { TypeKind: TypeKind.Class } => "class", - { TypeKind: TypeKind.Struct } => "struct", - { TypeKind: TypeKind.Interface } => "interface", - _ => String.Empty - }; - var nature = TypeNatures.Create(new(type), cancellationToken); - var isInterface = type.TypeKind == TypeKind.Interface; - var isTypeParameter = type is ITypeParameterSymbol; - var isStatic = type.IsStatic; - var isRecord = type.IsRecord; - var isGenericType = type is INamedTypeSymbol { TypeParameters.Length: > 0 }; - var hasNoBaseClass = - type.BaseType is null or - { - SpecialType: SpecialType.System_Object or - SpecialType.System_ValueType or - SpecialType.System_Enum - }; - var containingTypes = new List(); - var equatableContainingTypes = containingTypes.AsEquatable(); - var typeArgs = new List(); - var equatableTypeArgs = typeArgs.AsEquatable(); - var names = TypeNamesModel.Create(type, equatableContainingTypes, equatableTypeArgs, isNullableAnnotated, cancellationToken); - - result = new( - ContainingTypes: equatableContainingTypes, - TypeArgs: equatableTypeArgs, - DeclarationKeyword: declarationKeyword, - IsTypeParameter: isTypeParameter, - IsGenericType: isGenericType, - IsInterface: isInterface, - IsStatic: isStatic, - IsRecord: isRecord, - HasNoBaseClass: hasNoBaseClass, - Nature: nature, - IsNullableAnnotated: isNullableAnnotated, - Names: names); - - cache.Add(type, result); - - GetContainingTypeSignatures(type, containingTypes, cache, cancellationToken); - - GetTypeArgs(type, typeArgs, cache, cancellationToken); - - return result; - } - private static void GetContainingTypeSignatures( - ITypeSymbol type, - List signatures, - Dictionary cache, - CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - appendSignature(type.ContainingType); - - void appendSignature(ITypeSymbol? symbol) - { - cancellationToken.ThrowIfCancellationRequested(); - - if(symbol == null) - return; - - appendSignature(symbol.ContainingType); - - var isNullableAnnotated = symbol is { IsReferenceType: true, NullableAnnotation: NullableAnnotation.Annotated }; - var signature = Create(symbol, cache, isNullableAnnotated, cancellationToken); - signatures.Add(signature); - } - } - private static void GetTypeArgs( - ITypeSymbol type, - List args, - Dictionary cache, - CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - if(type is not INamedTypeSymbol named) - return; - - for(var i = 0; i < named.TypeArguments.Length; i++) - { - cancellationToken.ThrowIfCancellationRequested(); - - var typeArgument = named.TypeArguments[i]; - var isNullableAnnotated = typeArgument is { IsReferenceType: true, NullableAnnotation: NullableAnnotation.Annotated }; - var arg = Create(typeArgument, cache, isNullableAnnotated, cancellationToken); - args.Add(arg); - } - } - - private void Reify() - { - if(Interlocked.CompareExchange(ref _reifiedState, 1, 0) != 0) - return; - - Names.Reify(); - - for(var i = 0; i < ContainingTypes.Count; i++) - ContainingTypes[i].Reify(); - - for(var i = 0; i < TypeArgs.Count; i++) - TypeArgs[i].Reify(); - } - public override String ToString() => $"{Nature} {Names}"; - /// - public Boolean Equals(TypeSignatureModel? other) => - ReferenceEquals(this, other) || - other != null && - Nature == other.Nature && - IsStatic == other.IsStatic && - IsRecord == other.IsRecord && - IsTypeParameter == other.IsTypeParameter && - EqualityComparer.Default.Equals(Names, other.Names); - /// - public override Int32 GetHashCode() - { - var hashCode = -1817968017; - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(DeclarationKeyword); - hashCode = hashCode * -1521134295 + IsTypeParameter.GetHashCode(); - hashCode = hashCode * -1521134295 + Nature.GetHashCode(); - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(Names); - return hashCode; - } - /// - public void Receive(TVisitor visitor) - where TVisitor : IVisitor - => visitor.Visit(this); -} diff --git a/UnionsGenerator/Models/UnionTypeModel.cs b/UnionsGenerator/Models/UnionTypeModel.cs deleted file mode 100644 index 6ee1efb..0000000 --- a/UnionsGenerator/Models/UnionTypeModel.cs +++ /dev/null @@ -1,200 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Models; - -using RhoMicro.CodeAnalysis.UnionsGenerator.Transformation.Storage; -using RhoMicro.CodeAnalysis.UnionsGenerator.Transformation.Visitors; -using RhoMicro.CodeAnalysis.UnionsGenerator.Models.Storage; -using RhoMicro.CodeAnalysis.UnionsGenerator.Utils; -using Microsoft.CodeAnalysis; -using System.Collections.Immutable; - -/// -/// Represents a user-defined union type. -/// -/// -/// -/// -/// -/// -/// -/// -/// -/// -/// -/// -internal sealed record UnionTypeModel( - TypeSignatureModel Signature, - EquatableList RepresentableTypes, - EquatableList Relations, - SettingsModel Settings, - Boolean IsGenericType, - String ScopedDataTypeName, - GroupsModel Groups, - EquatedData StrategyHostContainer, - Boolean IsEqualsRequired, - Boolean IsToStringRequired, - EquatedData> Locations) : IModel -{ - public static UnionTypeModel Create(INamedTypeSymbol unionType, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var signature = TypeSignatureModel.Create(unionType, cancellationToken); - var relations = RelationModel.Create(unionType, cancellationToken); - var settings = SettingsModel.Create(unionType, cancellationToken); - var factories = FactoryModel.Create(unionType, cancellationToken); - var isEqualsRequired = PartialUnionTypeModel.IsEqualsRequiredForTarget(unionType); - var isToStringRequired = PartialUnionTypeModel.TargetDoesNotImplementToString(unionType) && settings.ToStringSetting != ToStringSetting.None; - var partials = PartialUnionTypeModel.Create(unionType, cancellationToken) - .Select(m => m.RepresentableType) - .ToEquatableList(cancellationToken); - var locations = unionType.Locations - .Where(l => l.IsInSource && !( l.SourceTree?.FilePath.EndsWith(".g.cs") ?? true )) - .ToImmutableArray(); - - var result = Create( - signature, - partials, - factories, - relations, - settings, - isEqualsRequired, - isToStringRequired, - locations, - cancellationToken); - - return result; - } - /// - public void Receive(TVisitor visitor) - where TVisitor : IVisitor - => visitor.Visit(this); - public static UnionTypeModel Create( - TypeSignatureModel signature, - EquatableList partialRepresentableTypes, - EquatableList factories, - EquatableList relations, - SettingsModel settings, - Boolean isEqualsRequired, - Boolean doesNotImplementToString, - CancellationToken cancellationToken) => - Create( - signature, - partialRepresentableTypes, - factories, - relations, - settings, - isEqualsRequired, - doesNotImplementToString && settings.ToStringSetting != ToStringSetting.None, - ImmutableArray.Empty, - cancellationToken); - - /// - /// Creates a new union type model. - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - public static UnionTypeModel Create( - TypeSignatureModel signature, - EquatableList partialRepresentableTypes, - EquatableList factories, - EquatableList relations, - SettingsModel settings, - Boolean isEqualsRequired, - Boolean isToStringRequired, - ImmutableArray locations, - CancellationToken cancellationToken) - { - Throw.ArgumentNull(signature, nameof(signature)); - Throw.ArgumentNull(partialRepresentableTypes, nameof(partialRepresentableTypes)); - Throw.ArgumentNull(factories, nameof(factories)); - Throw.ArgumentNull(relations, nameof(relations)); - Throw.ArgumentNull(settings, nameof(settings)); - - cancellationToken.ThrowIfCancellationRequested(); - - var isGenericType = signature.TypeArgs.Count > 0; - var conversionFunctionsTypeName = $"{signature.Names.FullIdentifierOrHintName}_ScopedData{signature.Names.TypeArgsString}"; - - var factoryMap = factories.ToDictionary(f => f.Parameter); - var representableTypes = GetRepresentableTypes(partialRepresentableTypes, settings, isGenericType, factoryMap, cancellationToken); - var groups = GroupsModel.Create(representableTypes); - var hostContainer = CreateHostContainerAndReceiveStrategies(signature, settings, isGenericType, representableTypes); - - var result = new UnionTypeModel( - signature, - representableTypes, - relations, - settings, - isGenericType, - conversionFunctionsTypeName, - groups, - hostContainer, - isEqualsRequired, - isToStringRequired, - locations); - - return result; - } - - private static EquatableList GetRepresentableTypes(EquatableList partialRepresentableTypes, SettingsModel settings, Boolean isGenericType, Dictionary factoryMap, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var result = partialRepresentableTypes - .Select(p => - ( - partial: p, - factory: factoryMap.TryGetValue(p.Signature, out var f) - ? f - : FactoryModel.CreateGenerated(p), - strategy: StorageStrategy.Create(settings, isGenericType, p) - )).Select(t => RepresentableTypeModel.Create(t.partial, t.factory, t.strategy, cancellationToken)) - .ToEquatableList(cancellationToken); - - return result; - } - - private static StrategySourceHost CreateHostContainerAndReceiveStrategies(TypeSignatureModel signature, SettingsModel settings, Boolean isGenericType, EquatableList representableTypes) - { - var hostContainer = new StrategySourceHost( - settings, - signature, - isGenericType, - representableTypes); - - for(var i = 0; i < representableTypes.Count; i++) - { - representableTypes[i].StorageStrategy.Value.Visit(hostContainer); - } - - return hostContainer; - } - - public String GetSpecificAccessibility(TypeSignatureModel _) - { - var accessibility = Settings.ConstructorAccessibility; - - if(accessibility == ConstructorAccessibilitySetting.PublicIfInconvertible - //TODO: consider omissions - /*&& OperatorOmissions.AllOmissions.Contains(representableType)*/) - { - accessibility = ConstructorAccessibilitySetting.Public; - } - - var result = accessibility == ConstructorAccessibilitySetting.Public ? - "public" : - "private"; - - return result; - } -} diff --git a/UnionsGenerator/Properties/launchSettings.json b/UnionsGenerator/Properties/launchSettings.json deleted file mode 100644 index b56fe1d..0000000 --- a/UnionsGenerator/Properties/launchSettings.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "profiles": { - "Tests": { - "commandName": "DebugRoslynComponent", - "targetProject": "..\\UnionsGenerator.EndToEnd.Tests\\UnionsGenerator.EndToEnd.Tests.csproj" - }, - "TestApp": { - "commandName": "DebugRoslynComponent", - "targetProject": "..\\TestApp\\TestApp.csproj" - }, - "DslGenerator": { - "commandName": "DebugRoslynComponent", - "targetProject": "..\\DslGenerator\\DslGenerator.csproj" - } - } -} \ No newline at end of file diff --git a/UnionsGenerator/Transformation/CommentBuilder.cs b/UnionsGenerator/Transformation/CommentBuilder.cs deleted file mode 100644 index 4fc27a9..0000000 --- a/UnionsGenerator/Transformation/CommentBuilder.cs +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.Library.Text; -using System; - -using RhoMicro.CodeAnalysis.UnionsGenerator.Models; - -internal partial record CommentBuilder -{ - public IndentedStringBuilder SeeRef(UnionTypeModel model) => - SeeRef(model.Signature); - public IndentedStringBuilder SeeRef(TypeSignatureModel signature) => - SeeRef(signature.Names.CommentRefString); - public IndentedStringBuilder Ref(TypeSignatureModel typeSignature) => - typeSignature.IsTypeParameter ? - Builder.Comment.TypeParamRef(typeSignature.Names.CommentRefString) : - Builder.Comment.SeeRef(typeSignature.Names.CommentRefString); - public IndentedStringBuilder InternalUse(TypeSignatureModel signature) => - Builder.Comment.OpenRemarks() - .Append("This member is not intended for use by user code inside of or any code outside of ").Comment.Ref(signature).Append('.') - .CloseBlock(); -} diff --git a/UnionsGenerator/Transformation/IndentedStringBuilder.cs b/UnionsGenerator/Transformation/IndentedStringBuilder.cs deleted file mode 100644 index 593f3f4..0000000 --- a/UnionsGenerator/Transformation/IndentedStringBuilder.cs +++ /dev/null @@ -1,322 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.Library.Text; -using System; -using System.Collections.Immutable; - -using RhoMicro.CodeAnalysis.UnionsGenerator.Models; -using RhoMicro.CodeAnalysis.UnionsGenerator.Utils; - -internal partial class IndentedStringBuilder -{ - public IndentedStringBuilder Typeof(TypeSignatureModel type, Boolean open = false) => - Append("typeof(").Append(type.IsTypeParameter ? type.Names.Name : open ? type.Names.FullOpenGenericName : type.Names.FullGenericName).Append(')'); - public IndentedStringBuilder UtilIsMarked(String typeExpression) => - Append("RhoMicro.CodeAnalysis.UnionsGenerator.Generated.Util.IsMarked(").Append(typeExpression).Append(")"); - public IndentedStringBuilder UtilGetFullString(String typeExpression) => - Append("RhoMicro.CodeAnalysis.UnionsGenerator.Generated.Util.GetFullString(").Append(typeExpression).Append(")"); - public IndentedStringBuilder UtilGetFullString(Action typeExpression) => - Append("RhoMicro.CodeAnalysis.UnionsGenerator.Generated.Util.GetFullString(").Append(typeExpression).Append(")"); - public IndentedStringBuilder UtilGetFullString(TypeSignatureModel type) => - Append("RhoMicro.CodeAnalysis.UnionsGenerator.Generated.Util.GetFullString(").Typeof(type).Append(")"); - public IndentedStringBuilder UtilUnsafeConvert( - String tFrom, - String tTo, - String valueExpression) => - Append("(RhoMicro.CodeAnalysis.UnionsGenerator.Generated.Util.UnsafeConvert<") - .Append(tFrom).Append(", ").Append(tTo).Append(">(").Append(valueExpression).Append("))"); - public IndentedStringBuilder UtilUnsafeConvert( - String tFrom, - TypeSignatureModel tTo, - String valueExpression) => - UtilUnsafeConvert(tFrom, tTo.IsTypeParameter ? tTo.Names.Name : tTo.Names.FullGenericName, valueExpression); - public IndentedStringBuilder TagSwitchExpr( - UnionTypeModel target, - Action caseExpr, - String instanceExpr = "this", - Action? specialDefault = null) - { - var representableTypes = target.RepresentableTypes; - - if(representableTypes.Count == 1) - { - caseExpr.Invoke(this, representableTypes[0]); - - return this; - } - - Append(instanceExpr).Append('.').Append(target.Settings.TagFieldName).Append(" switch") - .OpenBracesBlock() - .AppendJoinLines(representableTypes.Append(null), (b, t) => - { - if(t == null) - { - b.AppendCore("_ => "); - if(specialDefault != null) - { - specialDefault.Invoke(b); - } else - { - b.AppendCore(ConstantSources.InvalidTagStateThrow); - } - } else - { - b.Append(target.Settings.TagTypeName).Append('.').Append(t.Alias).AppendCore(" => "); - caseExpr.Invoke(b, t); - } - }) - .CloseBlockCore(); - - return this; - } - public IndentedStringBuilder TagSwitchExpr( - UnionTypeModel target, - ImmutableHashSet representableTypesSet, - Action caseExpr, - String instanceExpr, - Action? specialDefault = null) - { - if(representableTypesSet.Count == 1) - { - caseExpr.Invoke(this, representableTypesSet.Single()); - - return this; - } - - var aliasMap = target.RepresentableTypes.ToDictionary(t => t.Signature, t => t.Alias); - - Append(instanceExpr).Append('.').Append(target.Settings.TagFieldName).Append(" switch") - .OpenBracesBlock() - .AppendJoinLines(representableTypesSet.Append(null), (b, s) => - { - if(s == null) - { - var useSpecialDefault = specialDefault != null && representableTypesSet.Count != target.RepresentableTypes.Count; - if(useSpecialDefault) - { - b.Append("> (").Append(target.Settings.TagTypeName).Append(')') - .Append(target.RepresentableTypes.Count.ToString()).Append(" => ").Append(ConstantSources.InvalidTagStateThrow).AppendCore(','); - } - - b.AppendCore("_ => "); - if(useSpecialDefault) - { - specialDefault!.Invoke(b); - } else - { - b.AppendCore(ConstantSources.InvalidTagStateThrow); - } - } else - { - b.Append(target.Settings.TagTypeName).Append('.').Append(aliasMap[s]).AppendCore(" => "); - caseExpr.Invoke(b, s); - } - }) - .CloseBlockCore(); - - return this; - } - public IndentedStringBuilder TagSwitchStmt( - UnionTypeModel target, - Action caseExpr, - String instanceExpr = "this", - Action? specialDefault = null) - { - if(target.RepresentableTypes.Count == 1) - { - caseExpr.Invoke(this, target.RepresentableTypes[0]); - - return this; - } - - Append("switch(").Append(instanceExpr).Append('.').Append(target.Settings.TagFieldName).Append(')') - .OpenBracesBlock() - .AppendJoinLines( - StringOrChar.Empty, - target.RepresentableTypes.Append(null), - (b, t) => - { - if(t == null) - { - _ = b.Append("default:").OpenBracesBlock(); - - if(specialDefault != null) - { - specialDefault.Invoke(b); - } else - { - b.Append(ConstantSources.InvalidTagStateThrow).AppendCore(';'); - } - - CloseBlockCore(); - } else - { - _ = b.Append("case ").Append(target.Settings.TagTypeName).Append('.').Append(t.Alias).Append(":") - .OpenBracesBlock(); - caseExpr.Invoke(b, t); - CloseBlockCore(); - } - }) - .CloseBlockCore(); - - return this; - } - public IndentedStringBuilder FullMetadataNameSwitchExpr( - UnionTypeModel target, - String metadataNameExpr, - Action caseExpr, - Action defaultCase) - => FullMetadataNameSwitchExpr( - target, - b => b.Append(metadataNameExpr), - caseExpr, - defaultCase); - public IndentedStringBuilder FullMetadataNameSwitchExpr( - UnionTypeModel target, - Action metadataNameExpr, - Action caseExpr, - Action defaultCase) - { - if(target.RepresentableTypes.Count == 1) - { - caseExpr.Invoke(this, target.RepresentableTypes[0]); - - return this; - } - - Append(metadataNameExpr).Append(" switch") - .OpenBracesBlock() - .AppendJoinLines(target.RepresentableTypes.Append(null), (b, t) => - { - if(t == null) - { - b.AppendCore("_ => "); - defaultCase.Invoke(b); - } else - { - b.Append('"').Append(t.Signature.Names.FullMetadataName).AppendCore("\" => "); - caseExpr.Invoke(b, t); - } - }) - .CloseBlockCore(); - - return this; - } - public IndentedStringBuilder FullMetadataNameSwitchExpr( - ImmutableHashSet representableTypesSet, - Action metadataNameExpr, - Action caseExpr, - Action defaultCase) - { - if(representableTypesSet.Count == 1) - { - caseExpr.Invoke(this, representableTypesSet.Single()); - - return this; - } - - Append(metadataNameExpr).Append(" switch") - .OpenBracesBlock() - .AppendJoinLines(representableTypesSet.Append(null), (b, s) => - { - if(s == null) - { - b.AppendCore("_ => "); - defaultCase.Invoke(b); - } else - { - b.Append('"').Append(s.Names.FullMetadataName).AppendCore("\" => "); - caseExpr.Invoke(b, s); - } - }) - .CloseBlockCore(); - - return this; - } - public IndentedStringBuilder InvalidConversionThrow(String fromTypeNameExpression, String representedTypeNameExpression, String toTypeNameExpression) => - Append("throw new System.InvalidOperationException($\"Unable to convert from an instance of {") - .Append(fromTypeNameExpression) - .Append("} representing a value of type {") - .Append(representedTypeNameExpression) - .Append("} to an instance of {") - .Append(toTypeNameExpression) - .Append("}.\")"); - public IndentedStringBuilder MetadataNameSwitchStmt( - UnionTypeModel target, - Action typeExpr, - Action caseBody, - Action defaultBody) - { - var nonTypeParamCases = new List>(); - var typeParamCases = new List>(); - - foreach(var t in target.RepresentableTypes) - { - ( t.Signature.IsTypeParameter ? typeParamCases : nonTypeParamCases ) - .Add(t.Signature.IsTypeParameter ? - b => - { - _ = b.Append("if(metadataName == ").UtilGetFullString(t.Signature).Append(")") - .OpenBracesBlock(); - caseBody.Invoke(b, t); - b.CloseBlockCore(); - } - : - b => - { - _ = b.Append("case ") - .Append('"').Append(t.Signature.Names.FullGenericName).Append("\":") - .OpenBracesBlock(); - caseBody.Invoke(b, t); - b.CloseBlockCore(); - }); - } - - typeParamCases.Add(b => b.OpenBracesBlock().Append(defaultBody).CloseBlockCore()); - - Append("var metadataName = ").UtilGetFullString(typeExpr).AppendLine(';') - .Append("switch(metadataName)") - .OpenBracesBlock() - .AppendJoinLines(StringOrChar.Empty, nonTypeParamCases) - .Append("default:") - .OpenBracesBlock() - .AppendJoin(" else ", typeParamCases) - .CloseBlock() - .CloseBlockCore(); - - return this; - } - public IndentedStringBuilder ToUnionTypeConversion( - UnionTypeModel unionType, - RepresentableTypeModel unionTypeRepresentableType, - String relationTypeParameterName) => - Append('(').Append(unionType.Signature.Names.GenericName).Append('.').Append(unionTypeRepresentableType.Factory.Name) - .Append("((").Append(unionTypeRepresentableType.Signature.Names.FullGenericNullableName).Append(')').Append(relationTypeParameterName).Append("))"); - public IndentedStringBuilder ToRelatedTypeConversion( - RelatedTypeModel relatedType, - RepresentableTypeModel unionTypeRepresentableType, - String unionTypeParameterName) => - Append("((").Append(relatedType.Signature.Names.FullGenericNullableName).Append(')') - .Append(unionTypeRepresentableType.StorageStrategy.Value.StrongInstanceVariableExpression(unionTypeParameterName)).Append(')'); - public IndentedStringBuilder GeneratedUnnavigableInternalCode(UnionTypeModel model) => - GeneratedUnnavigableInternalCode(model.Signature); - public IndentedStringBuilder GeneratedUnnavigableInternalCode(TypeSignatureModel signature) => - Comment.InternalUse(signature) - .AppendLine(ConstantSources.GeneratedCode) - .AppendLine(ConstantSources.EditorBrowsableNever); - public IndentedStringBuilder AppendJoinLines( - IEnumerable values, - Action append) - => AppendJoinLines(values.Select(v => new IndentedStringBuilderAppendable(b => append.Invoke(b, v)))); - public IndentedStringBuilder AppendJoinLines( - StringOrChar separator, - IEnumerable values, - Action append) - => AppendJoinLines(separator, values.Select(v => new IndentedStringBuilderAppendable(b => append.Invoke(b, v)))); - public IndentedStringBuilder AppendJoin( - StringOrChar separator, - IEnumerable values, - Action append) - => AppendJoin(separator, values.Select(v => new IndentedStringBuilderAppendable(b => append.Invoke(b, v)))); - -} diff --git a/UnionsGenerator/Transformation/Storage/StorageSelectionViolation.cs b/UnionsGenerator/Transformation/Storage/StorageSelectionViolation.cs deleted file mode 100644 index e868b97..0000000 --- a/UnionsGenerator/Transformation/Storage/StorageSelectionViolation.cs +++ /dev/null @@ -1,71 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Transformation.Storage; - -internal enum StorageSelectionViolation -{ - None, - /// - /// - /// TypePure Value - /// SelectedReference - /// ActualReference - /// Diagnosticwarn about definite boxing - /// - /// - PureValueReferenceSelection, - /// - /// - /// TypePure Value - /// SelectedValue - /// ActualField - /// Diagnosticignored due to generic type - /// - /// - PureValueValueSelectionGeneric, - /// - /// - /// TypeImpure Value - /// SelectedReference - /// ActualReference - /// Diagnosticwarn about definite boxing - /// - /// - ImpureValueReference, - /// - /// - /// TypeImpure Value - /// SelectedValue - /// ActualField - /// Diagnosticignored due to guaranteed tle - /// - /// - ImpureValueValue, - /// - /// - /// TypeReference - /// SelectedValue - /// ActualReference - /// Diagnosticignored due to guaranteed tle - /// - /// - ReferenceValue, - /// - /// - /// TypeUnknown - /// SelectedReference - /// ActualReference - /// Diagnosticwarn about possible boxing - /// - /// - UnknownReference, - /// - /// - /// TypeUnknown - /// SelectedValue - /// ActualField - /// Diagnosticwarn about possible tle - /// - /// - UnknownValue -} diff --git a/UnionsGenerator/Transformation/Storage/StorageStrategy.FieldContainerStrategy.cs b/UnionsGenerator/Transformation/Storage/StorageStrategy.FieldContainerStrategy.cs deleted file mode 100644 index e2bc7f5..0000000 --- a/UnionsGenerator/Transformation/Storage/StorageStrategy.FieldContainerStrategy.cs +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Models.Storage; - -using RhoMicro.CodeAnalysis.Library.Text; -using static RhoMicro.CodeAnalysis.Library.Text.IndentedStringBuilder.Appendables; - -using System; -using RhoMicro.CodeAnalysis.UnionsGenerator.Transformation.Storage; - -internal partial class StorageStrategy -{ - private sealed class FieldContainerStrategy( - SettingsModel settings, - PartialRepresentableTypeModel representableType, - StorageOption selectedOption, - StorageSelectionViolation violation) - : StorageStrategy(settings, representableType, selectedOption, violation) - { - public override StorageOption ActualOption => StorageOption.Field; - public override IndentedStringBuilderAppendable ConvertedInstanceVariableExpression( - String targetType, - String instance) => - UtilUnsafeConvert(RepresentableType.Signature.Names.FullGenericName, targetType, $"{instance}.{FieldName}{NullableFieldBang}"); - public override IndentedStringBuilderAppendable StrongInstanceVariableExpression( - String instance) => - new(b => _ = b.Operators + '(' + instance + '.' + FieldName + NullableFieldBang + ')'); - public override IndentedStringBuilderAppendable InstanceVariableAssignmentExpression( - String valueExpression, - String instance) => - new(b => _ = b.Operators + instance + '.' + FieldName + " = " + valueExpression); - public override IndentedStringBuilderAppendable InstanceVariableExpression( - String instance) => - StrongInstanceVariableExpression(instance); - - public override void Visit(StrategySourceHost host) => host.AddDedicatedField(this); - } -} diff --git a/UnionsGenerator/Transformation/Storage/StorageStrategy.ReferenceContainerStrategy.cs b/UnionsGenerator/Transformation/Storage/StorageStrategy.ReferenceContainerStrategy.cs deleted file mode 100644 index 32bb6a5..0000000 --- a/UnionsGenerator/Transformation/Storage/StorageStrategy.ReferenceContainerStrategy.cs +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Models.Storage; - -using RhoMicro.CodeAnalysis.Library.Text; -using RhoMicro.CodeAnalysis.UnionsGenerator.Transformation.Storage; - -internal partial class StorageStrategy -{ - private sealed class ReferenceContainerStrategy( - SettingsModel settings, - PartialRepresentableTypeModel representableType, - StorageOption selectedOption, - StorageSelectionViolation violation) : StorageStrategy(settings, representableType, selectedOption, violation) - { - public override StorageOption ActualOption { get; } = StorageOption.Reference; - - public override IndentedStringBuilderAppendable ConvertedInstanceVariableExpression(String targetType, String instance) => - new(b => b.UtilUnsafeConvert(RepresentableType.Signature.Names.FullGenericNullableName, targetType, $"({RepresentableType.Signature.Names.FullGenericNullableName}){instance}.{Settings.ReferenceTypeContainerName}{NullableFieldBang}")); - public override IndentedStringBuilderAppendable InstanceVariableAssignmentExpression(String valueExpression, String instance) => - new(b => b.Append(instance).Append('.').Append(Settings.ReferenceTypeContainerName).Append(" = ").Append(valueExpression)); - public override IndentedStringBuilderAppendable InstanceVariableExpression(String instance) => - new(b => b.Append('(').Append(instance).Append('.').Append(Settings.ReferenceTypeContainerName).Append(NullableFieldBang).Append(')')); - public override IndentedStringBuilderAppendable StrongInstanceVariableExpression(String instance) => - new(b => b.Append("((").Append(RepresentableType.Signature.Names.FullGenericNullableName).Append(')').Append(instance).Append('.').Append(Settings.ReferenceTypeContainerName).Append(NullableFieldBang).Append(')')); - public override void Visit(StrategySourceHost host) => host.AddReferenceTypeContainerField(); - } -} diff --git a/UnionsGenerator/Transformation/Storage/StorageStrategy.ValueContainerStrategy.cs b/UnionsGenerator/Transformation/Storage/StorageStrategy.ValueContainerStrategy.cs deleted file mode 100644 index d4afffc..0000000 --- a/UnionsGenerator/Transformation/Storage/StorageStrategy.ValueContainerStrategy.cs +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Models.Storage; - -using System; - -using RhoMicro.CodeAnalysis.Library.Text; -using RhoMicro.CodeAnalysis.UnionsGenerator.Transformation.Storage; - -internal partial class StorageStrategy -{ - private sealed class ValueContainerStrategy( - SettingsModel settings, - PartialRepresentableTypeModel representableType, - StorageOption selectedOption, - StorageSelectionViolation violation) - : StorageStrategy(settings, representableType, selectedOption, violation) - { - public override StorageOption ActualOption => StorageOption.Value; - public override IndentedStringBuilderAppendable ConvertedInstanceVariableExpression( - String targetType, - String instance) => - IndentedStringBuilder.Appendables.UtilUnsafeConvert( - RepresentableType.Signature.Names.FullGenericNullableName, - targetType, - $"{instance}.{Settings.ValueTypeContainerName}.{RepresentableType.Alias}"); - public override IndentedStringBuilderAppendable StrongInstanceVariableExpression( - String instance) => - new(b => _ = b.Operators + '(' + instance + '.' + Settings.ValueTypeContainerName + '.' + RepresentableType.Alias + ')'); - public override IndentedStringBuilderAppendable InstanceVariableAssignmentExpression( - String valueExpression, - String instance) => - new(b => _ = b.Operators + instance + '.' + Settings.ValueTypeContainerName + " = new(" + valueExpression + ')'); - public override IndentedStringBuilderAppendable InstanceVariableExpression( - String instance) => - StrongInstanceVariableExpression(instance); - - public override void Visit(StrategySourceHost host) - { - host.AddValueTypeContainerField(); - host.AddValueTypeContainerType(); - host.AddValueTypeContainerInstanceFieldAndCtor(this); - } - } -} diff --git a/UnionsGenerator/Transformation/Storage/StorageStrategy.cs b/UnionsGenerator/Transformation/Storage/StorageStrategy.cs deleted file mode 100644 index c871978..0000000 --- a/UnionsGenerator/Transformation/Storage/StorageStrategy.cs +++ /dev/null @@ -1,162 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Models.Storage; - -using System; - -using RhoMicro.CodeAnalysis.Library.Text; -using RhoMicro.CodeAnalysis.UnionsGenerator.Transformation.Storage; - -using static RhoMicro.CodeAnalysis.Library.Text.IndentedStringBuilder.Appendables; - -internal abstract partial class StorageStrategy -{ - #region Constructor - private StorageStrategy( - SettingsModel settings, - PartialRepresentableTypeModel representableType, - StorageOption selectedOption, - StorageSelectionViolation violation) - { - Settings = settings; - RepresentableType = representableType; - SelectedOption = selectedOption; - Violation = violation; - FieldName = representableType.Alias.ToGeneratedCamelCase(); - NullableFieldBang = RepresentableType.Options.HasFlag(UnionTypeOptions.Nullable) ? String.Empty : "!"; - NullableFieldQuestionMark = RepresentableType.Signature is { Nature: TypeNature.UnknownType or TypeNature.ReferenceType } ? "?" : String.Empty; - } - #endregion - #region Fields - public String FieldName { get; } - protected SettingsModel Settings { get; } - public PartialRepresentableTypeModel RepresentableType { get; } - public StorageOption SelectedOption { get; } - public abstract StorageOption ActualOption { get; } - public StorageSelectionViolation Violation { get; } - protected String NullableFieldBang { get; } - public String NullableFieldQuestionMark { get; } - #endregion - #region Factory - public static StorageStrategy Create( - SettingsModel settings, - Boolean unionTypeIsGeneric, - PartialRepresentableTypeModel representableType) - { - var selectedOption = representableType.Storage; - var result = representableType.Signature.Nature switch - { - TypeNature.PureValueType => createForPureValueType(), - TypeNature.ImpureValueType => createForImpureValueType(), - TypeNature.ReferenceType => createForReferenceType(), - _ => createForUnknownType(), - }; - - return result; - - StorageStrategy createReference(StorageSelectionViolation violation = StorageSelectionViolation.None) => - new ReferenceContainerStrategy(settings, representableType, selectedOption, violation); - StorageStrategy createValue(StorageSelectionViolation violation = StorageSelectionViolation.None) => - new ValueContainerStrategy(settings, representableType, selectedOption, violation); - StorageStrategy createField(StorageSelectionViolation violation = StorageSelectionViolation.None) => - new FieldContainerStrategy(settings, representableType, selectedOption, violation); - - /* - read tables like so: - type nature | selected strategy | generic strat(diag) : nongeneric strat(diag) - */ - - /* - PureValue Reference => reference(box) - Value => field(generic) : value - Field => field - Auto => field : value - */ - StorageStrategy createForPureValueType() => - selectedOption switch - { - StorageOption.Reference => createReference(StorageSelectionViolation.PureValueReferenceSelection), - StorageOption.Value => unionTypeIsGeneric ? createField(StorageSelectionViolation.PureValueValueSelectionGeneric) : createValue(), - StorageOption.Field => createField(), - _ => unionTypeIsGeneric ? createField() : createValue() - }; - /* - ImpureValue Reference => reference(box) - Value => field(tle) - Field => field - Auto => field - */ - StorageStrategy createForImpureValueType() => - selectedOption switch - { - StorageOption.Reference => createReference(StorageSelectionViolation.ImpureValueReference), - StorageOption.Value => createField(StorageSelectionViolation.ImpureValueValue), - StorageOption.Field => createField(), - _ => createField() - }; - /* - Reference Reference => reference - Value => reference(tle) - Field => field - Auto => reference - */ - StorageStrategy createForReferenceType() => - selectedOption switch - { - StorageOption.Reference => createReference(), - StorageOption.Value => createReference(StorageSelectionViolation.ReferenceValue), - StorageOption.Field => createField(), - _ => createReference() - }; - /* - Unknown Reference => reference(pbox) - Value => field(ptle) - Field => field - Auto => field - */ - StorageStrategy createForUnknownType() => - selectedOption switch - { - StorageOption.Reference => createReference(StorageSelectionViolation.UnknownReference), - StorageOption.Value => createField(StorageSelectionViolation.UnknownValue), - StorageOption.Field => createField(), - _ => createField() - }; - } - #endregion - #region Template Methods - public abstract IndentedStringBuilderAppendable InstanceVariableExpression(String instance); - public abstract IndentedStringBuilderAppendable StrongInstanceVariableExpression(String instance); - public abstract IndentedStringBuilderAppendable ConvertedInstanceVariableExpression(String targetType, String instance); - public abstract IndentedStringBuilderAppendable InstanceVariableAssignmentExpression(String valueExpression, String instance); - #endregion - - public IndentedStringBuilderAppendable EqualsInvocation(String instance, String otherInstance) => - new(b => _ = b.Operators + '(' + "System.Collections.Generic.EqualityComparer<" + RepresentableType.Signature.Names.FullGenericNullableName + - ">.Default.Equals(" + StrongInstanceVariableExpression(instance) + ", " + StrongInstanceVariableExpression(otherInstance) + "))"); - - public IndentedStringBuilderAppendable EqualsInvocation(String otherInstance) => EqualsInvocation("this", otherInstance); - - public IndentedStringBuilderAppendable InstanceVariableExpression() => InstanceVariableExpression("this"); - public IndentedStringBuilderAppendable GetHashCodeInvocation(String instance) => - new(b => _ = b.Operators + - "(System.Collections.Generic.EqualityComparer<" + RepresentableType.Signature.Names.FullGenericNullableName + ">.Default.GetHashCode(" + StrongInstanceVariableExpression(instance) + "))"); - public IndentedStringBuilderAppendable GetHashCodeInvocation() => - GetHashCodeInvocation("this"); - - public IndentedStringBuilderAppendable TypesafeInstanceVariableExpression() - => StrongInstanceVariableExpression("this"); - - public IndentedStringBuilderAppendable ConvertedInstanceVariableExpression(String targetType) - => ConvertedInstanceVariableExpression(targetType, "this"); - - public IndentedStringBuilderAppendable InstanceVariableAssignmentExpression(String valueExpression) - => InstanceVariableAssignmentExpression(valueExpression, "this"); - - public IndentedStringBuilderAppendable ToStringInvocation(String instance) => - new(b => _ = b.Operators + '(' + InstanceVariableExpression(instance) + NullableFieldQuestionMark + ".ToString() ?? System.String.Empty)"); - public IndentedStringBuilderAppendable ToStringInvocation() - => ToStringInvocation("this"); - - public abstract void Visit(StrategySourceHost host); -} diff --git a/UnionsGenerator/Transformation/Storage/StrategySourceHost.cs b/UnionsGenerator/Transformation/Storage/StrategySourceHost.cs deleted file mode 100644 index f6671a2..0000000 --- a/UnionsGenerator/Transformation/Storage/StrategySourceHost.cs +++ /dev/null @@ -1,175 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Transformation.Storage; - -using RhoMicro.CodeAnalysis.Library; -using RhoMicro.CodeAnalysis.Library.Text; -using static RhoMicro.CodeAnalysis.Library.Text.IndentedStringBuilder.Appendables; - -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using RhoMicro.CodeAnalysis.UnionsGenerator.Models; -using RhoMicro.CodeAnalysis.UnionsGenerator.Models.Storage; -using RhoMicro.CodeAnalysis.UnionsGenerator.Utils; -using System.Reflection; - -internal sealed class StrategySourceHost( - SettingsModel settings, - TypeSignatureModel unionTypeSignature, - Boolean unionTypeIsGeneric, - EquatableList representableTypes) -{ - private readonly SettingsModel _settings = settings ?? throw new ArgumentNullException(nameof(settings)); - private readonly EquatableList _representableTypes = representableTypes ?? throw new ArgumentNullException(nameof(representableTypes)); - - private readonly List<(IndentedStringBuilderAppendable Appendable, String TypeName)> _dedicatedReferenceFieldAdditions = []; - private readonly List<(IndentedStringBuilderAppendable Appendable, String TypeName)> _dedicatedPureValueTypeFieldAdditions = []; - private readonly List<(IndentedStringBuilderAppendable Appendable, String TypeName)> _dedicatedImpureAndUnknownFieldAdditions = []; - public void AddDedicatedField(StorageStrategy strategy) - { - var fullName = strategy.RepresentableType.Signature.Names.FullGenericName; - ( strategy.RepresentableType.Signature.Nature switch - { - TypeNature.ReferenceType => _dedicatedReferenceFieldAdditions, - TypeNature.PureValueType => _dedicatedPureValueTypeFieldAdditions, - _ => _dedicatedImpureAndUnknownFieldAdditions - } ).Add( - (Appendable: new((b) => - { - _ = b - .Comment.OpenSummary() - .Append("Contains the value of instances of ").Comment.SeeRef(unionTypeSignature).Append(" representing an instance of ") - .Comment.Ref(strategy.RepresentableType.Signature) - .Append('.') - .CloseBlock() - .GeneratedUnnavigableInternalCode(unionTypeSignature) - .Append("private readonly ").Append(fullName).Append(strategy.NullableFieldQuestionMark).Append(' ').Append(strategy.FieldName).AppendLine(';'); - }), - TypeName: fullName)); - } - - public void DedicatedReferenceFields(IndentedStringBuilder builder) => - _dedicatedReferenceFieldAdditions.ForEach(t => t.Appendable.AppendTo(builder)); - public void DedicatedPureValueTypeFields(IndentedStringBuilder builder) => - _dedicatedPureValueTypeFieldAdditions.OrderByDescending(static t => - { - var pureValueType = Type.GetType(t.TypeName, false); - var size = pureValueType != null ? - Marshal.SizeOf(pureValueType) : - Int32.MaxValue; - return size; - }).ForEach(t => t.Appendable.AppendTo(builder)); - public void DedicatedImpureAndUnknownFields(IndentedStringBuilder builder) => - _dedicatedImpureAndUnknownFieldAdditions.ForEach(t => t.Appendable.AppendTo(builder)); - - private Boolean _referenceFieldRequired; - public void AddReferenceTypeContainerField() => _referenceFieldRequired = true; - public void ReferenceTypeContainerField(IndentedStringBuilder builder) - { - if(!_referenceFieldRequired) - return; - - _ = builder.Comment.OpenSummary() - .Append("Contains the value of instances of ").Comment.SeeRef(unionTypeSignature).Append(" representing one of these types:") - .Comment.OpenList("bullet"); - - var referenceTypes = representableTypes - .Select(t => t.StorageStrategy.Value) - .Where(s => s.ActualOption == StorageOption.Reference) - .Select(s => s.RepresentableType.Signature); - foreach(var referenceType in referenceTypes) - { - _ = builder.Comment.OpenItem() - .Comment.Ref(referenceType) - .CloseBlock(); - } - - _ = builder.CloseBlock() - .CloseBlock() - .GeneratedUnnavigableInternalCode(unionTypeSignature) - .Append("private readonly System.Object? ").Append(settings.ReferenceTypeContainerName).AppendLine(';'); - } - - private Boolean _valueTypeContainerTypeRequired; - public void AddValueTypeContainerType() => _valueTypeContainerTypeRequired = true; - public void AddValueTypeContainerField() => _valueTypeContainerTypeRequired = true; - private readonly List _valueTypeFieldAdditions = []; - - public void AddValueTypeContainerInstanceFieldAndCtor(StorageStrategy strategy) => - _valueTypeFieldAdditions.Add(new((b) => - { - var fullName = strategy.RepresentableType.Signature.Names.FullGenericName; - - _ = b.Comment.OpenSummary() - .Append("Contains the value of instances of ").Comment.SeeRef(unionTypeSignature).Append(" representing an instance of ") - .Comment.Ref(strategy.RepresentableType.Signature) - .Append('.') - .CloseBlock() - .Append(b => - { - if(!unionTypeIsGeneric) - b.Append("[System.Runtime.InteropServices.FieldOffset(0)]").AppendLineCore(); - }) - .Append("public readonly ").Append(fullName) - .Append(' ').Append(strategy.RepresentableType.Alias).AppendLine(';') - .Append("public ").Append(settings.ValueTypeContainerTypeName).Append('(') - .Append(fullName).Append(" value) => this.") - .Append(strategy.RepresentableType.Alias).AppendLine(" = value;"); - })); - - public void ValueTypeContainerField(IndentedStringBuilder builder) - { - if(!_valueTypeContainerTypeRequired) - return; - - _ = builder.Comment.OpenSummary() - .Append("Contains the value of instances of ").Comment.SeeRef(unionTypeSignature).Append(" representing one of these types:") - .Comment.OpenList("bullet"); - - var valueTypes = representableTypes - .Select(t => t.StorageStrategy.Value) - .Where(s => s.ActualOption == StorageOption.Value) - .Select(s => s.RepresentableType.Signature); - foreach(var valueType in valueTypes) - { - _ = builder.Comment.OpenItem() - .Comment.Ref(valueType) - .CloseBlock(); - } - - _ = builder.CloseBlock() - .CloseBlock() - .GeneratedUnnavigableInternalCode(unionTypeSignature) - .Append("private readonly ").Append(settings.ValueTypeContainerTypeName) - .Append(' ').Append(settings.ValueTypeContainerName).Append(';'); - } - public void ValueTypeContainerType(IndentedStringBuilder builder) - { - if(!_valueTypeContainerTypeRequired) - return; - - _ = builder.Comment.OpenSummary() - .Append("Helper type for storing value types efficiently.") - .CloseBlock() - .GeneratedUnnavigableInternalCode(unionTypeSignature); - - if(!unionTypeIsGeneric) - _ = builder.AppendLine("[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit)]"); - - _ = builder - .Append("readonly struct ").Append(settings.ValueTypeContainerTypeName) - .OpenBracesBlock() - .AppendJoinLines(StringOrChar.Empty, _valueTypeFieldAdditions) - .CloseBlock(); - } -} - -file static class EnumerableExtensions -{ - public static void ForEach(this IEnumerable values, Action action) - { - foreach(var value in values) - action.Invoke(value); - } -} diff --git a/UnionsGenerator/Transformation/Storage/StringExtensions.cs b/UnionsGenerator/Transformation/Storage/StringExtensions.cs deleted file mode 100644 index eef66ac..0000000 --- a/UnionsGenerator/Transformation/Storage/StringExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Transformation.Storage; -using System; -using System.Collections.Generic; -using System.Text; -using System.Text.RegularExpressions; - -internal static class StringExtensions -{ - //source: https://stackoverflow.com/a/58853591 - private static readonly Regex _camelCasePattern = - new(@"([A-Z])([A-Z]+|[a-z0-9_]+)($|[A-Z]\w*)", RegexOptions.Compiled); - public static String ToCamelCase(this String value) - { - if(value.Length == 0) - return value; - - if(value.Length == 1) - return value.ToLowerInvariant(); - - //source: https://stackoverflow.com/a/58853591 - var result = _camelCasePattern.Replace( - value, - static m => $"{m.Groups[1].Value.ToLowerInvariant()}{m.Groups[2].Value.ToLowerInvariant()}{m.Groups[3].Value}"); - - return result; - } - public static String ToGeneratedCamelCase(this String value) - { - var result = $"__{value.ToCamelCase()}"; - - return result; - } -} diff --git a/UnionsGenerator/Transformation/Visitors/AppendableSourceText.cs b/UnionsGenerator/Transformation/Visitors/AppendableSourceText.cs deleted file mode 100644 index 3d6e69f..0000000 --- a/UnionsGenerator/Transformation/Visitors/AppendableSourceText.cs +++ /dev/null @@ -1,1174 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Transformation.Visitors; - -using System.Collections.Immutable; -using System.Xml.Linq; - -using RhoMicro.CodeAnalysis.Library.Text; -using RhoMicro.CodeAnalysis.UnionsGenerator.Models; -using RhoMicro.CodeAnalysis.UnionsGenerator.Utils; - -using static Library.Text.IndentedStringBuilder.Appendables; - -internal sealed partial class AppendableSourceText(UnionTypeModel target) : IIndentedStringBuilderAppendable -{ - /// - public void AppendTo(IndentedStringBuilder builder) - { - Throw.ArgumentNull(builder, nameof(builder)); - - var signature = target.Signature; - - if(!String.IsNullOrEmpty(signature.Names.Namespace)) - builder.Append("namespace ").Append(signature.Names.Namespace).OpenBlockCore(Blocks.Braces(builder.Options.NewLine)); - - builder.AppendLine("using System.Linq;") - .AppendCore(ScopedData); - - foreach(var containingType in signature.ContainingTypes) - { - builder.Append("partial ").Append(containingType.DeclarationKeyword) - .Append(' ').Append(containingType.Names.GenericName).OpenBlockCore(Blocks.Braces(builder.Options.NewLine)); - } - - if(target.Settings.Miscellaneous.HasFlag(MiscellaneousSettings.GenerateJsonConverter)) - { - builder.Append("[System.Text.Json.Serialization.JsonConverter(typeof(") - .Append(target.Signature.Names.OpenGenericName) - .Append(".JsonConverter))]") - .AppendLineCore(); - } - - builder.Append("partial ").Append(signature.DeclarationKeyword).Append(' ').Append(signature.Names.GenericName) - .Append(" : global::System.IEquatable<").Append(signature.Names.GenericName).Append(b => - { - if(target.Signature.Nature == TypeNature.ReferenceType) - b.AppendCore('?'); - }).Append('>') - .OpenBracesBlock() - .Append(NestedTypes) - .Append(Constructors) - .Append(Fields) - .Append(Factories) - .Append(Switch) - .Append(Match) - .Append(RepresentedTypes) - .Append(IsAsProperties) - .Append(IsGroupProperties) - .Append(IsAsFunctions) - .Append(ToString) - .Append(GetHashCode) - .Append(Equals) - .Append(Conversions) - .CloseAllBlocksCore(); - //InterfaceIntersections, - } - public void IsGroupProperties(IndentedStringBuilder builder) - { - builder.OpenRegionBlock("Is Group Properties") - .Append(b => - b.AppendJoin( - StringOrChar.Empty, - target.Groups.Groups, - (b, group) => - { - var (name, members) = group; - b.Comment.OpenSummary() - .Comment.OpenParagraph() - .Append("Gets a value indicating whether the currently represented value is part of the ").Append(name).Append(" group.") - .CloseBlock() - .Comment.OpenParagraph() - .Append("The group encompasses values of the following types:") - .CloseBlock() - .Comment.OpenParagraph() - .Comment.OpenList("bullet") - .AppendJoin( - StringOrChar.Empty, - members, - (b, m) => b.Comment.OpenItem().Comment.Ref(m.Signature).CloseBlockCore()) - .CloseBlock() - .CloseBlock() - .CloseBlock() - .Append("public System.Boolean Is").Append(name).AppendLine("Group => ") - .TagSwitchExpr( - target, - (b, m) => b.Append(members.Contains(m) ? "true" : "false")) - .Append(';') - .AppendLineCore(); - })) - .CloseBlockCore(); - } - public void Conversions(IndentedStringBuilder builder) => - builder.OpenRegionBlock("Conversions") - .Append(RepresentableTypeConversions) - .Append(RelatedTypeConversions) - .CloseBlockCore(); - public void RepresentableTypeConversions(IndentedStringBuilder builder) - { -#pragma warning disable IDE0047 // Remove unnecessary parentheses - var convertableTypes = target.RepresentableTypes - .Where(t => !( t.OmitConversionOperators /*|| t.Signature.IsTypeParameter && t.Options.HasFlag(UnionTypeOptions.SupersetOfParameter) */)); -#pragma warning restore IDE0047 // Remove unnecessary parentheses - builder.OpenRegionBlock("Representable Type Conversions") - .AppendJoinLines( - StringOrChar.Empty, - convertableTypes, - (b, representableType) => - { - b.Comment.OpenSummary() - .Append("Converts an instance of the representable type ") - .Comment.Ref(representableType.Signature) - .Append(" to the union type ") - .Comment.Ref(target.Signature).Append('.') - .CloseBlock() - .Comment.OpenParam("value") - .Append("The value to convert.") - .CloseBlock() - .Comment.OpenReturns() - .Append("The union type instance.") - .CloseBlock() - .Append("public static implicit operator ").Append(target.Signature.Names.GenericName) - .Append('(') - .Append(representableType.Signature.Names.FullGenericNullableName).Append(" value) => ") - .Append(representableType.Factory.Name).Append("(value);") - .AppendLineCore(); - - var generateSolitaryExplicit = - target.RepresentableTypes.Count > 1 || - !representableType.Options.HasFlag(UnionTypeOptions.ImplicitConversionIfSolitary); - - if(generateSolitaryExplicit) - { - b.Comment.OpenSummary() - .Append("Converts an instance of the union type ") - .Comment.Ref(target.Signature) - .Append(" to the representable type ") - .Comment.Ref(representableType.Signature).Append('.') - .CloseBlock() - .Comment.OpenParam("union") - .Append("The union to convert.") - .CloseBlock() - .Comment.OpenReturns() - .Append("The represented value.") - .CloseBlock() - .Append("public static explicit operator ") - .Append(representableType.Signature.Names.FullGenericNullableName) - .Append('(') - .Append(target.Signature.Names.FullGenericName) - .AppendCore(" union) =>"); - - if(target.RepresentableTypes.Count > 1) - { - b.Append("union.").Append(target.Settings.TagFieldName).Append(" == ") - .Append(target.Settings.TagTypeName) - .Append('.') - .Append(representableType.Alias) - .Append('?') - .AppendLineCore(); - } - - b.AppendCore(representableType.StorageStrategy.Value.StrongInstanceVariableExpression("union")); - - if(target.RepresentableTypes.Count > 1) - { - _ = b.Append(':') - .InvalidConversionThrow( - fromTypeNameExpression: $"typeof({target.Signature.Names.GenericName})", - representedTypeNameExpression: "union.RepresentedType", - toTypeNameExpression: $"typeof({representableType.Signature.Names.FullGenericName})"); - } - - b.AppendCore(';'); - } else - { - b.Append("public static implicit operator ") - .Append(representableType.Signature.Names.FullGenericNullableName) - .Append('(') - .Append(target.Signature.Names.FullGenericName) - .Append(" union) => ") - .Append(representableType.StorageStrategy.Value.StrongInstanceVariableExpression("union")) - .Append(';') - .AppendLineCore(); - } - }) - .CloseBlockCore(); - } - public void RelatedTypeConversions(IndentedStringBuilder builder) - { - builder.OpenRegionBlock("Related Type Conversions") - .AppendJoinLines( - StringOrChar.Empty, - target.Relations.Where(r => r.RelationType != RelationType.Disjunct), - (b, relation) => - { - var representableTypesIntersectionSet = relation.RelatedType.RepresentableTypeSignatures - .Intersect(target.RepresentableTypes.Select(t => t.Signature)) - .ToImmutableHashSet(); - - var unionTypeRepresentableTypesMap = target.RepresentableTypes - .Where(t => representableTypesIntersectionSet.Contains(t.Signature)) - .ToDictionary(t => t.Signature); - - //conversion to model from relation - //public static _plicit operator Target(Relation relatedUnion) - var relationType = relation.RelationType; - b.OpenRegionBlock($"{relationType switch - { - RelationType.Congruent => "Congruency with ", - RelationType.Intersection => "Intersection with ", - RelationType.Superset => "Superset of ", - RelationType.Subset => "Subset of ", - _ => "Relation" - }} {relation.RelatedType.Signature.Names.GenericName}") - .Append("public static ") - .Append(relationType is RelationType.Congruent or RelationType.Superset ? "im" : "ex") - .Append("plicit operator ").Append(target.Signature.Names.GenericName) - .Append('(') - .Append(relation.RelatedType.Signature.Names.FullGenericName) - .AppendLine(" relatedUnion) =>") - .Indent() - .Append(b => - { - _ = b.FullMetadataNameSwitchExpr( - representableTypesSet: representableTypesIntersectionSet, - metadataNameExpr: b => b.UtilGetFullString($"relatedUnion.RepresentedType"), - caseExpr: (b, representableTypeSignature) => - b.ToUnionTypeConversion( - unionType: target, - unionTypeRepresentableType: unionTypeRepresentableTypesMap[representableTypeSignature], - relationTypeParameterName: "relatedUnion"), - defaultCase: b => b.InvalidConversionThrow( - fromTypeNameExpression: $"typeof({relation.RelatedType.Signature.Names.FullGenericName})", - representedTypeNameExpression: "relatedUnion.RepresentedType", - toTypeNameExpression: $"typeof({target.Signature.Names.GenericName})")); - }) - .AppendLine(';') - .DetentCore(); - - //conversion to relation from model - //public static _plicit operator Relation(Target relatedUnion) - b.Append("public static ") - .Append(relationType is RelationType.Congruent or RelationType.Subset ? "im" : "ex") - .Append("plicit operator ").Append(relation.RelatedType.Signature.Names.FullGenericName) - .Append('(') - .Append(target.Signature.Names.FullGenericName) - .AppendLine(" union) =>") - .Indent() - .Append(b => - { - _ = b.TagSwitchExpr( - target, - representableTypesIntersectionSet, - (b, representableTypeSignature) => - b.ToRelatedTypeConversion( - relatedType: relation.RelatedType, - unionTypeRepresentableType: unionTypeRepresentableTypesMap[representableTypeSignature], - unionTypeParameterName: "union"), - instanceExpr: "union", - specialDefault: b => b.InvalidConversionThrow( - fromTypeNameExpression: $"typeof({target.Signature.Names.FullGenericName})", - representedTypeNameExpression: "union.RepresentedType", - toTypeNameExpression: $"typeof({relation.RelatedType.Signature.Names.GenericName})")); - }) - .AppendLine(';') - .DetentCore(); - b.CloseBlockCore(); - }) - .CloseBlockCore(); - } - public void Equals(IndentedStringBuilder builder) - { - builder.OpenRegionBlock("Equality") - .Comment.InheritDoc() - .AppendLine("public override System.Boolean Equals(System.Object? obj) =>") - .Indent() - .Append("obj is ").Append(target.Signature.Names.GenericName).AppendLine(" union && Equals(union);") - .Detent() - .Append(b => - { - if(!target.IsEqualsRequired) - return; - - b.Comment.InheritDoc() - .Append("public System.Boolean Equals(").Append(target.Signature.Names.GenericName).Append(b => - { - if(target.Signature.Nature == TypeNature.ReferenceType) - b.AppendCore('?'); - }).AppendLine(" other) =>") - .Indent() - .Append(b => - { - if(target.Signature.Nature == TypeNature.ReferenceType) - { - b.AppendLine("ReferenceEquals(other, this)") - .AppendLine("|| other != null") - .AppendCore("&& "); - } - - if(target.RepresentableTypes.Count > 1) - { - b.Append("this.").Append(target.Settings.TagFieldName) - .Append(" == other.") - .AppendLine(target.Settings.TagFieldName) - .AppendCore("&& "); - } - }).TagSwitchExpr( - target, - (b, t) => b.Append(t.StorageStrategy.Value.EqualsInvocation("other"))) - .AppendLine(';') - .DetentCore(); - }) - .Append(b => - { - if(target is - { - Signature.Nature: TypeNature.PureValueType or TypeNature.ImpureValueType, - Settings.EqualityOperatorsSetting: EqualityOperatorsSetting.EmitOperatorsIfValueType or EqualityOperatorsSetting.EmitOperators - } or - { - Signature.Nature: TypeNature.ReferenceType or TypeNature.UnknownType, - Settings.EqualityOperatorsSetting: EqualityOperatorsSetting.EmitOperators - }) - { - b.Append("public static System.Boolean operator ==(") - .Append(target.Signature.Names.GenericName).Append(" a, ") - .Append(target.Signature.Names.GenericName).AppendLine(" b) => a.Equals(b);") - .Append("public static System.Boolean operator !=(") - .Append(target.Signature.Names.GenericName).Append(" a, ") - .Append(target.Signature.Names.GenericName).AppendCore(" b) => !a.Equals(b);"); - } - }) - .CloseBlockCore(); - } - public void GetHashCode(IndentedStringBuilder builder) - { - if(!target.IsEqualsRequired) - return; - - builder.OpenRegionBlock("GetHashCode") - .Comment.InheritDoc() - .AppendLine("public override System.Int32 GetHashCode() => ") - .TagSwitchExpr( - target, - (b, t) => b.Append(t.StorageStrategy.Value.GetHashCodeInvocation())) - .Append(';') - .CloseBlockCore(); - } - public void ToString(IndentedStringBuilder builder) - { - if(!target.IsToStringRequired) - return; - - builder.OpenRegionBlock("ToString") - .Comment.InheritDoc() - .Append(b => - { - if(target.Settings.ToStringSetting == ToStringSetting.Simple) - { - builder.Append("public override System.String ToString() =>") - .Append(simpleToStringExpression) - .AppendCore(';'); - } else - { - builder.Append("public override System.String ToString()") - .OpenBracesBlock() - .Append("var stringRepresentation = ").Append(simpleToStringExpression) - .AppendLine(';') - .Append("var result = $\"").Append(target.Signature.Names.GenericName) - .Append('(') - .Append(b => - { - if(target.RepresentableTypes.Count == 1) - { - b.Append('<').Append(target.RepresentableTypes[0].Signature.Names.GenericName).AppendCore('>'); - } else - { - _ = b.AppendJoin( - '|', - target.RepresentableTypes, - (b, t) => b.Append("{(") - .Append(target.Settings.TagFieldName).Append(" == ") - .Append(target.Settings.TagTypeName).Append('.').Append(t.Alias) - .Append(" ? \"<").Append(t.Alias).Append(">\" : \"").Append(t.Alias) - .AppendCore("\")}")); - } - }) - .AppendLine("){{{stringRepresentation}}}\";") - .AppendLine("return result;") - .CloseBlockCore(); - } - }) - .CloseBlockCore(); - - void simpleToStringExpression(IndentedStringBuilder b) - { - if(target.RepresentableTypes.Count > 1) - { - _ = b.TagSwitchExpr( - target, - (b, t) => b.AppendCore(t.StorageStrategy.Value.ToStringInvocation())); - } else - { - b.AppendCore(target.RepresentableTypes[0].StorageStrategy.Value.ToStringInvocation()); - } - } - } - public void IsAsFunctions(IndentedStringBuilder builder) - { - builder.OpenRegionBlock("Is/As Functions") - .AppendJoin( - StringOrChar.Empty, - target.RepresentableTypes, - (b, t) => - { - b.Comment.OpenSummary() - .Append("Determines whether this instance is representing a value of type ") - .Comment.Ref(t.Signature).Append('.') - .CloseBlock() - .Comment.OpenReturns() - .Comment.Langword("true").Append(" if this instance is representing a value of type ") - .Comment.Ref(t.Signature).Append("; otherwise, ") - .Comment.Langword("false").Append('.') - .CloseBlock() - .Comment.OpenParam("value") - .Append("If this instance is representing a value of type ").Comment.Ref(t.Signature) - .Append(", this parameter will contain that value; otherwise, ").Comment.Langword("default").Append('.') - .CloseBlock() - .Append("public System.Boolean TryAs").Append(t.Alias).Append('(') - .Append(b => - { - if(t.Signature.Nature is not TypeNature.ReferenceType) - return; - b.AppendCore("[System.Diagnostics.CodeAnalysis.NotNullWhen(true)]"); - }) - .Append(" out ").Append(t.Signature.Names.FullGenericNullableName).Append(" value)") - .OpenBracesBlock() - .Append(b => - { - if(target.RepresentableTypes.Count == 1) - { - b.Append("value = ").Append(target.RepresentableTypes[0].StorageStrategy.Value.StrongInstanceVariableExpression("this")).AppendLine(';') - .AppendCore("return true;"); - } else - { - b.Append("if(") - .Append("this").Append('.').Append(target.Settings.TagFieldName) - .Append(" == ") - .Append(target.Settings.TagTypeName).Append('.').Append(t.Alias).Append(')') - .OpenBracesBlock() - .Append("value = ").Append(t.StorageStrategy.Value.StrongInstanceVariableExpression("this")).AppendLine(';') - .Append("return true;") - .CloseBlock() - .AppendLine("value = default;") - .AppendCore("return false;"); - } - }) - .CloseBlockCore(); - }) - .Comment.OpenSummary() - .Append("Determines whether this instance is representing a value of type ") - .Comment.TypeParamRef(target.Settings.GenericTValueName).Append('.') - .CloseBlock() - .Comment.OpenTypeParam(target.Settings.GenericTValueName) - .Append("The type whose representation in this instance to determine.") - .CloseBlock() - .Comment.OpenReturns() - .Comment.Langword("true").Append(" if this instance is representing a value of type ") - .Comment.TypeParamRef(target.Settings.GenericTValueName).Append("; otherwise, ") - .Comment.Langword("false").Append('.') - .CloseBlock() - .Append("public System.Boolean Is<").Append(target.Settings.GenericTValueName).Append(">() =>") - .Append(b => - { - _ = target.RepresentableTypes.Count > 1 - ? b.Append("typeof(").Append(target.Settings.GenericTValueName).Append(") ==") - .TagSwitchExpr( - target, - (b, a) => b.Typeof(a.Signature)) - : b.Append("typeof(").Append(target.Settings.GenericTValueName).Append(") == ") - .Typeof(target.RepresentableTypes[0].Signature); - }) - .AppendLine(';') - .Comment.OpenSummary() - .Append("Determines whether this instance is representing a value of type ") - .Comment.TypeParamRef(target.Settings.GenericTValueName).Append('.') - .CloseBlock() - .Comment.OpenParam("value") - .Append("If this instance is representing a value of type ").Comment.TypeParamRef(target.Settings.GenericTValueName) - .Append(", this parameter will contain that value; otherwise, ").Comment.Langword("default").Append('.') - .CloseBlock() - .Comment.OpenTypeParam(target.Settings.GenericTValueName) - .Append("The type whose representation in this instance to determine.") - .CloseBlock() - .Comment.OpenReturns() - .Comment.Langword("true").Append(" if this instance is representing a value of type ") - .Comment.TypeParamRef(target.Settings.GenericTValueName) - .Append("; otherwise, ").Comment.Langword("false").Append('.') - .CloseBlock() - .Append("public System.Boolean Is<").Append(target.Settings.GenericTValueName).Append(">(out ") - .Append(target.Settings.GenericTValueName).Append("? value)") - .OpenBracesBlock() - .Append(b => - { - if(target.RepresentableTypes.Count > 1) - { - _ = b.MetadataNameSwitchStmt( - target, - b => b.Append("typeof(").Append(target.Settings.GenericTValueName).Append(')'), - (b, a) => b.Append("value = ").Append(a.StorageStrategy.Value.ConvertedInstanceVariableExpression(target.Settings.GenericTValueName)) - .AppendLine(';').Append("return true;"), - b => b.AppendLine("value = default;").Append("return false;")); - } else - { - b.Append("if(typeof(").Append(target.Settings.GenericTValueName).Append(") == ") - .Typeof(target.RepresentableTypes[0].Signature).Append(")") - .OpenBracesBlock() - .Append("value = ").Append( - target.RepresentableTypes[0].StorageStrategy.Value.ConvertedInstanceVariableExpression(target.Settings.GenericTValueName)) - .AppendLine(';') - .Append("return true;") - .CloseBlock() - .Append("else") - .OpenBracesBlock() - .AppendLine("value = default;") - .Append("return false;") - .CloseBlockCore(); - } - }) - .CloseBlock() - .Comment.OpenSummary() - .Append("Determines whether this instance is representing an instance of ") - .Comment.ParamRef("type").Append('.') - .CloseBlock() - .Comment.OpenParam("type") - .Append("The type whose representation in this instance to determine.") - .CloseBlock() - .Comment.OpenReturns() - .Comment.Langword("true").Append(" if this instance is representing an instance of ") - .Comment.ParamRef("type").Append("; otherwise, ").Comment.Langword("false").Append('.') - .CloseBlock() - .AppendLine("public System.Boolean Is(System.Type type) =>") - .Append(b => - { - _ = target.RepresentableTypes.Count > 0 - ? b.Append("type == ").TagSwitchExpr( - target, - (b, a) => b.Typeof(a.Signature)) - : b.Append("type == ").Typeof(target.RepresentableTypes[0].Signature); - }) - .AppendLine(';') - .Comment.OpenSummary() - .Append("Retrieves the value represented by this instance as an instance of ") - .Comment.TypeParamRef(target.Settings.GenericTValueName).Append('.') - .CloseBlock() - .Comment.OpenTypeParam(target.Settings.GenericTValueName) - .Append("The type to retrieve the represented value as.") - .CloseBlock() - .Comment.OpenReturns() - .Append("The currently represented value as an instance of ").Comment.TypeParamRef(target.Settings.GenericTValueName).Append('.') - .CloseBlock() - .Append("public ").Append(target.Settings.GenericTValueName) - .Append(" As<").Append(target.Settings.GenericTValueName).AppendLine(">() =>") - .Append(b => - { - if(target.RepresentableTypes.Count > 1) - { - b.TagSwitchExpr( - target, - (b, a) => b.Append("typeof(").Append(target.Settings.GenericTValueName).Append(") == ") - .Typeof(a.Signature).AppendLine() - .Append("? ").AppendLine(a.StorageStrategy.Value.ConvertedInstanceVariableExpression(target.Settings.GenericTValueName)) - .Append(": ") - .InvalidConversionThrow( - fromTypeNameExpression: $"typeof({target.Signature.Names.FullGenericName})", - representedTypeNameExpression: "this.RepresentedType", - toTypeNameExpression: $"typeof({target.Settings.GenericTValueName})")) - .AppendCore(';'); - } else - { - b.Append("typeof(").Append(target.Settings.GenericTValueName).Append(") == ") - .Typeof(target.RepresentableTypes[0].Signature).AppendLine() - .Append("? ").Append( - target.RepresentableTypes[0].StorageStrategy.Value.ConvertedInstanceVariableExpression(target.Settings.GenericTValueName)) - .AppendLine() - .Append(": ") - .InvalidConversionThrow( - fromTypeNameExpression: $"typeof({target.Signature.Names.FullGenericName})", - representedTypeNameExpression: "this.RepresentedType", - toTypeNameExpression: $"typeof({target.Settings.GenericTValueName})") - .AppendCore(';'); - } - }) - .CloseBlockCore(); - } - public void IsAsProperties(IndentedStringBuilder builder) - { - _ = builder.OpenRegionBlock("Is/As Properties"); - if(target.RepresentableTypes.Count > 1) - { - builder.AppendJoinLines( - StringOrChar.Semicolon, - target.RepresentableTypes, - (b, a) => - b.Comment.OpenSummary() - .Append("Gets a value indicating whether this instance is representing a value of type ").Comment.Ref(a.Signature).Append('.') - .CloseBlock() - .Append(b => - { - if(a.Signature.Nature is TypeNature.ReferenceType) - { - b.Append("[global::System.Diagnostics.CodeAnalysis.MemberNotNullWhen(true, \"As").Append(a.Alias).Append("\")]").AppendLineCore(); - } - - if(target.RepresentableTypes.Count == 2) - { - var otherRepresentable = target.RepresentableTypes.Single(r => r.Alias != a.Alias); - - if(otherRepresentable.Signature.Nature == TypeNature.ReferenceType) - { - b.Append("[global::System.Diagnostics.CodeAnalysis.MemberNotNullWhen(false, \"As").Append(otherRepresentable.Alias).Append("\")]").AppendLineCore(); - } - } - }) - .Append("public System.Boolean Is").Append(a.Alias).Append(" => ") - .Append(target.Settings.TagFieldName).Append(" == ") - .Append(target.Settings.TagTypeName).Append('.').Append(a.Alias)) - .AppendLine(';') - .AppendJoinLines( - StringOrChar.Semicolon, - target.RepresentableTypes, - (b, a) => - b.Comment.OpenSummary() - .Append("Retrieves the value represented by this instance as a ").Comment.Ref(a.Signature).Append('.') - .CloseBlock() - .Append("public ").Append(a.Signature.Names.FullGenericName) - .Append(a.Signature.Nature == TypeNature.ReferenceType ? "?" : String.Empty) - .Append(" As").Append(a.Alias).Append(" => ") - .Append(target.Settings.TagFieldName).Append(" == ") - .Append(target.Settings.TagTypeName).Append('.').AppendLine(a.Alias) - .Append("? ").AppendLine(a.StorageStrategy.Value.StrongInstanceVariableExpression("this")) - .Append(": ").Append(a.Signature.Nature == TypeNature.ReferenceType ? "null" : "default")) - .AppendCore(';'); - } else - { - var attribute = target.RepresentableTypes[0]; - builder.Comment.OpenSummary() - .Append("Gets a value indicating whether this instance is representing a value of type ").Comment.Ref(attribute.Signature).Append('.') - .CloseBlock() - .Append("public System.Boolean Is").Append(attribute.Alias).AppendLine(" => true;") - .Comment.OpenSummary() - .Append("Retrieve the value represented by this instance as a ").Comment.Ref(attribute.Signature).Append('.') - .CloseBlock() - .Append("public ").Append(attribute.Signature.Names.FullGenericName).Append(" As").Append(attribute.Alias).Append(" => ") - .Append(attribute.StorageStrategy.Value.StrongInstanceVariableExpression("this")) - .AppendCore(';'); - } - - builder.CloseBlockCore(); - } - public void RepresentedTypes(IndentedStringBuilder builder) - { - builder.OpenRegionBlock("Represented Type") - .Comment.OpenSummary() - .Append("Gets the types of value this union type can represent.") - .CloseBlock() - .AppendLine("public static System.Collections.Generic.IReadOnlyCollection RepresentableTypes { get; } = ") - .Indent() - .Append(target.ScopedDataTypeName).Append(".RepresentableTypes;") - .Detent() - .Comment.OpenSummary() - .Append("Gets the type of value represented by this instance.") - .CloseBlock() - .AppendLine("public System.Type RepresentedType => ") - .TagSwitchExpr( - target, - (b, a) => b.Typeof(a.Signature)) - .Append(';') - .CloseBlockCore(); - } - public void Match(IndentedStringBuilder builder) - { - builder.OpenRegionBlock("Match") - .Comment.OpenSummary() - .Append("Invokes a projection based on the type of value being represented.") - .CloseBlockCore(); - - foreach(var attribute in target.RepresentableTypes) - { - builder.Comment.OpenParam($"on{attribute.Alias}") - .Append("The projection to invoke if the union is currently representing an instance of ").Comment.Ref(attribute.Signature).Append('.') - .CloseBlockCore(); - } - - builder.Comment.OpenTypeParam(target.Settings.MatchTypeName) - .Append("The type of value produced by the projections passed.") - .CloseBlock() - .Comment.OpenReturns() - .Append("The projected value.") - .CloseBlock() - .Append("public ").Append(target.Settings.MatchTypeName).Append(" Match<").Append(target.Settings.MatchTypeName).AppendLine(">(") - .Indent() - .AppendJoinLines(target.RepresentableTypes - .Select>(a => b => - b.Append("System.Func<").Append(a.Signature.Names.FullGenericNullableName).Append(", ") - .Append(target.Settings.MatchTypeName).Append("> on").Append(a.Alias))) - .AppendLine(") =>") - .Detent() - .TagSwitchExpr( - target, - (b, t) => - b.Append("on").Append(t.Alias).Append(".Invoke(") - .Append(t.StorageStrategy.Value.StrongInstanceVariableExpression("this")) - .AppendLine(")")) - .Append(';') - .CloseBlockCore(); - } - public void Switch(IndentedStringBuilder builder) - { - builder.OpenRegionBlock("Switch") - .Comment.OpenSummary() - .Append("Invokes a handler based on the type of value being represented.") - .CloseBlockCore(); - - foreach(var attribute in target.RepresentableTypes) - { - builder.Comment.OpenParam($"on{attribute.Alias}") - .Append("The handler to invoke if the union is currently representing an instance of ").Comment.Ref(attribute.Signature).Append('.') - .CloseBlockCore(); - } - - builder.AppendLine("public void Switch(") - .Indent() - .AppendJoinLines(target.RepresentableTypes - .Select>(a => b => - b.Append("System.Action<").Append(a.Signature.Names.FullGenericNullableName).Append("> on").Append(a.Alias))) - .AppendLine(")") - .Detent() - .OpenBracesBlock() - .TagSwitchStmt( - target, - (b, t) => - b.Append("on").Append(t.Alias).Append(".Invoke(") - .Append(t.StorageStrategy.Value.StrongInstanceVariableExpression("this")) - .AppendLine(");") - .Append("return;")) - .CloseBlock() - .CloseBlockCore(); - } - public void ScopedData(IndentedStringBuilder builder) - { - builder.OpenRegionBlock("Scoped Data") - .Append("file static class ").Append(target.ScopedDataTypeName) - .OpenBracesBlock() - .AppendLine("public static System.Collections.Concurrent.ConcurrentDictionary Cache { get; } = new();") - .AppendLine("public static System.Collections.Generic.HashSet RepresentableTypes { get; } = ") - .AppendLine("new ()") - .OpenBracesBlock() - .AppendJoinLines( - target.RepresentableTypes, - (b, a) => b.Typeof(a.Signature)) - .CloseBlock() - .AppendLine(';') - .CloseBlock() - .CloseBlockCore(); - } - public void Factories(IndentedStringBuilder builder) - { - var tValueName = target.Settings.GenericTValueName; - builder.OpenRegionBlock("Factories") - .AppendJoin( - StringOrChar.Empty, - target.RepresentableTypes.Where(a => a.Factory.RequiresGeneration) - .Select>(a => b => - { - b.Comment.OpenSummary() - .Append("Creates a new instance of ").Comment.SeeRef(target) - .Append(" representing an instance of ").Comment.Ref(a.Signature).Append('.') - .CloseBlock() - .Comment.OpenParam("value") - .Append("The value to be represented by the new instance of ").Comment.SeeRef(target).Append('.') - .CloseBlock() - .Comment.OpenReturns() - .Append("A new instance of ").Comment.SeeRef(target).Append(" representing ").Comment.ParamRef("value").Append('.') - .CloseBlock() - .Append("public static ").Append(target.Signature.Names.GenericName).Append(' ') - .Append(a.Factory.Name).Append("([RhoMicro.CodeAnalysis.UnionTypeFactory]").Append(a.Signature.Names.FullGenericNullableName) - .Append(" value) => new(value);").AppendLineCore(); - })) - .Comment.OpenSummary() - .Append("Attempts to create an instance of ").Comment.SeeRef(target) - .Append(" from an instance of ").Comment.TypeParamRef(tValueName).Append('.') - .CloseBlock() - .Comment.OpenParam("value") - .Append("The value from which to attempt to create an instance of ").Comment.SeeRef(target).Append('.') - .CloseBlock() - .Comment.OpenParam("result") - .Append("If an instance of ").Comment.SeeRef(target) - .Append(" could successfully be created, this parameter will contain the newly created instance; otherwise, ") - .Comment.Langword("default").Append('.') - .CloseBlock() - .Comment.OpenReturns() - .Comment.Langword("true").Append(" if an instance of ").Comment.SeeRef(target).Append(" could successfully be created; otherwise, ") - .Comment.Langword("false").AppendLine('.') - .CloseBlock() - .Append("public static System.Boolean TryCreate<").Append(tValueName) - .Append(">(").Append(tValueName).Append(" value, ").Append(b => - { - if(target.Signature.Nature == TypeNature.ReferenceType) - { - b.AppendCore("[System.Diagnostics.CodeAnalysis.NotNullWhen(true)]"); - } - }).Append(" out ") - .Append(target.Signature.Names.GenericName).Append(b => - { - if(target.Signature.Nature == TypeNature.ReferenceType) - { - b.AppendCore('?'); - } - }).Append(" result)") - .OpenBracesBlock() - .MetadataNameSwitchStmt( - target: target, - typeExpr: b => b.Append("typeof(").Append(tValueName).Append(")"), - caseBody: (b, t) => _ = b.Operators + - "result = " + t.Factory.Name + '(' + UtilUnsafeConvert(tValueName, t.Signature, "value") + ");" + NewLine + - "return true;", - defaultBody: b => b.Append(TryCreateDefaultCase)) - .CloseBlock() - .Comment.OpenSummary() - .Append("Creates an instance of ").Comment.SeeRef(target).Append(" from an instance of ").Comment.TypeParamRef(tValueName).Append('.') - .CloseBlock() - .Comment.OpenParam("value") - .Append("The value from which to create an instance of ").Comment.SeeRef(target).Append('.') - .CloseBlock() - .Comment.OpenReturns() - .Append("A new instance of ").Comment.SeeRef(target).Append(" representing ").Comment.ParamRef("value").Append('.') - .CloseBlock() - .Append("public static ").Append(target.Signature.Names.GenericName) - .Append(" Create<").Append(tValueName).Append(">(").Append(tValueName).Append(" value)") - .OpenBracesBlock() - .MetadataNameSwitchStmt( - target: target, - typeExpr: b => b.Append("typeof(").Append(tValueName).Append(")"), - caseBody: (b, t) => _ = b.Operators + - "return " + t.Factory.Name + '(' + UtilUnsafeConvert(tValueName, t.Signature, "value") + ");", - defaultBody: b => b.Append(CreateDefaultCase)) - .CloseBlock() - .CloseBlockCore(); - } - public void CreateDefaultCase(IndentedStringBuilder builder) - { - builder.Append("var sourceType = typeof(").Append(target.Settings.GenericTValueName).AppendLine(");") - .Append("if(!").Append(target.ScopedDataTypeName).Append(".Cache.TryGetValue(sourceType, out var weakMatch))") - .OpenBracesBlock() - .Append("if(!").Append(b => b.UtilIsMarked("sourceType")).Append(')') - .OpenBracesBlock() - .Append(invalidCreationThrow) - .CloseBlock() - .Append(ConversionCacheWeakMatchExpr) - .CloseBlock() - .Append("var match = (System.Func)weakMatch;") - .AppendLine("var matchResult = match.Invoke(value);") - .AppendLine("if(!matchResult.Item1)") - .OpenBracesBlock() - .Append(invalidCreationThrow) - .CloseBlock() - .Append("return matchResult.Item2;") - .AppendLineCore(); - - void invalidCreationThrow(IndentedStringBuilder builder) => - builder.Append("throw new System.InvalidOperationException($\"Unable to create an instance of ") - .Append(target.Signature.Names.FullGenericName) - .Append(" from an instance of {typeof(").Append(target.Settings.GenericTValueName).AppendCore(")}.\");"); - } - public void ConversionCacheWeakMatchExpr(IndentedStringBuilder builder) => - builder.Append("weakMatch = ").Append(target.ScopedDataTypeName).Append(".Cache.GetOrAdd(sourceType, t =>") - .OpenBracesBlock() - .Append("var tupleType = typeof(System.ValueTuple);") - .AppendLine("var matchMethod = sourceType.GetMethod(nameof(Match), System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public)") - .Indent() - .AppendLine("?.MakeGenericMethod(tupleType) ??") - .AppendLine("throw new System.InvalidOperationException(\"Unable to locate match function on source union type. This indicates a bug in the marker detection algorithm.\");") - .Detent() - .Append("var targetFactoryMap = ").Typeof(target.Signature).AppendLine(".GetMethods()") - .Indent() - .Append(".Where(c => c.CustomAttributes.Any(a => a.AttributeType.FullName == \"").Append(UnionTypeFactoryAttribute.MetadataName).AppendLine("\"))") - .AppendLine(".ToDictionary(c => c.GetParameters()[0].ParameterType);") - .Detent() - .AppendLine("var handlers = matchMethod.GetParameters()") - .Indent() - .AppendLine(".Select(p => p.ParameterType.GenericTypeArguments[0])") - .AppendLine(".Select(t => (ParameterExpr: System.Linq.Expressions.Expression.Parameter(t), ParameterExprType: t))") - .Append(".Select(t =>") - .OpenBracesBlock() - .AppendLine("var delegateType = typeof(System.Func<,>).MakeGenericType(t.ParameterExprType, tupleType);") - .AppendLine("System.Linq.Expressions.Expression expression = targetFactoryMap.TryGetValue(t.ParameterExprType, out var factory)") - .Indent() - .AppendLine("? System.Linq.Expressions.Expression.New(tupleType.GetConstructors()[0], System.Linq.Expressions.Expression.Constant(true), System.Linq.Expressions.Expression.Call(factory, t.ParameterExpr))") - .AppendLine(": System.Linq.Expressions.Expression.Default(tupleType);") - .Detent() - .AppendLine("return System.Linq.Expressions.Expression.Lambda(delegateType, expression, t.ParameterExpr);") - .CloseBlock() - .Append(");") - .Detent() - .AppendLine("var paramExpr = System.Linq.Expressions.Expression.Parameter(sourceType);") - .AppendLine("var callExpr = System.Linq.Expressions.Expression.Call(paramExpr, matchMethod, handlers);") - .AppendLine("var lambdaExpr = System.Linq.Expressions.Expression.Lambda(callExpr, paramExpr);") - .AppendLine("var result = lambdaExpr.Compile();") - .AppendLine("return result;") - .CloseBlock() - .Append(");"); - public void TryCreateDefaultCase(IndentedStringBuilder builder) - { - builder.Append("var sourceType = typeof(").Append(target.Settings.GenericTValueName).AppendLine(");") - .Append("if(!").Append(target.ScopedDataTypeName).AppendLine(".Cache.TryGetValue(sourceType, out var weakMatch))") - .OpenBracesBlock() - .Append("if(!RhoMicro.CodeAnalysis.UnionsGenerator.Generated.Util.IsMarked(sourceType))") - .OpenBracesBlock() - .AppendLine("result = default;") - .Append("return false;") - .CloseBlock() - .Append(ConversionCacheWeakMatchExpr) - .CloseBlock() - .Append("var match = (System.Func)weakMatch;") - .AppendLine("var matchResult = match.Invoke(value);") - .AppendLine("if(!matchResult.Item1)") - .OpenBracesBlock() - .AppendLine("result = default;") - .Append("return false;") - .CloseBlock() - .AppendLine("result = matchResult.Item2;") - .Append("return true;") - .AppendLineCore(); - } - public void Fields(IndentedStringBuilder builder) - { - builder.OpenRegionBlock("Fields") - .Append(target.StrategyHostContainer.Value.ReferenceTypeContainerField) - .Append(target.StrategyHostContainer.Value.DedicatedReferenceFields) - .Append(target.StrategyHostContainer.Value.DedicatedImpureAndUnknownFields) - .Append(target.StrategyHostContainer.Value.DedicatedPureValueTypeFields) - .AppendCore(target.StrategyHostContainer.Value.ValueTypeContainerField); - - if(target.RepresentableTypes.Count > 1) - { - builder.Comment.OpenSummary() - .Append("Used to determine the currently represented type and value.") - .CloseBlock() - .GeneratedUnnavigableInternalCode(target) - .Append("private readonly ") - .Append(target.Settings.TagTypeName) - .Append(' ') - .Append(target.Settings.TagFieldName) - .AppendCore(';'); - } - - builder.CloseBlockCore(); - } - public void Constructors(IndentedStringBuilder builder) - { - var ctors = target.RepresentableTypes.Select(t => - new IndentedStringBuilderAppendable(b => - { - var accessibility = target.GetSpecificAccessibility(t.Signature); - b.Comment.OpenSummary() - .Append("Creates a new instance of ").Comment.SeeRef(target).Append("representing an instance of ").Comment.Ref(t.Signature).Append('.') - .CloseBlockCore(); - - if(!t.Factory.RequiresGeneration) - { - b.Comment.OpenRemarks() - .Append("Using this constructor will sidestep any validation or sideeffects defined in the ").Comment.SeeRef(t.Factory.Name).Append(" factory method.") - .CloseBlockCore(); - } - - b.AppendLine(ConstantSources.EditorBrowsableNever) - .AppendLine(ConstantSources.GeneratedCode) - .Append(accessibility).Append(' ').Append(target.Signature.Names.Name) - .Append('(').Append(t.Signature.Names.FullGenericNullableName).Append(" value)") - .OpenBlockCore(Blocks.Braces(b.Options.NewLine)); - - if(target.RepresentableTypes.Count > 1) - { - b.Append(target.Settings.TagFieldName).Append(" = ") - .Append(target.Settings.TagTypeName).Append('.').Append(t.Alias).Append(';') - .AppendLineCore(); - } - - b.Append(t.StorageStrategy.Value - .InstanceVariableAssignmentExpression("value", "this")).Append(';') - .CloseBlockCore(); - })); - - builder.OpenRegionBlock("Constructors") - .AppendJoinLines(StringOrChar.Empty, ctors) - .CloseBlockCore(); - } - public void TagType(IndentedStringBuilder builder) - { - var (representableTypes, settings) = (target.RepresentableTypes, target.Settings); - - if(representableTypes.Count < 2) - return; - - builder - .OpenRegionBlock("Tag Type") - .Comment.OpenSummary() - .Append("Defines tags to discriminate between representable types.") - .CloseBlock() - .GeneratedUnnavigableInternalCode(target) - .Append("private enum ").Append(settings.TagTypeName).Append(" : System.Byte") - .OpenBracesBlock() - .Comment.OpenSummary() - .Append("Used when not representing any type due to e.g. incorrect or missing initialization.") - .CloseBlock() - .Append(target.Settings.TagNoneName) - .AppendLine(',') - .AppendLine() - .AppendJoinLines(representableTypes, (b, t) => - b.Comment.OpenSummary() - .Append("Used when representing an instance of ").Comment.Ref(t.Signature).Append('.') - .CloseBlock() - .Append(t.Alias)) - .CloseBlock() - .CloseBlockCore(); - } - public void JsonConverterType(IndentedStringBuilder builder) - { - var (representableTypes, settings) = (target.RepresentableTypes, target.Settings); - - if(!settings.Miscellaneous.HasFlag(MiscellaneousSettings.GenerateJsonConverter)) - return; - - using var __ = builder.OpenRegionBlockScope("Json Converter Type"); - - builder.Comment.OpenSummary() - .Append("Implements json conversion logic for the ").Comment.Ref(target.Signature).AppendLine(" type.") - .CloseBlock() - .AppendLine(ConstantSources.GeneratedCode) - .Append("public sealed class JsonConverter : System.Text.Json.Serialization.JsonConverter<") - .Append(target.Signature.Names.GenericName).AppendLine('>') - .OpenBracesBlock() - .Append("sealed class Dto") - .OpenBracesBlock() - .Append("public static Dto Create(").Append(target.Signature.Names.GenericName) - .Append(" value) => new()") - .OpenBracesBlock() - .AppendCore("RepresentedType = "); - - if(target.RepresentableTypes.Count == 1) - { - builder.Append('"').Append(target.RepresentableTypes[0].Signature.Names.FullMetadataName).AppendCore('"'); - } else - { - _ = builder.TagSwitchExpr( - target, - (b, t) => b.Append('"').Append(t.Signature.Names.FullMetadataName).AppendCore('"'), - instanceExpr: "value"); - } - - builder.AppendLine(',').AppendCore("RepresentedValue = "); - - if(target.RepresentableTypes.Count == 1) - { - builder.AppendCore( - target.RepresentableTypes[0].StorageStrategy.Value - .InstanceVariableExpression("value")); - } else - { - _ = builder.TagSwitchExpr( - target, - (b, t) => b.AppendCore( - t.StorageStrategy.Value.InstanceVariableExpression(instance: "value")), - instanceExpr: "value"); - } - - builder.CloseBlock() - .AppendLine(';') - .Append("public ").Append(target.Signature.Names.GenericName).AppendLine(" Reconstitute() =>") - .FullMetadataNameSwitchExpr( - target, - "RepresentedType", - (b, t) => - { - var isNullable = t.Options.HasFlag(UnionTypeOptions.Nullable) || t.Signature.Names.FullOpenGenericName == "System.Nullable<>"; - if(isNullable) - { - b.Append(target.Signature.Names.GenericName).Append('.').Append(t.Factory.Name).AppendLine('(') - .Indent() - .AppendLine("RepresentedValue != null") - .Append("? System.Text.Json.JsonSerializer.Deserialize<").Append(t.Signature.Names.FullGenericName).AppendLine(">((System.Text.Json.JsonElement)RepresentedValue)") - .Indent() - .Append("?? throw new System.Text.Json.JsonException(\"Unable to deserialize an instance of the nullable type ") - .Append(t.Signature.Names.FullGenericName) - .Append(" from an unknown value in the RepresentedValue property.\")") - .Detent() - .Append(": null)") - .DetentCore(); - } else - { - b.Append(target.Signature.Names.GenericName).Append('.').Append(t.Factory.Name).AppendLine('(') - .Indent() - .Append("System.Text.Json.JsonSerializer.Deserialize<") - .Append(t.Signature.Names.FullGenericName) - .AppendLine(">(") - .Indent() - .AppendLine("(System.Text.Json.JsonElement)") - .AppendLine("(RepresentedValue") - .Append("?? throw new System.Text.Json.JsonException(\"Unable to deserialize an instance of the non-nullable type ") - .Append(t.Signature.Names.FullGenericName) - .AppendLine(" from a null value in the RepresentedValue property.\")))") - .Detent() - .Append(b => - { - if(t.Signature.Nature is TypeNature.ReferenceType or TypeNature.UnknownType) - { - b.Append("?? throw new System.Text.Json.JsonException(\"Unable to deserialize an instance of the non-nullable type ") - .Append(t.Signature.Names.FullGenericName) - .AppendCore(" from an unknown value in the RepresentedValue property.\")"); - } - }) - .Append(")") - .DetentCore(); - } - }, defaultCase: b => b - .Append("throw new System.Text.Json.JsonException($\"Unable to deserialize a union instance representing an instance of {RepresentedType} as an instance of ") - .Append(target.Signature.Names.FullGenericName).AppendCore("\")")) - .AppendLine(';') - .AppendLine("public System.String RepresentedType { get; set; }") - .AppendLine("public System.Object? RepresentedValue { get; set; }") - .CloseBlock() - .Comment.InheritDoc() - .Append("public override ").Append(target.Signature.Names.GenericName) - .Append(target.Signature.Nature == TypeNature.ReferenceType ? "?" : String.Empty) - .AppendLine(" Read(ref System.Text.Json.Utf8JsonReader reader, System.Type typeToConvert, System.Text.Json.JsonSerializerOptions options)") - .OpenBracesBlock() - .AppendLine("var dto = System.Text.Json.JsonSerializer.Deserialize(ref reader, options);") - .AppendLine("if(dto == null)") - .OpenBracesBlock() - .Append("throw new System.Text.Json.JsonException(\"Unable to deserialize union instance from invalid dto.\");") - .CloseBlock() - .AppendLine("var result = dto.Reconstitute();") - .AppendLine("return result;") - .CloseBlock() - .Comment.InheritDoc() - .Append("public override void Write(System.Text.Json.Utf8JsonWriter writer, ") - .Append(target.Signature.Names.GenericName) - .AppendLine(" value, System.Text.Json.JsonSerializerOptions options) => System.Text.Json.JsonSerializer.Serialize(writer, Dto.Create(value), options);") - .CloseBlockCore(); - } - public void ValueTypeContainerType(IndentedStringBuilder builder) - { - using var _ = builder.OpenRegionBlockScope("Value Type Container"); - target.StrategyHostContainer.Value.ValueTypeContainerType(builder); - } - public void NestedTypes(IndentedStringBuilder builder) - { - using var _ = builder.OpenRegionBlockScope("Nested Types"); - - ValueTypeContainerType(builder); - TagType(builder); - JsonConverterType(builder); - } -} diff --git a/UnionsGenerator/Transformation/Visitors/IVisitor.cs b/UnionsGenerator/Transformation/Visitors/IVisitor.cs deleted file mode 100644 index 9adede0..0000000 --- a/UnionsGenerator/Transformation/Visitors/IVisitor.cs +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Transformation.Visitors; - -using RhoMicro.CodeAnalysis.UnionsGenerator.Models; - -/// -/// Represents a visitor to visit models. -/// -/// The type of model to visit. -public interface IVisitor -{ - /// - /// Visits a model. - /// - /// - /// Implementations should never call s own - /// function, - /// as that will likely induce a . - /// - /// The model to visit. - void Visit(TModel model); -} diff --git a/UnionsGenerator/Transformation/Visitors/SourceTextVisitor.cs b/UnionsGenerator/Transformation/Visitors/SourceTextVisitor.cs deleted file mode 100644 index 1315f41..0000000 --- a/UnionsGenerator/Transformation/Visitors/SourceTextVisitor.cs +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Transformation.Visitors; - -using RhoMicro.CodeAnalysis.Library.Text; -using RhoMicro.CodeAnalysis.UnionsGenerator.Models; - -internal sealed class SourceTextVisitor(IndentedStringBuilder builder) : IVisitor -{ - private readonly IndentedStringBuilder _builder = builder; - - public void Visit(UnionTypeModel model) => _builder.Append(new AppendableSourceText(model)); - - public override String ToString() => _builder.ToString(); -} diff --git a/UnionsGenerator/Transformation/Visitors/StructuralRepresentationVisitor.cs b/UnionsGenerator/Transformation/Visitors/StructuralRepresentationVisitor.cs deleted file mode 100644 index 21e9021..0000000 --- a/UnionsGenerator/Transformation/Visitors/StructuralRepresentationVisitor.cs +++ /dev/null @@ -1,228 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Transformation.Visitors -{ - using RhoMicro.CodeAnalysis; - using RhoMicro.CodeAnalysis.Library.Text; - using RhoMicro.CodeAnalysis.UnionsGenerator.Models; - - internal sealed class StructuralRepresentationVisitor(IndentedStringBuilder builder) : IVisitor - { - private readonly IndentedStringBuilder _builder = builder; - - public static StructuralRepresentationVisitor Create() => new(new()); - public static StructuralRepresentationVisitor Create(IndentedStringBuilderOptions builderOptions) => new(new(builderOptions)); - - public void Visit(UnionTypeModel model) => _builder.ModelString(model); - - public override String ToString() => _builder.ToString(); - } -} - -namespace RhoMicro.CodeAnalysis.Library.Text -{ - using RhoMicro.CodeAnalysis.UnionsGenerator.Models; - using RhoMicro.CodeAnalysis.UnionsGenerator.Models.Storage; - - internal partial class Blocks - { - public static Block SameLineBraces = new('{', '}'); - } - - internal partial class IndentedStringBuilder - { - private IndentedStringBuilder OpenSameLineBracesBlock() => OpenBlock(Blocks.SameLineBraces); - private IndentedStringBuilder CloseSameLineBracesBlock() => CloseBlock(); - public IndentedStringBuilder ModelString(RelatedTypeModel model) => - OpenSameLineBracesBlock() - .Property(nameof(model.Signature), ModelString, model.Signature) - .Property(nameof(model.RepresentableTypeSignatures), ModelString, model.RepresentableTypeSignatures, isLast: true) - .CloseSameLineBracesBlock(); - public IndentedStringBuilder ModelString(StorageStrategy strategy) => - OpenSameLineBracesBlock() - .Property(nameof(strategy.SelectedOption), strategy.SelectedOption) - .Property(nameof(strategy.ActualOption), strategy.ActualOption) - .Property(nameof(strategy.FieldName), strategy.FieldName) - .Property(nameof(strategy.NullableFieldQuestionMark), strategy.NullableFieldQuestionMark) - .Property(nameof(strategy.Violation), strategy.Violation, isLast: true) - .CloseSameLineBracesBlock(); - public IndentedStringBuilder ModelString(RelationModel model) => - OpenSameLineBracesBlock() - .Property(nameof(model.RelatedType), ModelString, model.RelatedType) - .Property(nameof(model.RelationType), model.RelationType, isLast: true) - .CloseSameLineBracesBlock(); - public IndentedStringBuilder ModelString(TypeNamesModel model) => - OpenSameLineBracesBlock() - .Property(nameof(model.CommentRefString), model.CommentRefString) - .Property(nameof(model.ContainingTypesString), model.ContainingTypesString) - .Property(nameof(model.FullGenericName), model.FullGenericName) - .Property(nameof(model.FullGenericNullableName), model.FullGenericNullableName) - .Property(nameof(model.FullMetadataName), model.FullMetadataName) - .Property(nameof(model.FullOpenGenericName), model.FullOpenGenericName) - .Property(nameof(model.GenericName), model.GenericName) - .Property(nameof(model.IdentifierOrHintName), model.IdentifierOrHintName) - .Property(nameof(model.FullIdentifierOrHintName), model.FullIdentifierOrHintName) - .Property(nameof(model.OpenGenericName), model.OpenGenericName) - .Property(nameof(model.TypeArgsString), model.TypeArgsString) - .Property(nameof(model.Name), model.Name) - .Property(nameof(model.Namespace), model.Namespace, isLast: true) - .CloseSameLineBracesBlock(); - public IndentedStringBuilder ModelString(SettingsModel model) => - OpenSameLineBracesBlock() - #region Settings - .Property(nameof(model.ToStringSetting), model.ToStringSetting) - .Property(nameof(model.Layout), model.Layout) - .Property(nameof(model.DiagnosticsLevel), model.DiagnosticsLevel) - .Property(nameof(model.ConstructorAccessibility), model.ConstructorAccessibility) - .Property(nameof(model.InterfaceMatchSetting), model.InterfaceMatchSetting) - .Property(nameof(model.EqualityOperatorsSetting), model.EqualityOperatorsSetting) - .Property(nameof(model.Miscellaneous), model.Miscellaneous) - #endregion - #region Strings - .Property(nameof(model.TypeDeclarationPreface), model.TypeDeclarationPreface) - .Property(nameof(model.GenericTValueName), model.GenericTValueName) - .Property(nameof(model.TryConvertTypeName), model.TryConvertTypeName) - .Property(nameof(model.MatchTypeName), model.MatchTypeName) - .Property(nameof(model.TagTypeName), model.TagTypeName) - .Property(nameof(model.ValueTypeContainerTypeName), model.ValueTypeContainerTypeName) - .Property(nameof(model.ValueTypeContainerName), model.ValueTypeContainerName) - .Property(nameof(model.ReferenceTypeContainerName), model.ReferenceTypeContainerName) - .Property(nameof(model.TagFieldName), model.TagFieldName) - .Property(nameof(model.TagNoneName), model.TagNoneName) - .Property(nameof(model.JsonConverterTypeName), model.JsonConverterTypeName, isLast: true) - #endregion - .CloseSameLineBracesBlock(); - public IndentedStringBuilder ModelString(GroupsModel model) => - OpenSameLineBracesBlock() - .Property(nameof(model.Names), model.Names) - .Property(nameof(model.Groups), ModelString, model.Groups, isLast: true) - .CloseSameLineBracesBlock(); - public IndentedStringBuilder ModelString(GroupModel model) => - OpenSameLineBracesBlock() - .Property(nameof(model.Name), model.Name) - .Property(nameof(model.Members), ModelString, model.Members) - .CloseSameLineBracesBlock(); - public IndentedStringBuilder ModelString(UnionTypeModel model) => - OpenSameLineBracesBlock() - .Property(nameof(model.Groups), ModelString, model.Groups) - .Property(nameof(model.IsEqualsRequired), model.IsEqualsRequired) - .Property(nameof(model.IsGenericType), model.IsGenericType) - .Property(nameof(model.ScopedDataTypeName), model.ScopedDataTypeName) - .Property(nameof(model.Signature), ModelString, model.Signature) - .Property(nameof(model.RepresentableTypes), ModelString, model.RepresentableTypes) - .Property(nameof(model.Relations), ModelString, model.Relations) - .Property(nameof(model.Settings), ModelString, model.Settings, isLast: true) - .CloseSameLineBracesBlock(); - public IndentedStringBuilder ModelString(FactoryModel model) => - OpenSameLineBracesBlock() - .Property(nameof(model.Name), model.Name) - .Property(nameof(model.Parameter), ModelString, model.Parameter) - .Property(nameof(model.RequiresGeneration), model.RequiresGeneration, isLast: true) - .CloseSameLineBracesBlock(); - public IndentedStringBuilder ModelString(RepresentableTypeModel model) => - OpenSameLineBracesBlock() - .Property(nameof(model.Alias), model.Alias) - .Property(nameof(model.IsBaseClassToUnionType), model.IsBaseClassToUnionType) - .Property(nameof(model.OmitConversionOperators), model.OmitConversionOperators) - .Property(nameof(model.Options), model.Options) - .Property(nameof(model.Storage), model.Storage) - .Property(nameof(model.Groups), model.Groups) - .Property(nameof(model.Factory), ModelString, model.Factory) - .Property(nameof(model.StorageStrategy), ModelString, model.StorageStrategy.Value) - .Property(nameof(model.Signature), ModelString, model.Signature, isLast: true) - .CloseSameLineBracesBlock(); - public IndentedStringBuilder ModelString(TypeSignatureModel model) => - OpenSameLineBracesBlock() - .Property(nameof(model.HasNoBaseClass), model.HasNoBaseClass) - .Property(nameof(model.IsGenericType), model.IsGenericType) - .Property(nameof(model.IsInterface), model.IsInterface) - .Property(nameof(model.IsNullableAnnotated), model.IsNullableAnnotated) - .Property(nameof(model.IsRecord), model.IsRecord) - .Property(nameof(model.IsStatic), model.IsStatic) - .Property(nameof(model.IsTypeParameter), model.IsTypeParameter) - .Property(nameof(model.TypeArgs), model.TypeArgs.Select(a => a.Names.FullGenericNullableName)) - .Property(nameof(model.DeclarationKeyword), model.DeclarationKeyword) - .Property(nameof(model.Nature), model.Nature) - .Property(nameof(model.Names), ModelString, model.Names, isLast: true) - .CloseSameLineBracesBlock(); - private IndentedStringBuilder Property(String name, T value, Boolean isLast = false) - where T : struct, Enum - => Property(name, Enum, value, isLast); - private IndentedStringBuilder Property(String name, Boolean value, Boolean isLast = false) - => Property(name, Append, value.ToString(), isLast); - private IndentedStringBuilder Property(String name, String value, Boolean isLast = false) - => Property(name, Literal, value, isLast); - private IndentedStringBuilder Property(String name, IEnumerable value, Boolean isLast = false) - => Property(name, Literals, value, isLast); - private IndentedStringBuilder Property( - String name, - Func valueAppend, - IEnumerable values, - Boolean isLast = false) - { - Append(name).AppendCore(": ["); - - using var enumerator = values.GetEnumerator(); - if(enumerator.MoveNext()) - _ = valueAppend.Invoke(enumerator.Current); - - while(enumerator.MoveNext()) - { - Append(',').AppendLineCore(); - _ = valueAppend.Invoke(enumerator.Current); - } - - AppendCore(']'); - if(!isLast) - Append(',').AppendLineCore(); - - return this; - } - private IndentedStringBuilder Property( - String name, - Func valueAppend, - T value, - Boolean isLast = false) - { - Append(name).AppendCore(": "); - _ = valueAppend.Invoke(value); - if(!isLast) - Append(',').AppendLineCore(); - - return this; - } - private IndentedStringBuilder Enum(T value) - where T : struct, Enum - => Append(value.ToString()); - private IndentedStringBuilder Literal(String literal) - { - LiteralCore(literal); - return this; - } - private IndentedStringBuilder Literals(IEnumerable literals) - { - AppendCore('['); - - using var enumerator = literals.GetEnumerator(); - if(enumerator.MoveNext()) - LiteralCore(enumerator.Current); - - while(enumerator.MoveNext()) - { - AppendCore(','); - LiteralCore(enumerator.Current); - } - - AppendCore(']'); - - return this; - } - private void LiteralCore(String literal) - { - if(literal == null) - AppendCore("null"); - else - Append('"').Append(literal).AppendCore('"'); - } - } -} diff --git a/UnionsGenerator/UnionsGenerator.csproj b/UnionsGenerator/UnionsGenerator.csproj deleted file mode 100644 index 4b97e8e..0000000 --- a/UnionsGenerator/UnionsGenerator.csproj +++ /dev/null @@ -1,58 +0,0 @@ - - - - - netstandard2.0 - false - true - true - true - - - - true - true - - Generate hybrid (tagged/type) union types. - - Source Generator; Union Types; Unions - https://raw.githubusercontent.com/PaulBraetz/RhoMicro.CodeAnalysis/master/UnionsGenerator/PackageLogo.svg - - - - $(DefineConstants);UNIONS_GENERATOR - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - diff --git a/UnionsGenerator/Usings.cs b/UnionsGenerator/Usings.cs deleted file mode 100644 index f84698c..0000000 --- a/UnionsGenerator/Usings.cs +++ /dev/null @@ -1,3 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -global using RhoMicro.CodeAnalysis.Library; diff --git a/UnionsGenerator/Utils/ConstantSources.cs b/UnionsGenerator/Utils/ConstantSources.cs deleted file mode 100644 index 5f28bb2..0000000 --- a/UnionsGenerator/Utils/ConstantSources.cs +++ /dev/null @@ -1,142 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Utils; - -using System; - -internal static partial class ConstantSources -{ - public static String GeneratedCode = $"[System.CodeDom.Compiler.GeneratedCodeAttribute(\"RhoMicro.CodeAnalysis.UnionsGenerator\", \"{typeof(ConstantSources).Assembly.GetName().Version}\")]"; - public const String EditorBrowsableNever = "[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]"; - public static String Util = - $$""" - // - // This file was generated by RhoMicro.CodeAnalysis.UnionsGenerator - // The tool used to generate this code may be subject to license terms; - // this generated code is however not subject to those terms, instead it is - // subject to the license (if any) applied to the containing project. - // - #nullable enable - #pragma warning disable - - namespace RhoMicro.CodeAnalysis.UnionsGenerator.Generated - { - using System.Collections.Concurrent; - using System.Collections.Generic; - using System.Text; - using System.Linq; - using System; - - {{GeneratedCode}} - {{EditorBrowsableNever}} - internal static class Util - { - private readonly static ConcurrentDictionary _cache = new(); - internal static String GetFullString(Type type) => _cache.GetOrAdd(type, ValueFactory); - static String ValueFactory(Type type) - { - var result = getString(type, new()); - - return result; - - static String getString(Type type, StringBuilder builder) - { - var unboundTransitiveParameters = 0; - var transitiveParameters = new List<(String Format, Type? Argument)>(); - append(type, builder, transitiveParameters, ref unboundTransitiveParameters); - var result = builder.ToString(); - - for(var i = 0; i < transitiveParameters.Count; i++) - { - _ = builder.Clear(); - var (format, argument) = transitiveParameters[i]; - var replacement = getString(argument!, builder); - result = result.Replace(format, replacement); - } - - return result; - - static void append( - Type type, - StringBuilder builder, - List<(String Format, Type? Argument)> transitiveArgumentsMap, - ref Int32 unboundTransitiveParameters) - { - if(type.IsGenericParameter && type.DeclaringMethod is null) - { - var format = $"{Guid.NewGuid()}"; - _ = builder.Append(format); - transitiveArgumentsMap.Add((format, null)); - unboundTransitiveParameters++; - return; - } else if(type.DeclaringType != null) - { - append(type.DeclaringType, builder, transitiveArgumentsMap, ref unboundTransitiveParameters); - _ = builder.Append('.'); - } else if(type.Namespace != null) - { - _ = builder.Append(type.Namespace) - .Append('.'); - } - - var tickIndex = type.Name.IndexOf('`'); - _ = tickIndex != -1 ? - builder.Append(type.Name.Substring(0, tickIndex)) : - builder.Append(type.Name); - - var arguments = type.GetGenericArguments(); - var inflectionPoint = unboundTransitiveParameters; - if(arguments.Length > 0 && unboundTransitiveParameters > 0) - { - for(; unboundTransitiveParameters > 0;) - { - unboundTransitiveParameters--; - var (format, _) = transitiveArgumentsMap[unboundTransitiveParameters]; - transitiveArgumentsMap[unboundTransitiveParameters] = (format, arguments[unboundTransitiveParameters]); - } - } - - if(arguments.Length > inflectionPoint) - { - _ = builder.Append('<'); - append(arguments[inflectionPoint], builder, transitiveArgumentsMap, ref unboundTransitiveParameters); - - for(var i = inflectionPoint + 1; i < type.GenericTypeArguments.Length; i++) - { - _ = builder.Append(", "); - append(arguments[i], builder, transitiveArgumentsMap, ref unboundTransitiveParameters); - } - - _ = builder.Append('>'); - } - } - } - } - - internal static System.Boolean IsMarked(Type type) => - type.CustomAttributes.Any(a => a.AttributeType.FullName == "{{Qualifications.NonGenericFullMetadataName}}") || - type.GenericTypeArguments.Any(t => t.CustomAttributes.Any(a => - a.AttributeType.FullName.StartsWith("{{Qualifications.GenericFullMetadataName}}") - && a.AttributeType.GenericTypeArguments.Length < {{Qualifications.MaxRepresentableTypesCount}})); - - private static readonly System.Collections.Concurrent.ConcurrentDictionary<(Type, Type), Object> _conversionImplementations = new(); - internal static TTo UnsafeConvert(in TFrom from) - { - var impl = (System.Func)_conversionImplementations.GetOrAdd((typeof(TFrom), typeof(TTo)), k => - { - var param = System.Linq.Expressions.Expression.Parameter(k.Item1); - var castExpr = System.Linq.Expressions.Expression.Convert(param, k.Item2); - var lambda = System.Linq.Expressions.Expression.Lambda(castExpr, param).Compile(); - - return lambda; - }); - var result = impl.Invoke(from); - - return result; - } - } - } - """; - - public const String InvalidTagStateThrow = "throw new System.InvalidOperationException(\"Unable to determine the represented type or value. The union type was likely not initialized correctly.\")"; -} diff --git a/UnionsGenerator/Utils/EnumerableExtensions.cs b/UnionsGenerator/Utils/EnumerableExtensions.cs deleted file mode 100644 index 7d6577c..0000000 --- a/UnionsGenerator/Utils/EnumerableExtensions.cs +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Utils; -using System; -using System.Collections.Generic; -using System.Text; - -internal static class EnumerableExtensions -{ - public static IEnumerable DistinctBy(this IEnumerable values, Func discriminatorSelector) - { - var yieldedValueDiscriminators = new HashSet(); - foreach(var value in values) - { - var discriminator = discriminatorSelector.Invoke(value); - if(yieldedValueDiscriminators.Add(discriminator)) - yield return value; - } - } -} diff --git a/UnionsGenerator/Utils/Qualifications.cs b/UnionsGenerator/Utils/Qualifications.cs deleted file mode 100644 index ce22781..0000000 --- a/UnionsGenerator/Utils/Qualifications.cs +++ /dev/null @@ -1,139 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Utils; - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -using System; -using System.Runtime.CompilerServices; -using System.Threading; - -internal static class Qualifications -{ - public const String NonGenericFullMetadataName = MetadataNamespace + "." + NonGenericMetadataName; - public const String NonGenericMetadataName = "UnionTypeAttribute"; - public const String MetadataNamespace = "RhoMicro.CodeAnalysis"; - public const String NonGenericRelationMetadataName = "RelationAttribute"; - public const String FactoryMetadataName = "UnionTypeFactoryAttribute"; - public const String GenericFullMetadataName = MetadataNamespace + "." + GenericMetadataName; - public const String GenericMetadataName = NonGenericMetadataName + "`"; - private static readonly Dictionary _genericNames = - Enumerable.Range(1, MaxRepresentableTypesCount).ToDictionary(i => i, i => $"{GenericFullMetadataName}{i}"); - public static String GetGenericFullMetadataName(Int32 typeParamCount) => _genericNames[typeParamCount]; - public static IEnumerable GenericMetadataNames => _genericNames.Values; - - public const Int32 MaxRepresentableTypesCount = 255; //limit to 255 due to tag type being byte + None tag - - public static Boolean IsUnionTypeSymbol(INamedTypeSymbol symbol, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - var attributes = symbol.GetAttributes(); - foreach(var attribute in attributes) - { - cancellationToken.ThrowIfCancellationRequested(); - if(IsUnionTypeAttribute(attribute) - || IsRelationAttribute(attribute) - || IsUnionTypeSettingsAttribute(attribute)) - { - return true; - } - } - - cancellationToken.ThrowIfCancellationRequested(); - var result = symbol is INamedTypeSymbol named && - named.GetMembers() - .OfType() - .Any(IsUnionTypeFactorySymbol); - - return result; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Boolean IsRelationAttribute(this AttributeData data) => - data.IsAttributesNamespaceAttribute() - && ( data.AttributeClass?.MetadataName.StartsWith(NonGenericRelationMetadataName) ?? false ); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static Boolean IsAttributesNamespaceAttribute(this AttributeData data) => - data.AttributeClass?.ContainingNamespace.ToDisplayString( - SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted)) - == MetadataNamespace; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Boolean IsUnionTypeDeclarationAttribute(this AttributeData data) => - data.IsAttributesNamespaceAttribute() - && data.AttributeClass!.MetadataName.StartsWith(GenericMetadataName) - && data.AttributeClass.TypeArguments.Length < MaxRepresentableTypesCount; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Boolean IsUnionTypeParameterAttribute(this AttributeData data) => - data.IsAttributesNamespaceAttribute() - && data.AttributeClass?.MetadataName == NonGenericMetadataName - && data.AttributeClass.TypeArguments.Length == 0; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Boolean IsUnionTypeAttribute(this AttributeData data) => data.IsUnionTypeParameterAttribute() || data.IsUnionTypeDeclarationAttribute(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Boolean IsUnionTypeSettingsAttribute(this AttributeData data) => - data.IsAttributesNamespaceAttribute() - && data.AttributeClass?.MetadataName == typeof(UnionTypeSettingsAttribute).Name; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Boolean IsUnionTypeParameterSyntax(SyntaxNode? node, CancellationToken cancellationToken) - { - //checking [UnionType] on type parameter - - cancellationToken.ThrowIfCancellationRequested(); - //only type parameters are valid targets - if(node is not TypeParameterSyntax tps) - return false; - - cancellationToken.ThrowIfCancellationRequested(); - //the containing declaration must be a type declaration that is itself a valid target (partial, class/struct etc) - //TypeParameter<-TypeParameterList<-TypeDeclaration - return IsUnionTypeDeclarationSyntax(tps.Parent?.Parent, cancellationToken); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Boolean IsUnionTypeDeclarationSyntax(SyntaxNode? node, CancellationToken cancellationToken) - { - //checking [UnionType] on type declaration - - cancellationToken.ThrowIfCancellationRequested(); - //only classes & structs are valid targets; records & interfaces are excluded - if(node is not ClassDeclarationSyntax and not StructDeclarationSyntax) - return false; - - cancellationToken.ThrowIfCancellationRequested(); - //declaration must be partial - return ( (TypeDeclarationSyntax)node ).Modifiers.Any(SyntaxKind.PartialKeyword); - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Boolean IsUnionTypeFactoryAttribute(AttributeData data) => - data.IsAttributesNamespaceAttribute() && - data.AttributeClass?.MetadataName == FactoryMetadataName; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Boolean IsUnionTypeFactorySymbol(IMethodSymbol methodSymbol) - { - var result = methodSymbol.IsStatic - && !SymbolEqualityComparer.IncludeNullability.Equals(methodSymbol.ReturnType, methodSymbol.ContainingSymbol) - && methodSymbol.Parameters.Length == 1 - && methodSymbol.TypeParameters.Length == 0 - && methodSymbol.Parameters[0].GetAttributes().Any(IsUnionTypeFactoryAttribute); - - return result; - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Boolean IsUnionTypeFactoryDeclarationSyntax(SyntaxNode? node, CancellationToken cancellationToken) - { - //checking [UnionTypeFactory] on static factory methods - - cancellationToken.ThrowIfCancellationRequested(); - //check that target is actually a method - if(node is not ParameterSyntax paramSyntax) - return false; - - cancellationToken.ThrowIfCancellationRequested(); - //check target method is static, takes one parameter and is not generic - return paramSyntax.Parent?.Parent is MethodDeclarationSyntax mds && - mds.Modifiers.Any(SyntaxKind.StaticKeyword) && - mds.ParameterList.Parameters.Count == 1 && - mds.TypeParameterList?.Parameters.Count is 0 or null; - } -} diff --git a/UnionsGenerator/Utils/Throw.cs b/UnionsGenerator/Utils/Throw.cs deleted file mode 100644 index bc1f477..0000000 --- a/UnionsGenerator/Utils/Throw.cs +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.UnionsGenerator.Utils; -using System; -using System.Collections.Generic; -using System.Text; - -internal static class Throw -{ - public static void ArgumentNull(T value, String valueName) - where T : class => - _ = value ?? throw new ArgumentNullException(valueName); -} diff --git a/UnionsGenerator/requirements.md b/UnionsGenerator/requirements.md deleted file mode 100644 index 13ec4e1..0000000 --- a/UnionsGenerator/requirements.md +++ /dev/null @@ -1,74 +0,0 @@ -# New UnionsGenerator Design - -## Terminology - -The term `union` refers to a tagged union or sum type, or an instance thereof. -It is able to represent an instance of a type present in its set of representable types. - -The term `Union` refers to a nonspecific generated type implementing the defined union. - -The term `variant` refers to a type representable by a union. -Unless improperly initialized, a union is always representing an instance of one of its variants. - -The term `value` refers to the variant instance being represented by a union. - -The term `adapter` refers to the type adapting the union onto interfaces implemented by all its variants. - -## Requirements - -| id | description | issues | met (×/✓) | -|----|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------|-----------| -| 1 | An error is issued if `Union` is not partial. | #151 | × | -| 2 | Interfaces implemented by all variants are implemented by an adapter property on the union. | #40 | × | -| 3 | Conflicting interface member implementations on the adapter are implemented explicitly. | #40 | × | -| 4 | An error is issued for `ref` like variants. | | × | -| 4 | An error is issued for `record` unions. | | × | -| 5 | A warning is issued for non-nullable reference variant on struct union. | | × | -| 6 | An error is issued if more than 63 variant groups are defined. This is to allow groups to be modelled using a [Flags] enum. | | × | -| 7 | Variant group names have diagnostics mapped onto their definition in the attribute usage. | | × | -| 8 | Variant names have diagnostics mapped onto their definition in the attribute usage. | | × | -| 9 | The backing type of the `VariantKind` is chosen as the smallest integral datatype able to accommodate all variants. | | × | -| 10 | An option is provided to box managed struct variants. | | × | -| 11 | Managed struct variants are stored in dedicated fields by default. | | × | -| 12 | An error is issued for static variants. | | × | -| 13 | An error is issued for conflicting variant names. | | × | -| 14 | An error is issued if no unmanaged, managed struct or nullable reference variant is defined for a struct union. This is to prevent struct unions to be left in an invalid state when uninitialized. | | × | -| 15 | Variants are ordered by unmanaged, managed struct, nullable reference, reference, fully qualified type name. This is to force the default variant to be valid when the struct union is uninitialized. | | × | -| 16 | If exactly one variant is defined, an implicit conversion to that variant is defined. | | × | -| 17 | If multiple variants are defined, an explicit conversion to each variant is defined. | | × | -| 18 | If a validation method is implemented, then an explicit conversion from the validated variant is defined. | | × | -| 19 | If a validation method is not implemented, then an implicit conversion from the variant is defined. | | × | -| 20 | Documentation comments are emitted for every generated member. | | × | -| 21 | Parameter lists are wrapped and indented. | | × | -| 22 | The backing type of the `VariantGroupKinds` is chosen as the smallest integral datatype able to accommodate all variants groups. | | × | -| 23 | `Union` methods are grouped by the variant they are specific to. | | × | -| 24 | Variant group names are ordered alphabetically, except for the default group `None`, which must always be the first element. | | × | -| 25 | | | × | -| 26 | | | × | -| 27 | | | × | -| 28 | | | × | -| 29 | | | × | -| 30 | | | × | - -## Notes - -### TODO - -- relations -- json support - -### Table Template - -| id | signature | description | conditions | issues | met (×/✓) | -|----|-----------|-------------|------------|--------|-----------| -| 1 | | | | | × | -| 2 | | | | | × | -| 3 | | | | | × | -| 4 | | | | | × | -| 5 | | | | | × | -| 6 | | | | | × | -| 7 | | | | | × | -| 8 | | | | | × | -| 9 | | | | | × | -| 10 | | | | | × | - diff --git a/UnionsGenerator/tailwind.extension.json b/UnionsGenerator/tailwind.extension.json deleted file mode 100644 index f546671..0000000 --- a/UnionsGenerator/tailwind.extension.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/theron-wang/VS2022-Editor-Support-for-Tailwind-CSS/refs/heads/main/tailwind.extension.schema.json", - "ConfigurationFiles": [], - "BuildFiles": [], - "PackageConfigurationFile": null, - "CustomRegexes": { - "Razor": { - "Override": false, - "Values": [] - }, - "HTML": { - "Override": false, - "Values": [] - }, - "JavaScript": { - "Override": false, - "Values": [] - } - }, - "UseCli": false -} \ No newline at end of file From deba3dabee3fc3eaa58c7bff5fbb53993bdc1073 Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Tue, 9 Dec 2025 03:13:19 +0100 Subject: [PATCH 17/36] increment ikvm msbuild version --- JsonSchemaGenerator.Cli/JsonSchemaGenerator.Cli.csproj | 2 +- JsonSchemaGenerator/JsonSchemaGenerator.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/JsonSchemaGenerator.Cli/JsonSchemaGenerator.Cli.csproj b/JsonSchemaGenerator.Cli/JsonSchemaGenerator.Cli.csproj index 27cec79..5b2f8db 100644 --- a/JsonSchemaGenerator.Cli/JsonSchemaGenerator.Cli.csproj +++ b/JsonSchemaGenerator.Cli/JsonSchemaGenerator.Cli.csproj @@ -13,7 +13,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/JsonSchemaGenerator/JsonSchemaGenerator.csproj b/JsonSchemaGenerator/JsonSchemaGenerator.csproj index 1c8b61a..53b91cc 100644 --- a/JsonSchemaGenerator/JsonSchemaGenerator.csproj +++ b/JsonSchemaGenerator/JsonSchemaGenerator.csproj @@ -29,7 +29,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 02bc09795321d8a52ec100f1f416dfbc93b7a7b3 Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Tue, 9 Dec 2025 03:13:38 +0100 Subject: [PATCH 18/36] add solution name --- Directory.Build.props | 1 + 1 file changed, 1 insertion(+) diff --git a/Directory.Build.props b/Directory.Build.props index 2fe2bc3..b4a0e28 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -8,6 +8,7 @@ enable True true + RhoMicro.CodeAnalysis $(SolutionName) $(SolutionName).$(MSBuildProjectName) From 2299568771874293513b5e4ea62c6bbd02e127ba Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Tue, 9 Dec 2025 03:13:47 +0100 Subject: [PATCH 19/36] remove globals.json --- global.json | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 global.json diff --git a/global.json b/global.json deleted file mode 100644 index a11f48e..0000000 --- a/global.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "sdk": { - "version": "10.0.0", - "rollForward": "latestMajor", - "allowPrerelease": true - } -} \ No newline at end of file From 065233fd1bd292d1082d33d8489c4bb695d9d8a2 Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Tue, 9 Dec 2025 03:13:59 +0100 Subject: [PATCH 20/36] project changes --- RhoMicro.CodeAnalysis.slnx | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/RhoMicro.CodeAnalysis.slnx b/RhoMicro.CodeAnalysis.slnx index 027ae17..f8eb793 100644 --- a/RhoMicro.CodeAnalysis.slnx +++ b/RhoMicro.CodeAnalysis.slnx @@ -1,13 +1,13 @@ - + + + - - @@ -17,15 +17,20 @@ + + + + + + + - + - - - + @@ -40,6 +45,6 @@ - + \ No newline at end of file From 27318a5643fd5f25c43528e9a9a84556d3e634c5 Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Tue, 9 Dec 2025 03:14:12 +0100 Subject: [PATCH 21/36] add Equa --- Equa/ComponentFactory.cs | 21 +++ Equa/ContainingTypeModel.cs | 47 ++++++ Equa/Equa.csproj | 45 ++++++ Equa/Generator.cs | 218 ++++++++++++++++++++++++++++ Equa/MemberModel.cs | 5 + Equa/Properties/launchSettings.json | 8 + Equa/README.md | 1 + Equa/RecordComponent.cs | 175 ++++++++++++++++++++++ Equa/RecordModel.cs | 17 +++ Equa/RecordNameComponent.cs | 18 +++ Equa/ValueEqualityAttribute.cs | 12 ++ 11 files changed, 567 insertions(+) create mode 100644 Equa/ComponentFactory.cs create mode 100644 Equa/ContainingTypeModel.cs create mode 100644 Equa/Equa.csproj create mode 100644 Equa/Generator.cs create mode 100644 Equa/MemberModel.cs create mode 100644 Equa/Properties/launchSettings.json create mode 100644 Equa/README.md create mode 100644 Equa/RecordComponent.cs create mode 100644 Equa/RecordModel.cs create mode 100644 Equa/RecordNameComponent.cs create mode 100644 Equa/ValueEqualityAttribute.cs diff --git a/Equa/ComponentFactory.cs b/Equa/ComponentFactory.cs new file mode 100644 index 0000000..4cc2f67 --- /dev/null +++ b/Equa/ComponentFactory.cs @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Lyra; + +using Equa; +using Library.Models.Collections; + +partial class ComponentFactory +{ + + public static ListComponent, MemberModel, String> List( + EquatableList list, + Action append, + String separator = "", + String terminator = "") + => List, MemberModel>( + list, + append, + separator, + terminator); +} diff --git a/Equa/ContainingTypeModel.cs b/Equa/ContainingTypeModel.cs new file mode 100644 index 0000000..3457eba --- /dev/null +++ b/Equa/ContainingTypeModel.cs @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Equa; + +using System.Collections.Immutable; + +internal readonly record struct ContainingTypeModel( + String Modifier, + String Name, + ImmutableArray TypeParameters) +{ + public Boolean Equals(ContainingTypeModel other) + { + if (other.Modifier != Modifier) + { + return false; + } + + if (other.Name != Name) + { + return false; + } + + if (!other.TypeParameters.SequenceEqual(TypeParameters)) + { + return false; + } + + return true; + } + + public override Int32 GetHashCode() + { + var hc = new HashCode(); + hc.Add(Modifier); + hc.Add(Name); + + foreach (var typeParameter in TypeParameters) + { + hc.Add(typeParameter); + } + + var result = hc.ToHashCode(); + + return result; + } +} diff --git a/Equa/Equa.csproj b/Equa/Equa.csproj new file mode 100644 index 0000000..6533c42 --- /dev/null +++ b/Equa/Equa.csproj @@ -0,0 +1,45 @@ + + + + netstandard2.0 + true + true + true + enable + true + + + + + true + true + + Generates equality implementations that take into account value equality of collections. + + Source Generator; Equality; Value Equality; Collection Equality + + + + $(DefineConstants);RHOMICRO_CODEANALYSIS_EQUA + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/Equa/Generator.cs b/Equa/Generator.cs new file mode 100644 index 0000000..4bfc1f3 --- /dev/null +++ b/Equa/Generator.cs @@ -0,0 +1,218 @@ +namespace RhoMicro.CodeAnalysis.Equa; + +using System.Collections.Immutable; +using System.Runtime.InteropServices; +using Generated; +using Library.Models; +using Lyra; +using Microsoft.CodeAnalysis; + +/// +/// Generates collection aware value equality implementations for +/// and . +/// +[Generator(LanguageNames.CSharp)] +public sealed partial class Generator : IIncrementalGenerator +{ + private static readonly SymbolDisplayFormat _namespaceFormat = new( + globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted, + typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces); + + private static readonly CSharpSourceBuilderOptions _sourceBuilderOptions = new() + { + Prelude = static (b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.AppendLine( + $""" + // + // This file was generated using the Equa source generator. + // SPDX-License-Identifier: MPL-2.0 + // + """); + } + }; + + /// + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var sourceProvider = context.SyntaxProvider.ForAttributeWithMetadataName( + typeof(ValueEqualityAttribute).FullName, + static (n, ct) => + { + ct.ThrowIfCancellationRequested(); + + return true; + }, static (ctx, ct) => + { + ct.ThrowIfCancellationRequested(); + + if (ctx.TargetSymbol is not { ContainingType: { } target }) + { + return null; + } + + using var collections = ModelCreationContext.CreateDefault(ct); + + var containingTypes = collections.CollectionFactory.CreateList(); + if (target.ContainingType is { } t) + { + appendContainingType(t); + } + + var @namespace = target.ContainingNamespace.ToDisplayString(_namespaceFormat) ?? String.Empty; + var typeParameters = collections.CollectionFactory.CreateList(); + var vectorMembers = collections.CollectionFactory.CreateList(); + var scalarMembers = collections.CollectionFactory.CreateList(); + + for (var i = 0; i < target.TypeParameters.Length; i++) + { + ct.ThrowIfCancellationRequested(); + + typeParameters[i] = target.TypeParameters[i].Name; + } + + foreach (var member in target.GetMembers()) + { + ct.ThrowIfCancellationRequested(); + + if (member is not + { + CanBeReferencedByName: true, + Kind: SymbolKind.Property or SymbolKind.Field, + IsStatic: false + }) + { + continue; + } + + var memberType = member switch + { + IFieldSymbol f => f.Type, + IPropertySymbol p => p.Type, + _ => null + }; + + if (memberType is null) + { + continue; + } + + var memberModel = new MemberModel( + Type: memberType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), + Name: member.Name); + + if (ImplementsIEnumerable(memberType, ct)) + { + vectorMembers.Add(memberModel); + } + else + { + scalarMembers.Add(memberModel); + } + } + + var result = new RecordModel( + target.TypeKind, + target.IsRecord, + target.Name, + @namespace, + typeParameters, + containingTypes, + vectorMembers, + scalarMembers); + + return result; + + void appendContainingType(INamedTypeSymbol type) + { + if (type.ContainingType is { } t) + { + appendContainingType(t); + } + + var typeParameters = new String[type.Arity]; + + for (var i = 0; i < type.TypeParameters.Length; i++) + { + ct.ThrowIfCancellationRequested(); + + typeParameters[i] = type.TypeParameters[i].Name; + } + + var containingType = new ContainingTypeModel( + $"{(type.IsRecord ? "record " : String.Empty)}{(type.TypeKind is TypeKind.Struct ? "struct" : "class")}", + type.Name, + ImmutableCollectionsMarshal.AsImmutableArray(typeParameters)); + + containingTypes.Add(containingType); + } + })! + .Where(m => m is not null) + .Select(static (m, ct) => + { + ct.ThrowIfCancellationRequested(); + + var builder = new CSharpSourceBuilder(_sourceBuilderOptions).SetCancellationToken(ct); + + var hintName = builder + .SetCondition(m.Namespace is not []) + .Append($"{m.Namespace}.") + .UnsetCondition() + .Append(m.Name) + .SetCondition(m.TypeParameters is not []) + .Append($"`{m.TypeParameters.Count}") + .UnsetCondition() + .Append(".g.cs") + .ToString(); + + var source = builder.Clear().Append(new RecordComponent(m)).ToString(); + + return (hintName, source); + }); + + context.RegisterSourceOutput(sourceProvider, static (ctx, t) => ctx.AddSource(t.hintName, t.source)); + IncludedFileSources.RegisterToContext(context); + } + + private static Boolean ImplementsIEnumerable(ITypeSymbol type, CancellationToken ct) + { + ct.ThrowIfCancellationRequested(); + + foreach (var @interface in type.AllInterfaces) + { + ct.ThrowIfCancellationRequested(); + + var isIEnumerable = @interface is + { + TypeKind: TypeKind.Interface, + Name: nameof(IEnumerable<>), + TypeArguments: [{ }], + ContainingNamespace: + { + Name: "Generic", + ContainingNamespace: + { + Name: "Collections", + ContainingNamespace: + { + Name: "System", + ContainingNamespace: + { + IsGlobalNamespace: true + } + } + } + } + }; + + if (isIEnumerable) + { + return true; + } + } + + return false; + } +} diff --git a/Equa/MemberModel.cs b/Equa/MemberModel.cs new file mode 100644 index 0000000..9894699 --- /dev/null +++ b/Equa/MemberModel.cs @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Equa; + +internal readonly record struct MemberModel(String Type, String Name); diff --git a/Equa/Properties/launchSettings.json b/Equa/Properties/launchSettings.json new file mode 100644 index 0000000..fd87e49 --- /dev/null +++ b/Equa/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Janus": { + "commandName": "DebugRoslynComponent", + "targetProject": "../Janus/Janus.csproj" + } + } +} diff --git a/Equa/README.md b/Equa/README.md new file mode 100644 index 0000000..06f909c --- /dev/null +++ b/Equa/README.md @@ -0,0 +1 @@ +# Equa diff --git a/Equa/RecordComponent.cs b/Equa/RecordComponent.cs new file mode 100644 index 0000000..4b6e254 --- /dev/null +++ b/Equa/RecordComponent.cs @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Equa; + +using Library.Models.Collections; +using Lyra; +using Microsoft.CodeAnalysis; + +internal readonly record struct RecordComponent(RecordModel Model) : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var type = ComponentFactory.Create(Model, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append("partial") + .SetCondition(m.IsRecord) + .Append(" record") + .UnsetCondition() + .Append(m.TypeKind is TypeKind.Struct ? " struct " : " class ") + .Append(new RecordNameComponent(m)) + .SetCondition(m.IsRecord) + .Append(" : global::System.IEquatable<") + .Append(m.Name) + .SetCondition(m.TypeParameters is not []) + .Append($"<{ComponentFactory.List(m.TypeParameters, separator: ", ")}>") + .UnsetCondition() + .Append('>') + .UnsetCondition() + .AppendLine() + .Append($$""" + { + public override int GetHashCode() + { + var hc = new global::System.HashCode(); + + {{ComponentFactory.List(m.ScalarMembers, static (m, i, l, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"hc.Add(this.{m});"); + }, "\n")}} + + {{ComponentFactory.List(m.VectorMembers, static (m, i, l, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($$""" + foreach(var element in {{m}}) + { + hc.Add(element); + } + """); + }, "\n")}} + + var result = hc.ToHashCode(); + + return result; + } + + {{ComponentFactory.Create(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + if (m.IsRecord) + { + return; + } + + b.Append("public override bool Equals(object? other) => other is ") + .Append(new RecordNameComponent(m)) + .Append(" o && Equals(o);"); + })}} + + {{ComponentFactory.Create(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + if (m.IsRecord) + { + return; + } + + b.Append($$""" + public {{(m.IsRecord ? String.Empty : "override ")}}bool Equals({{new RecordNameComponent(m)}}{{(m.TypeKind is TypeKind.Struct ? String.Empty : "?")}} other) + { + {{ComponentFactory.Create(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + if (m.TypeKind is TypeKind.Struct) + { + return; + } + + b.Append(""" + if(other is null) + { + return false; + } + """ + ); + })}} + + {{ComponentFactory.List(m.ScalarMembers, static (m, i, l, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($$""" + if(!global::System.Collections.Generic.EqualityComparer<{{m.Type}}>.Default.Equals(this.{{m.Name}}, other.{{m.Name}})) + { + return false; + } + """); + }, "\n")}} + + {{ComponentFactory.List(m.VectorMembers, static (m, i, l, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($$""" + if(!this.{{m.Name}}.SequenceEqual(other.{{m.Name}})) + { + return false; + } + """); + }, "\n")}} + + return true; + } + """ + ); + })}} + } + """ + ); + }); + + var containingTypes = ComponentFactory.Create((Model, type), static (t, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (model, type) = t; + + b.AppendLine("using System.Linq;"); + + foreach (var containingType in model.ContainingTypes) + { + b.Append($"{containingType.Modifier} {containingType.Name}"); + if (containingType.TypeParameters is not []) + { + b.Append($"<{ComponentFactory.List(containingType.TypeParameters, separator: ", ")}>"); + } + + b.AppendLine().AppendLine('{').Indent(); + } + + b.AppendLine(type); + + for (var i = 0; i < model.ContainingTypes.Count; i++) + { + b.Detent().AppendLine('}'); + } + }); + + var @namespace = ComponentFactory.Namespace( + Model.Namespace, + containingTypes); + + builder.Append(@namespace); + } +} diff --git a/Equa/RecordModel.cs b/Equa/RecordModel.cs new file mode 100644 index 0000000..2b6a3ef --- /dev/null +++ b/Equa/RecordModel.cs @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Equa; + +using System.Collections.Immutable; +using Library.Models.Collections; +using Microsoft.CodeAnalysis; + +internal sealed record RecordModel( + TypeKind TypeKind, + Boolean IsRecord, + String Name, + String Namespace, + EquatableList TypeParameters, + EquatableList ContainingTypes, + EquatableList VectorMembers, + EquatableList ScalarMembers); diff --git a/Equa/RecordNameComponent.cs b/Equa/RecordNameComponent.cs new file mode 100644 index 0000000..43deb4c --- /dev/null +++ b/Equa/RecordNameComponent.cs @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Equa; + +using Lyra; + +internal readonly record struct RecordNameComponent(RecordModel Model) : ICSharpSourceComponent +{ + public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + builder.Append(Model.Name) + .SetCondition(Model.TypeParameters is not []) + .Append($"<{ComponentFactory.List(Model.TypeParameters, separator: ", ")}>") + .UnsetCondition(); + } +} diff --git a/Equa/ValueEqualityAttribute.cs b/Equa/ValueEqualityAttribute.cs new file mode 100644 index 0000000..70950de --- /dev/null +++ b/Equa/ValueEqualityAttribute.cs @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis; + +using System; + +#if RHOMICRO_CODEANALYSIS_EQUA +[RhoMicro.CodeAnalysis.IncludeFile] +[RhoMicro.CodeAnalysis.GenerateFactory] +#endif +[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +internal sealed partial class ValueEqualityAttribute : Attribute; From 66756276f521084c8ceefbeebc4500d731ea1656 Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Tue, 9 Dec 2025 03:14:26 +0100 Subject: [PATCH 22/36] utilize janus --- DslGenerator.TestApp/DslGenerator.TestApp.csproj | 5 +++-- DslGenerator/DslGenerator.csproj | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/DslGenerator.TestApp/DslGenerator.TestApp.csproj b/DslGenerator.TestApp/DslGenerator.TestApp.csproj index 8f47357..c3ded4c 100644 --- a/DslGenerator.TestApp/DslGenerator.TestApp.csproj +++ b/DslGenerator.TestApp/DslGenerator.TestApp.csproj @@ -22,10 +22,11 @@ - + + - \ No newline at end of file + diff --git a/DslGenerator/DslGenerator.csproj b/DslGenerator/DslGenerator.csproj index 9fe7d37..b109d5c 100644 --- a/DslGenerator/DslGenerator.csproj +++ b/DslGenerator/DslGenerator.csproj @@ -40,7 +40,8 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + + From f52ac7c9ddb64f04909f6c9456cab3f4257e7a5c Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Tue, 9 Dec 2025 03:14:35 +0100 Subject: [PATCH 23/36] utilize lyra --- CSharpSourceBuilder.TestApplication/Program.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/CSharpSourceBuilder.TestApplication/Program.cs b/CSharpSourceBuilder.TestApplication/Program.cs index cd0d93f..551df15 100644 --- a/CSharpSourceBuilder.TestApplication/Program.cs +++ b/CSharpSourceBuilder.TestApplication/Program.cs @@ -1,6 +1,7 @@ // See https://aka.ms/new-console-template for more information using RhoMicro.CodeAnalysis; +using RhoMicro.CodeAnalysis.Lyra; using var builder = new CSharpSourceBuilder(); From d085ba03e7ceb03ef502ca97feeeb23067cb8400 Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Sat, 20 Dec 2025 10:02:24 +0100 Subject: [PATCH 24/36] fix all warnings --- ...CSharpSourceBuilder.TestApplication.csproj | 15 - .../Program.cs | 8 - .../MacroExpansions/TailExpansion.cs | 2 + .../Comments/CommentContents.cs | 16 - .../Comments/DocumentationComment.cs | 11 - DocReflect.Library/Comments/Empty.cs | 16 - DocReflect.Library/Comments/Example.cs | 17 - DocReflect.Library/Comments/Param.cs | 23 - DocReflect.Library/Comments/Remarks.cs | 17 - DocReflect.Library/Comments/Returns.cs | 17 - DocReflect.Library/Comments/Summary.cs | 17 - DocReflect.Library/Comments/Typeparam.cs | 22 - DocReflect.Library/DocReflect.Library.csproj | 47 -- DocReflect.Library/Documentation.cs | 66 -- .../EnumerableExtensions.Internal.cs | 35 - .../DocumentationProviderAttribute.cs | 12 - .../Infrastructure/IDocumentationProvider.cs | 30 - DocReflect.Library/MethodDocumentation.cs | 51 -- DocReflect.Library/PropertyDocumentation.cs | 29 - DocReflect.Library/README.md | 3 - DocReflect.Library/TypeDocumentation.cs | 40 - .../TypeExtensions.KeyValuePairComparer.cs | 18 - DocReflect.Library/TypeExtensions.cs | 116 --- DocReflect/DocReflect.csproj | 74 -- DocReflect/Generators/DocReflect.cs | 91 --- .../Generators/ExtractCommentStepResult.cs | 28 - DocReflect/Generators/FinalStepResult.cs | 5 - DocReflect/Generators/ParseStepResult.cs | 27 - .../Comments/CommentContents.Internal.cs | 13 - .../Comments/DocumentationComment.Internal.cs | 11 - .../Library/TypeDocumentation.Internal.cs | 17 - DocReflect/Properties/launchSettings.json | 8 - DocReflect/README.md | 3 - .../DslGenerator.TestApp.csproj | 32 - DslGenerator.TestApp/Program.cs | 11 - DslGenerator.TestApp/README.md | 3 - DslGenerator.TestApp/Rmbnf.rmbnf | 32 - DslGenerator.Tests/DslGenerator.Tests.csproj | 29 - DslGenerator.Tests/GlobalUsings.cs | 4 - DslGenerator.Tests/ParserTests.cs | 271 ------- DslGenerator.Tests/TokenizerTests.cs | 124 --- DslGenerator/Analysis/Diagnostic.cs | 23 - DslGenerator/Analysis/DiagnosticDescriptor.cs | 23 - .../Analysis/DiagnosticDescriptors.cs | 26 - .../Analysis/DiagnosticsCollection.cs | 39 - DslGenerator/DslGenerator.csproj | 56 -- .../Attributes/GeneratedGrammarAttribute.cs | 11 - .../Attributes/GeneratedRuleListAttribute.cs | 14 - DslGenerator/Generators/DslGenerator.cs | 102 --- .../Generators/GeneratedRuleListGenerator.cs | 271 ------- DslGenerator/Grammar/DisplayStringVisitor.cs | 25 - DslGenerator/Grammar/IndentedStringBuilder.cs | 37 - DslGenerator/Grammar/ListExtensions.cs | 50 -- DslGenerator/Grammar/Name.cs | 48 -- DslGenerator/Grammar/NamedRuleList.cs | 36 - DslGenerator/Grammar/Rule.Alternative.cs | 34 - DslGenerator/Grammar/Rule.Any.cs | 29 - DslGenerator/Grammar/Rule.Concatenation.cs | 32 - DslGenerator/Grammar/Rule.Grouping.cs | 32 - DslGenerator/Grammar/Rule.OptionalGrouping.cs | 31 - DslGenerator/Grammar/Rule.Range.cs | 31 - DslGenerator/Grammar/Rule.Reference.cs | 32 - .../Grammar/Rule.SpecificRepetition.cs | 33 - DslGenerator/Grammar/Rule.Terminal.cs | 41 - .../Grammar/Rule.VariableRepetition.cs | 31 - DslGenerator/Grammar/Rule.cs | 14 - DslGenerator/Grammar/RuleBuilder.cs | 144 ---- .../Grammar/RuleDefinition.Incremental.cs | 28 - DslGenerator/Grammar/RuleDefinition.New.cs | 29 - DslGenerator/Grammar/RuleDefinition.cs | 20 - DslGenerator/Grammar/RuleList.cs | 36 - DslGenerator/Grammar/RuleListBuilder.cs | 50 -- DslGenerator/Grammar/SyntaxNode.cs | 117 --- DslGenerator/Grammar/SyntaxNodeVisitor.cs | 22 - DslGenerator/Grammar/Utils.cs | 16 - DslGenerator/Lexing/Lexeme.cs | 43 - DslGenerator/Lexing/Lexemes.cs | 30 - DslGenerator/Lexing/Location.cs | 65 -- DslGenerator/Lexing/SourceText.cs | 40 - DslGenerator/Lexing/StringSlice.cs | 50 -- DslGenerator/Lexing/Token.cs | 29 - DslGenerator/Lexing/TokenType.cs | 30 - DslGenerator/Lexing/TokenizeResult.cs | 15 - DslGenerator/Lexing/Tokenizer.cs | 302 ------- DslGenerator/Lexing/Tokens.cs | 24 - DslGenerator/Parsing/ParseResult.cs | 16 - DslGenerator/Parsing/Parser.cs | 373 --------- DslGenerator/Properties/launchSettings.json | 8 - DslGenerator/README.md | 3 - DslGenerator/Usings.cs | 4 - .../Generators/EnumConstStringGenerator.cs | 4 + EnumStringTestApp/EnumStringTestApp.csproj | 14 - EnumStringTestApp/GlobalSuppressions.cs | 10 - EnumStringTestApp/Program.cs | 18 - .../IndentedStringBuilderTestApp.csproj | 21 - IndentedStringBuilderTestApp/Program.cs | 73 -- Janus.Analyzers/AnalyzerReleases.Shipped.md | 25 - Janus.Analyzers/AnalyzerReleases.Unshipped.md | 0 .../{ => Attributes}/UnionTypeAttribute.cs | 0 .../UnionTypeSettingsAttribute.cs | 10 +- .../Components/ConstructorComponent.cs | 2 +- .../Components/FactoriesComponent.cs | 72 +- .../Components/FactoryComponent.cs | 24 +- .../Components/OperatorsComponent.cs | 2 +- .../Components/ValidationComponent.cs | 2 +- Janus.Analyzers/DiagnosticDescriptors.cs | 8 + Janus.Analyzers/Janus.Analyzers.csproj | 5 - Janus.Analyzers/JanusAnalyzer.cs | 87 ++ Janus.Benchmarks/Directory.build.props | 5 + Janus.Benchmarks/Janus.Benchmarks.csproj | 23 + Janus.Benchmarks/Program.cs | 6 + Janus.Benchmarks/SwitchStateBenchmark.cs | 38 + .../UnmanagedOverlayingBenchmark.cs | 26 + Janus.TestApplication/Program.cs | 8 +- Janus.Tests.EndToEnd/IsPropertyTests.cs | 1 + Janus.Tests.EndToEnd/ReadMeAssertions.cs | 2 + Janus.Tests.EndToEnd/TryAsFunctionsTests.cs | 1 + Janus.Tests/Janus.Tests.csproj | 10 +- Janus.Tests/JanusAnalyzerTests.cs | 34 +- Janus.Tests/JanusTest.cs | 4 +- ...oadResolutionPriorityAttributeGenerator.cs | 4 +- Janus/Janus.csproj | 1 + Janus/README.md | 755 ++++-------------- JsonSchemaGenerator.Cli/MainService.cs | 35 +- JsonSchemaGenerator.Cli/Settings.cs | 2 + JsonSchemaGenerator.Tests/AnnotationsTests.cs | 16 +- JsonSchemaGenerator.Tests/CliTests.cs | 12 +- JsonSchemaGenerator.Tests/EnumTests.cs | 14 +- JsonSchemaGenerator.Tests/Extensions.cs | 16 + .../JsonSchemaGenerator.Tests.csproj | 65 +- JsonSchemaGenerator.Tests/ListLikeTests.cs | 122 ++- JsonSchemaGenerator.Tests/MapLikeTests.cs | 12 +- .../NullableReferenceTypeTests.cs | 4 +- .../NullableValueTypesTests.cs | 2 +- JsonSchemaGenerator.Tests/PrimitivesTest.cs | 16 +- JsonSchemaGenerator.Tests/RefTests.cs | 10 +- JsonSchemaGenerator.Tests/TestBase.cs | 10 +- .../JsonSchemaGenerator.csproj | 130 ++- .../Models/Core/JsonListLikeModel.cs | 2 + .../Models/Core/JsonNumberModel.cs | 2 +- .../Models/Core/JsonObjectModel.cs | 2 + .../Models/Core/JsonSchemaModel.cs | 8 + .../Wrappers/AdditionalPropertiesModel.cs | 2 + .../Models/Wrappers/AnnotationsBuilder.cs | 5 + JsonSchemaGenerator/Models/Wrappers/Id.cs | 2 + .../Models/Wrappers/SubSchemaModelBuilder.cs | 32 + Lyra/CSharpSourceBuilder.cs | 4 +- OptionsGenerator.Tests/AnalyzerTests.cs | 14 +- .../OptionsGenerator.Tests.csproj | 68 +- OptionsGenerator/AnalyzerReleases.Shipped.md | 9 - .../AnalyzerReleases.Unshipped.md | 0 OptionsGenerator/Analyzers/OptionsAnalyzer.cs | 4 + .../Generators/OptionsGenerator.cs | 2 + OptionsGenerator/Generators/OptionsModel.cs | 2 + OptionsGenerator/Generators/PropertyModel.cs | 6 + ...ConfigurationFullyQualifiedNameTemplate.cs | 2 + .../ConfigurationNameTemplate.cs | 2 + .../Configuration/ConfigurationTemplate.cs | 2 + ...utableOptionsFullyQualifiedNameTemplate.cs | 2 + .../Immutable/ImmutableOptionsNameTemplate.cs | 2 + .../Immutable/ImmutableOptionsTemplate.cs | 2 + .../Immutable/ImmutablePropertyTemplate.cs | 2 + ...erfaceOptionsFullyQualifiedNameTemplate.cs | 2 + .../Interface/InterfaceOptionsNameTemplate.cs | 2 + .../Interface/InterfaceOptionsTemplate.cs | 2 + .../Templates/MonitorPropertyTemplate.cs | 2 + .../MutableDefaultExpressionTemplate.cs | 2 + ...utableOptionsFullyQualifiedNameTemplate.cs | 2 + .../Mutable/MutableOptionsNameTemplate.cs | 2 + .../Mutable/MutableOptionsTemplate.cs | 2 + .../Mutable/MutablePropertyTemplate.cs | 2 + ...efaultOptionsFullyQualifiedNameTemplate.cs | 2 + .../Passthrough/DefaultOptionsNameTemplate.cs | 2 + ...onitorOptionsFullyQualifiedNameTemplate.cs | 2 + .../Passthrough/MonitorOptionsNameTemplate.cs | 2 + ...assthroughOptionsImplementationTemplate.cs | 2 + .../PassthroughPropertyTemplate.cs | 2 + ...apshotOptionsFullyQualifiedNameTemplate.cs | 2 + .../SnapshotOptionsNameTemplate.cs | 2 + .../Templates/PropertyAnnotationsTemplate.cs | 2 + ...ationStrategyFullyQualifiedNameTemplate.cs | 2 + .../RegistrationStrategyNameTemplate.cs | 2 + .../RegistrationStrategyTemplate.cs | 2 + .../Templates/RootNamespaceTemplate.cs | 2 + .../Generators/Templates/RootTemplate.cs | 2 + .../ServiceCollectionExtensionsTemplate.cs | 2 + OptionsGenerator/OptionsGenerator.csproj | 5 + Project1/Program.cs | 49 -- Project1/Project1.csproj | 21 - Project1/README.md | 3 - RhoMicro.CodeAnalysis.slnx | 66 +- TestApp/Program.cs | 216 ----- TestApp/TestApp.csproj | 38 - TestApp/appsettings.json | 14 - TestApp/appsettings.schema.json | 9 - .../BlockTerminatorsBenchmark.cs | 56 ++ .../ParserMatchBenchmark.cs | 44 +- .../TemplateBenchmark.cs | 2 + .../UtilityGenerators.Benchmarks.csproj | 6 + .../UtilityGenerators.Dev.csproj | 9 +- UtilityGenerators.Tests.E2E/TestAttribute.cs | 2 +- .../UtilityGenerators.Tests.E2E.csproj | 6 + .../Collections/EquatableDictionaryTests.cs | 16 +- .../Library/Collections/EquatableListTests.cs | 30 +- .../Library/Collections/EquatableSetTests.cs | 28 +- .../LazyEquatableDictionaryTests.cs | 16 +- .../Collections/LazyEquatableListTests.cs | 30 +- .../Text/Templating/TemplateRendererTests.cs | 6 + .../Templating/LexerTests.cs | 2 + .../Templating/ParserTests.cs | 14 +- .../Templating/TemplateStringTests.cs | 4 +- .../Templating/TestHelpers.cs | 2 + UtilityGenerators.Tests/TestBase.cs | 8 +- .../UtilityGenerators.Tests.csproj | 63 +- .../AttributeFactoryGenerator.cs | 58 ++ .../AttributeFactory/AttributeFactoryModel.cs | 2 + .../AttributeFactory/ConstructorModel.cs | 18 + .../InitializationMethodModel.cs | 2 + .../AttributeFactory/PropertyModel.cs | 20 +- UtilityGenerators/GlobalUsings.cs | 1 + ...dentedStringBuilderAppendablesGenerator.cs | 22 + .../System/Buffers/ArrayBufferWriter.cs | 8 + .../Library/External/System/HashCode.cs | 10 + .../System/LocalAppContextSwitches.cs | 5 + .../Models/AttributeParameterTypeKind.cs | 2 + .../Collections/DictionaryEqualityComparer.cs | 4 + .../Collections/EnumerableEqualityComparer.cs | 4 + .../Models/Collections/EquatableList.cs | 2 + .../Models/Collections/EquatableSet.cs | 2 + .../Models/Collections/LazyEquatableList.cs | 4 + .../Models/Collections/MutabilityContext.cs | 2 + .../Models/Collections/SetEqualityComparer.cs | 4 + .../Library/Models/NamedTypeModel.cs | 38 + UtilityGenerators/Library/Models/TypeModel.cs | 6 + .../Text/SourceTexts/BlockScopeCollection.cs | 2 + .../Text/SourceTexts/CommentBuilder.cs | 20 + .../Text/SourceTexts/IndentedStringBuilder.cs | 44 + .../IndentedStringBuilderOptions.cs | 6 +- .../DynamicallyAllocatedCharBuffer.cs | 4 + .../Templating/TemplateRenderer.Render.cs | 2 + UtilityGenerators/Library/ThrowHelpers.cs | 58 ++ .../NonEquatable/NonEquatableGenerator.cs | 6 + UtilityGenerators/Templating/Lexer.cs | 2 + UtilityGenerators/Templating/Parser.cs | 8 + .../Templating/SourcePosition.cs | 11 + .../Visitors/CommentXmlTreeStringBuilder.cs | 4 + ...eratingTemplateVisitor.CodeBlockVisitor.cs | 6 + ...ingTemplateVisitor.TemplateBlockVisitor.cs | 6 +- .../SourceGeneratingTemplateVisitor.cs | 6 + .../TemplateStringReconstructionVisitor.cs | 4 + .../Syntax/Visitors/TokenValidator.cs | 6 + .../Syntax/Visitors/XmlTreeStringBuilder.cs | 4 + .../Templating/TemplateAttribute.Model.cs | 2 +- .../Templating/TemplateAttribute.cs | 11 +- .../Templating/TemplateString.cs | 2 + .../Templating/TemplatingGenerator.cs | 156 ++-- .../TypeSymbolPatternGenerator.cs | 136 ++-- UtilityGenerators/bootstrap.sh | 45 ++ .../Generators/Models/NodeModel.cs | 16 + .../Generators/Models/NodeSignatureModel.cs | 6 + .../Generators/Models/PropertyModel.cs | 4 + .../Generators/VisitorGenerator.cs | 22 + VisitorGenerator/VisitorGenerator.csproj | 2 +- WorkerService1/Program.cs | 89 --- WorkerService1/Properties/launchSettings.json | 12 - WorkerService1/WorkerService1.csproj | 21 - WorkerService1/appsettings.Development.json | 8 - WorkerService1/appsettings.json | 14 - 268 files changed, 1871 insertions(+), 5846 deletions(-) delete mode 100644 CSharpSourceBuilder.TestApplication/CSharpSourceBuilder.TestApplication.csproj delete mode 100644 CSharpSourceBuilder.TestApplication/Program.cs delete mode 100644 DocReflect.Library/Comments/CommentContents.cs delete mode 100644 DocReflect.Library/Comments/DocumentationComment.cs delete mode 100644 DocReflect.Library/Comments/Empty.cs delete mode 100644 DocReflect.Library/Comments/Example.cs delete mode 100644 DocReflect.Library/Comments/Param.cs delete mode 100644 DocReflect.Library/Comments/Remarks.cs delete mode 100644 DocReflect.Library/Comments/Returns.cs delete mode 100644 DocReflect.Library/Comments/Summary.cs delete mode 100644 DocReflect.Library/Comments/Typeparam.cs delete mode 100644 DocReflect.Library/DocReflect.Library.csproj delete mode 100644 DocReflect.Library/Documentation.cs delete mode 100644 DocReflect.Library/EnumerableExtensions.Internal.cs delete mode 100644 DocReflect.Library/Infrastructure/DocumentationProviderAttribute.cs delete mode 100644 DocReflect.Library/Infrastructure/IDocumentationProvider.cs delete mode 100644 DocReflect.Library/MethodDocumentation.cs delete mode 100644 DocReflect.Library/PropertyDocumentation.cs delete mode 100644 DocReflect.Library/README.md delete mode 100644 DocReflect.Library/TypeDocumentation.cs delete mode 100644 DocReflect.Library/TypeExtensions.KeyValuePairComparer.cs delete mode 100644 DocReflect.Library/TypeExtensions.cs delete mode 100644 DocReflect/DocReflect.csproj delete mode 100644 DocReflect/Generators/DocReflect.cs delete mode 100644 DocReflect/Generators/ExtractCommentStepResult.cs delete mode 100644 DocReflect/Generators/FinalStepResult.cs delete mode 100644 DocReflect/Generators/ParseStepResult.cs delete mode 100644 DocReflect/Library/Comments/CommentContents.Internal.cs delete mode 100644 DocReflect/Library/Comments/DocumentationComment.Internal.cs delete mode 100644 DocReflect/Library/TypeDocumentation.Internal.cs delete mode 100644 DocReflect/Properties/launchSettings.json delete mode 100644 DocReflect/README.md delete mode 100644 DslGenerator.TestApp/DslGenerator.TestApp.csproj delete mode 100644 DslGenerator.TestApp/Program.cs delete mode 100644 DslGenerator.TestApp/README.md delete mode 100644 DslGenerator.TestApp/Rmbnf.rmbnf delete mode 100644 DslGenerator.Tests/DslGenerator.Tests.csproj delete mode 100644 DslGenerator.Tests/GlobalUsings.cs delete mode 100644 DslGenerator.Tests/ParserTests.cs delete mode 100644 DslGenerator.Tests/TokenizerTests.cs delete mode 100644 DslGenerator/Analysis/Diagnostic.cs delete mode 100644 DslGenerator/Analysis/DiagnosticDescriptor.cs delete mode 100644 DslGenerator/Analysis/DiagnosticDescriptors.cs delete mode 100644 DslGenerator/Analysis/DiagnosticsCollection.cs delete mode 100644 DslGenerator/DslGenerator.csproj delete mode 100644 DslGenerator/Generators/Attributes/GeneratedGrammarAttribute.cs delete mode 100644 DslGenerator/Generators/Attributes/GeneratedRuleListAttribute.cs delete mode 100644 DslGenerator/Generators/DslGenerator.cs delete mode 100644 DslGenerator/Generators/GeneratedRuleListGenerator.cs delete mode 100644 DslGenerator/Grammar/DisplayStringVisitor.cs delete mode 100644 DslGenerator/Grammar/IndentedStringBuilder.cs delete mode 100644 DslGenerator/Grammar/ListExtensions.cs delete mode 100644 DslGenerator/Grammar/Name.cs delete mode 100644 DslGenerator/Grammar/NamedRuleList.cs delete mode 100644 DslGenerator/Grammar/Rule.Alternative.cs delete mode 100644 DslGenerator/Grammar/Rule.Any.cs delete mode 100644 DslGenerator/Grammar/Rule.Concatenation.cs delete mode 100644 DslGenerator/Grammar/Rule.Grouping.cs delete mode 100644 DslGenerator/Grammar/Rule.OptionalGrouping.cs delete mode 100644 DslGenerator/Grammar/Rule.Range.cs delete mode 100644 DslGenerator/Grammar/Rule.Reference.cs delete mode 100644 DslGenerator/Grammar/Rule.SpecificRepetition.cs delete mode 100644 DslGenerator/Grammar/Rule.Terminal.cs delete mode 100644 DslGenerator/Grammar/Rule.VariableRepetition.cs delete mode 100644 DslGenerator/Grammar/Rule.cs delete mode 100644 DslGenerator/Grammar/RuleBuilder.cs delete mode 100644 DslGenerator/Grammar/RuleDefinition.Incremental.cs delete mode 100644 DslGenerator/Grammar/RuleDefinition.New.cs delete mode 100644 DslGenerator/Grammar/RuleDefinition.cs delete mode 100644 DslGenerator/Grammar/RuleList.cs delete mode 100644 DslGenerator/Grammar/RuleListBuilder.cs delete mode 100644 DslGenerator/Grammar/SyntaxNode.cs delete mode 100644 DslGenerator/Grammar/SyntaxNodeVisitor.cs delete mode 100644 DslGenerator/Grammar/Utils.cs delete mode 100644 DslGenerator/Lexing/Lexeme.cs delete mode 100644 DslGenerator/Lexing/Lexemes.cs delete mode 100644 DslGenerator/Lexing/Location.cs delete mode 100644 DslGenerator/Lexing/SourceText.cs delete mode 100644 DslGenerator/Lexing/StringSlice.cs delete mode 100644 DslGenerator/Lexing/Token.cs delete mode 100644 DslGenerator/Lexing/TokenType.cs delete mode 100644 DslGenerator/Lexing/TokenizeResult.cs delete mode 100644 DslGenerator/Lexing/Tokenizer.cs delete mode 100644 DslGenerator/Lexing/Tokens.cs delete mode 100644 DslGenerator/Parsing/ParseResult.cs delete mode 100644 DslGenerator/Parsing/Parser.cs delete mode 100644 DslGenerator/Properties/launchSettings.json delete mode 100644 DslGenerator/README.md delete mode 100644 DslGenerator/Usings.cs delete mode 100644 EnumStringTestApp/EnumStringTestApp.csproj delete mode 100644 EnumStringTestApp/GlobalSuppressions.cs delete mode 100644 EnumStringTestApp/Program.cs delete mode 100644 IndentedStringBuilderTestApp/IndentedStringBuilderTestApp.csproj delete mode 100644 IndentedStringBuilderTestApp/Program.cs delete mode 100644 Janus.Analyzers/AnalyzerReleases.Shipped.md delete mode 100644 Janus.Analyzers/AnalyzerReleases.Unshipped.md rename Janus.Analyzers/{ => Attributes}/UnionTypeAttribute.cs (100%) rename Janus.Analyzers/{ => Attributes}/UnionTypeSettingsAttribute.cs (98%) create mode 100644 Janus.Benchmarks/Directory.build.props create mode 100644 Janus.Benchmarks/Janus.Benchmarks.csproj create mode 100644 Janus.Benchmarks/Program.cs create mode 100644 Janus.Benchmarks/SwitchStateBenchmark.cs create mode 100644 Janus.Benchmarks/UnmanagedOverlayingBenchmark.cs delete mode 100644 OptionsGenerator/AnalyzerReleases.Shipped.md delete mode 100644 OptionsGenerator/AnalyzerReleases.Unshipped.md delete mode 100644 Project1/Program.cs delete mode 100644 Project1/Project1.csproj delete mode 100644 Project1/README.md delete mode 100644 TestApp/Program.cs delete mode 100644 TestApp/TestApp.csproj delete mode 100644 TestApp/appsettings.json delete mode 100644 TestApp/appsettings.schema.json create mode 100644 UtilityGenerators/bootstrap.sh delete mode 100644 WorkerService1/Program.cs delete mode 100644 WorkerService1/Properties/launchSettings.json delete mode 100644 WorkerService1/WorkerService1.csproj delete mode 100644 WorkerService1/appsettings.Development.json delete mode 100644 WorkerService1/appsettings.json diff --git a/CSharpSourceBuilder.TestApplication/CSharpSourceBuilder.TestApplication.csproj b/CSharpSourceBuilder.TestApplication/CSharpSourceBuilder.TestApplication.csproj deleted file mode 100644 index 2fc683e..0000000 --- a/CSharpSourceBuilder.TestApplication/CSharpSourceBuilder.TestApplication.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - Exe - net9.0 - preview - enable - enable - - - - - - - diff --git a/CSharpSourceBuilder.TestApplication/Program.cs b/CSharpSourceBuilder.TestApplication/Program.cs deleted file mode 100644 index 551df15..0000000 --- a/CSharpSourceBuilder.TestApplication/Program.cs +++ /dev/null @@ -1,8 +0,0 @@ -// See https://aka.ms/new-console-template for more information - -using RhoMicro.CodeAnalysis; -using RhoMicro.CodeAnalysis.Lyra; - -using var builder = new CSharpSourceBuilder(); - - diff --git a/CopyToGenerator/MacroExpansions/TailExpansion.cs b/CopyToGenerator/MacroExpansions/TailExpansion.cs index 7cd16c5..f43c764 100644 --- a/CopyToGenerator/MacroExpansions/TailExpansion.cs +++ b/CopyToGenerator/MacroExpansions/TailExpansion.cs @@ -15,7 +15,9 @@ internal sealed class TailExpansion(Model model) : MacroExpansionBase(model, Mac public override void Expand(IExpandingMacroStringBuilder builder, CancellationToken cancellationToken) { if(!Model.IsInGlobalNamespace) + { _ = builder.Append('}'); + } _ = builder.Append('}'); } diff --git a/DocReflect.Library/Comments/CommentContents.cs b/DocReflect.Library/Comments/CommentContents.cs deleted file mode 100644 index ad0d517..0000000 --- a/DocReflect.Library/Comments/CommentContents.cs +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DocReflect.Comments; -using System; - -/// -/// Represents a comments contents. -/// -/// The textual contents of the comment. -public readonly partial record struct CommentContents(String Text) -{ - /// - /// Gets empty comment contents. - /// - public static CommentContents Empty { get; } = new(String.Empty); -} diff --git a/DocReflect.Library/Comments/DocumentationComment.cs b/DocReflect.Library/Comments/DocumentationComment.cs deleted file mode 100644 index d9b98a8..0000000 --- a/DocReflect.Library/Comments/DocumentationComment.cs +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DocReflect.Comments; - -/// -/// Represents a top level documentation comment attached to some member. -/// -/// -/// The contents of this comment. -/// -public abstract partial record DocumentationComment(CommentContents Contents); diff --git a/DocReflect.Library/Comments/Empty.cs b/DocReflect.Library/Comments/Empty.cs deleted file mode 100644 index bdef616..0000000 --- a/DocReflect.Library/Comments/Empty.cs +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DocReflect.Comments; -using System; - -/// -/// Represents an empty or non-existent top-level documentation comment. -/// -public sealed partial record Empty : DocumentationComment -{ - /// - /// Initializes a new instance. - /// - public Empty() : base(CommentContents.Empty) - { } -} diff --git a/DocReflect.Library/Comments/Example.cs b/DocReflect.Library/Comments/Example.cs deleted file mode 100644 index f499c81..0000000 --- a/DocReflect.Library/Comments/Example.cs +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DocReflect.Comments; - -/// -/// Represents a example comment. -/// -public sealed partial record Example : DocumentationComment -{ - /// - /// Initializes a new instance. - /// - /// - public Example(CommentContents content) : base(content) - { - } -} diff --git a/DocReflect.Library/Comments/Param.cs b/DocReflect.Library/Comments/Param.cs deleted file mode 100644 index 45a4514..0000000 --- a/DocReflect.Library/Comments/Param.cs +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DocReflect.Comments; -using System; - -/// -/// Represents a param comment. -/// -public sealed partial record Param : DocumentationComment -{ - /// - /// Initializes a new instance. - /// - /// - /// The name of the parameter referenced. - public Param(CommentContents contents, String name) - : base(contents) - => Name = name; - /// - /// Gets the name of the parameter referenced. - /// - public String Name { get; } -} diff --git a/DocReflect.Library/Comments/Remarks.cs b/DocReflect.Library/Comments/Remarks.cs deleted file mode 100644 index 1014a06..0000000 --- a/DocReflect.Library/Comments/Remarks.cs +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DocReflect.Comments; - -/// -/// Represents a remarks comment. -/// -public sealed partial record Remarks : DocumentationComment -{ - /// - /// Initializes a new instance. - /// - /// - public Remarks(CommentContents content) : base(content) - { - } -} diff --git a/DocReflect.Library/Comments/Returns.cs b/DocReflect.Library/Comments/Returns.cs deleted file mode 100644 index 641f17f..0000000 --- a/DocReflect.Library/Comments/Returns.cs +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DocReflect.Comments; - -/// -/// Represents a returns comment. -/// -public sealed partial record Returns : DocumentationComment -{ - /// - /// Initializes a new instance. - /// - /// - public Returns(CommentContents content) : base(content) - { - } -} diff --git a/DocReflect.Library/Comments/Summary.cs b/DocReflect.Library/Comments/Summary.cs deleted file mode 100644 index 7958952..0000000 --- a/DocReflect.Library/Comments/Summary.cs +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DocReflect.Comments; - -/// -/// Represents a summary comment. -/// -public sealed partial record Summary : DocumentationComment -{ - /// - /// Initializes a new instance. - /// - /// - public Summary(CommentContents content) : base(content) - { - } -} diff --git a/DocReflect.Library/Comments/Typeparam.cs b/DocReflect.Library/Comments/Typeparam.cs deleted file mode 100644 index cecc988..0000000 --- a/DocReflect.Library/Comments/Typeparam.cs +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DocReflect.Comments; - -/// -/// Represents a typeparam comment. -/// -public sealed partial record Typeparam : DocumentationComment -{ - /// - /// Initializes a new instance. - /// - /// - /// The name of the type parameter referenced. - public Typeparam(CommentContents content, String name) - : base(content) - => Name = name; - /// - /// Gets the name of the type parameter referenced. - /// - public String Name { get; } -} diff --git a/DocReflect.Library/DocReflect.Library.csproj b/DocReflect.Library/DocReflect.Library.csproj deleted file mode 100644 index 7c4da3c..0000000 --- a/DocReflect.Library/DocReflect.Library.csproj +++ /dev/null @@ -1,47 +0,0 @@ - - - - netstandard2.0 - RhoMicro.CodeAnalysis.DocReflect - - - - true - true - Provides the types required to inspect type documentation using a reflection-like api. - Source Generator; Documentation; Reflection - - - - - - - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - \ No newline at end of file diff --git a/DocReflect.Library/Documentation.cs b/DocReflect.Library/Documentation.cs deleted file mode 100644 index ec90673..0000000 --- a/DocReflect.Library/Documentation.cs +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DocReflect; - -using RhoMicro.CodeAnalysis.DocReflect.Comments; -using RhoMicro.CodeAnalysis.Library.Models.Collections; - -using System; - -/// -/// Represents a members documentation. -/// -public partial class Documentation : IEquatable -{ - /// - /// Initializes a new instance. - /// - /// All top level documentation comments; in order of declaration. - public Documentation(IEnumerable topLevelComments) - { - _ = topLevelComments ?? throw new ArgumentNullException(nameof(topLevelComments)); - - using var factory = EquatableCollectionFactory.CreateDefault(); - - var tlcList = factory.CreateList(); - - foreach(var tlc in topLevelComments) - { - if(tlc is Summary summary) - Summary ??= summary; - else if(tlc is Remarks remarks) - Remarks ??= remarks; - else if(tlc is Example example) - Example ??= example; - - tlcList.Add(tlc); - } - - TopLevelComments = tlcList; - } - - /// - /// Gets all top level documentation comments; in order of declaration. - /// - public IReadOnlyList TopLevelComments { get; } - /// - /// Gets the summary comment if one exists; otherwise, . - /// - public Summary? Summary { get; } - /// - /// Gets the remarks comment if one exists; otherwise, . - /// - public Remarks? Remarks { get; } - /// - /// Gets the example comment if one exists; otherwise, . - /// - public Example? Example { get; } - /// - public override Boolean Equals(Object? obj) => Equals(obj as Documentation); - /// - public Boolean Equals(Documentation? other) => other is not null && - TopLevelComments.Count == other.TopLevelComments.Count && - TopLevelComments.Equals(other.TopLevelComments); - /// - public override Int32 GetHashCode() => TopLevelComments.GetHashCode(); -} diff --git a/DocReflect.Library/EnumerableExtensions.Internal.cs b/DocReflect.Library/EnumerableExtensions.Internal.cs deleted file mode 100644 index 401f956..0000000 --- a/DocReflect.Library/EnumerableExtensions.Internal.cs +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.Library; - -using RhoMicro.CodeAnalysis.Library.Models.Collections; - -internal static partial class EnumerableExtensions -{ - public static IReadOnlyDictionary ToEquatableNameMap( - this IEnumerable elements, - Func nameSelector, - String elementsName) - { - _ = elements ?? throw new ArgumentNullException(elementsName); - - using var factory = EquatableCollectionFactory.CreateDefault(); - - var map = factory.CreateDictionary(); - - foreach(var element in elements) - { - var name = nameSelector.Invoke(element); - if(map.ContainsKey(name)) - { - throw new ArgumentException( - $"{elementsName} contains duplicate name: {name}", - elementsName); - } - - map.Add(name, element); - } - - return map; - } -} diff --git a/DocReflect.Library/Infrastructure/DocumentationProviderAttribute.cs b/DocReflect.Library/Infrastructure/DocumentationProviderAttribute.cs deleted file mode 100644 index 153468c..0000000 --- a/DocReflect.Library/Infrastructure/DocumentationProviderAttribute.cs +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DocReflect.Infrastructure; - -/// -/// Marks a class as a provider of documentation. -/// In order to be used by the infrastructure, classes -/// marked by this attribute must implement -/// and have a parameterless constructor. -/// -[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] -public sealed class DocumentationProviderAttribute : Attribute; diff --git a/DocReflect.Library/Infrastructure/IDocumentationProvider.cs b/DocReflect.Library/Infrastructure/IDocumentationProvider.cs deleted file mode 100644 index b31f471..0000000 --- a/DocReflect.Library/Infrastructure/IDocumentationProvider.cs +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DocReflect.Infrastructure; - -using System.Reflection; - -/// -/// Provides documentation for types, methods and properties. -/// In order to be considered by the infrastructure, -/// provider types must be marked using -/// and have a parameterless constructor. -/// -public interface IDocumentationProvider -{ - /// - /// Provides a map of documentations onto their types. - /// - /// Documentations associated with their respective type. - IEnumerable> GetTypeDocumentations(); - /// - /// Provides a map of documentations onto their methods. - /// - /// Documentations associated with their respective method. - IEnumerable> GetMethodDocumentations(); - /// - /// Provides a map of documentations onto their properties. - /// - /// Documentations associated with their respective property. - IEnumerable> GetPropertyDocumentations(); -} diff --git a/DocReflect.Library/MethodDocumentation.cs b/DocReflect.Library/MethodDocumentation.cs deleted file mode 100644 index b42b092..0000000 --- a/DocReflect.Library/MethodDocumentation.cs +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DocReflect; - -using RhoMicro.CodeAnalysis.DocReflect.Comments; -using RhoMicro.CodeAnalysis.Library; - -using System.Collections.Generic; - -/// -/// Represents a methods documentation. -/// -public partial class MethodDocumentation : Documentation -{ - /// - /// Initializes a new instance. - /// - /// - /// - /// - /// - /// The methods type parameters. - /// - /// - /// The methods parameters. - /// - public MethodDocumentation( - IEnumerable topLevelComments, - IEnumerable typeParameters, - IEnumerable parameters) - : base(topLevelComments) - { - TypeParameters = typeParameters.ToEquatableNameMap(p => p.Name, nameof(typeParameters)); - Parameters = parameters.ToEquatableNameMap(p => p.Name, nameof(parameters)); - } - - /// - /// Gets an empty type documentation comment. - /// - public static MethodDocumentation Empty { get; } = - new(Array.Empty(), Array.Empty(), Array.Empty()); - - /// - /// Gets the type parameter comments; in order of declaration. - /// - public IReadOnlyDictionary TypeParameters { get; } - /// - /// Gets the parameter comments; in order of declaration. - /// - public IReadOnlyDictionary Parameters { get; } -} diff --git a/DocReflect.Library/PropertyDocumentation.cs b/DocReflect.Library/PropertyDocumentation.cs deleted file mode 100644 index 943be0b..0000000 --- a/DocReflect.Library/PropertyDocumentation.cs +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DocReflect; - -using RhoMicro.CodeAnalysis.DocReflect.Comments; - -using System.Collections.Generic; - -/// -/// Represents a properties documentation. -/// -public partial class PropertyDocumentation : Documentation -{ - /// - /// Initializes a new instance. - /// - /// - /// - /// - public PropertyDocumentation(IEnumerable topLevelComments) - : base(topLevelComments) - { } - - /// - /// Gets an empty type documentation comment. - /// - public static PropertyDocumentation Empty { get; } = - new(Array.Empty()); -} diff --git a/DocReflect.Library/README.md b/DocReflect.Library/README.md deleted file mode 100644 index ffca571..0000000 --- a/DocReflect.Library/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# DocReflect.Library - -Provides the types required to inspect type documentation using a reflection-like api. \ No newline at end of file diff --git a/DocReflect.Library/TypeDocumentation.cs b/DocReflect.Library/TypeDocumentation.cs deleted file mode 100644 index ae6b863..0000000 --- a/DocReflect.Library/TypeDocumentation.cs +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DocReflect; - -using RhoMicro.CodeAnalysis.DocReflect.Comments; -using RhoMicro.CodeAnalysis.Library; - -using System.Collections.Generic; - -/// -/// Represents a types documentation. -/// -public partial class TypeDocumentation : Documentation -{ - /// - /// Initializes a new instance. - /// - /// - /// - /// - /// - /// The types type parameters. - /// - public TypeDocumentation( - IEnumerable topLevelComments, - IEnumerable typeParameters) - : base(topLevelComments) - => TypeParameters = typeParameters.ToEquatableNameMap(p => p.Name, nameof(typeParameters)); - - /// - /// Gets an empty type documentation comment. - /// - public static TypeDocumentation Empty { get; } = - new(Array.Empty(), Array.Empty()); - - /// - /// Gets the type parameter comments; in order of declaration. - /// - public IReadOnlyDictionary TypeParameters { get; } -} diff --git a/DocReflect.Library/TypeExtensions.KeyValuePairComparer.cs b/DocReflect.Library/TypeExtensions.KeyValuePairComparer.cs deleted file mode 100644 index a5e3bff..0000000 --- a/DocReflect.Library/TypeExtensions.KeyValuePairComparer.cs +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DocReflect; -public static partial class Extensions -{ - private sealed class KeyValuePairComparer : IEqualityComparer> - { - public KeyValuePairComparer() { } - - private static readonly EqualityComparer _keyComparer = EqualityComparer.Default; - - public Boolean Equals(KeyValuePair x, KeyValuePair y) => - _keyComparer.Equals(x.Key, y.Key); - - public Int32 GetHashCode(KeyValuePair obj) => - _keyComparer.GetHashCode(obj.Key); - } -} diff --git a/DocReflect.Library/TypeExtensions.cs b/DocReflect.Library/TypeExtensions.cs deleted file mode 100644 index fb34838..0000000 --- a/DocReflect.Library/TypeExtensions.cs +++ /dev/null @@ -1,116 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DocReflect; - -using DocReflect.Infrastructure; - -using System.Collections.Concurrent; -using System.ComponentModel; -using System.Reflection; - -/// -/// Contains reflection extensions required to access type, method and property documentation. -/// -public static partial class Extensions -{ - private static readonly Lazy> _providers = - new(() => AppDomain.CurrentDomain - .GetAssemblies() - .SelectMany(a => a.GetTypes()) - .Where(t => - t.CustomAttributes.Any(a => a.AttributeType == typeof(DocumentationProviderAttribute)) && - typeof(IDocumentationProvider).IsAssignableFrom(t) && - t.GetConstructors().Any(c => c.IsPublic && c.GetParameters().Length == 0)) - .Distinct() - .Select(Activator.CreateInstance) - .OfType() - .ToList()); - private static readonly Lazy> _typeDocs = - new(() => _providers.Value - .SelectMany(p => - { - try - { - return p.GetTypeDocumentations(); - } catch - { - return Array.Empty>(); - } - }) - .Distinct(new KeyValuePairComparer()) - .ToDictionary(kvp => kvp.Key, kvp => kvp.Value)); - private static readonly Lazy> _methodDocs = - new(() => _providers.Value - .SelectMany(p => - { - try - { - return p.GetMethodDocumentations(); - } catch - { - return Array.Empty>(); - } - }) - .Distinct(new KeyValuePairComparer()) - .ToDictionary(kvp => kvp.Key, kvp => kvp.Value)); - private static readonly Lazy> _propertyDocs = - new(() => _providers.Value - .SelectMany(p => - { - try - { - return p.GetPropertyDocumentations(); - } catch - { - return Array.Empty>(); - } - }) - .Distinct(new KeyValuePairComparer()) - .ToDictionary(kvp => kvp.Key, kvp => kvp.Value)); - - /// - /// Gets the documentation available for this type. - /// - /// The type whose documentation to locate. - /// - /// The documentation located for , - /// if one could be located; otherwise, . - public static TypeDocumentation GetDocumentation(this Type type) - { - var result = _typeDocs.Value.TryGetValue(type, out var r) ? - r : - TypeDocumentation.Empty; - - return result; - } - /// - /// Gets the documentation available for this method. - /// - /// The method whose documentation to locate. - /// - /// The documentation located for , - /// if one could be located; otherwise, . - public static MethodDocumentation GetDocumentation(this MethodInfo method) - { - var result = _methodDocs.Value.TryGetValue(method, out var r) ? - r : - MethodDocumentation.Empty; - - return result; - } - /// - /// Gets the documentation available for this property. - /// - /// The property whose documentation to locate. - /// - /// The documentation located for , - /// if one could be located; otherwise, . - public static PropertyDocumentation GetDocumentation(this PropertyInfo property) - { - var result = _propertyDocs.Value.TryGetValue(property, out var r) ? - r : - PropertyDocumentation.Empty; - - return result; - } -} diff --git a/DocReflect/DocReflect.csproj b/DocReflect/DocReflect.csproj deleted file mode 100644 index a8f8c3c..0000000 --- a/DocReflect/DocReflect.csproj +++ /dev/null @@ -1,74 +0,0 @@ - - - - - netstandard2.0 - false - true - true - true - enable - enable - - - - true - true - Generates reflection-like API to examine member documentation. - Source Generator; Documentation; Reflection - - - - $(DefineConstants);GENERATOR - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - diff --git a/DocReflect/Generators/DocReflect.cs b/DocReflect/Generators/DocReflect.cs deleted file mode 100644 index 894a93c..0000000 --- a/DocReflect/Generators/DocReflect.cs +++ /dev/null @@ -1,91 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DocReflect.Generators; - -using RhoMicro.CodeAnalysis.Library.Text.SourceTexts; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -using System.Collections.Immutable; -using System.Xml; - -using static RhoMicro.CodeAnalysis.Library.Text.SourceTexts.IndentedStringBuilder.Appendables; - -/// -/// Generates documentation providers for DocReflect. -/// -[Generator(LanguageNames.CSharp)] -public sealed class DocReflect : IIncrementalGenerator -{ - /// - public void Initialize(IncrementalGeneratorInitializationContext context) - { - var provider = context.SyntaxProvider.CreateSyntaxProvider( - (node, ct) => node is TypeDeclarationSyntax, - ExtractCommentStep) - .Select(ParseStep) - .Collect() - .Select(FinalStep); - } - - private static ParseStepResult ParseStep(ExtractCommentStepResult result, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - private static FinalStepResult FinalStep(ImmutableArray results, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var options = IndentedStringBuilderOptions.GeneratedFile with - { - AmbientCancellationToken = cancellationToken, - GeneratorName = "RhoMicro.CodeAnalysis.DocReflect" - }; - - var builder = new IndentedStringBuilder(options) - .Append("namespace RhoMicro.CodeAnalysis.Generated.DocReflect") - .OpenBracesBlock() - .Append("internal sealed class TypeDocumentationProvider : RhoMicro.CodeAnalysis.DocReflect.Infrastructure.IDocumentationProvider") - .OpenBracesBlock() - .AppendLine("/// ") - .AppendLine("public IEnumerable> GetMethodDocumentations() => Array.Empty>();") - .AppendLine("/// ") - .AppendLine("public IEnumerable> GetMethodDocumentations() => Array.Empty>();") - .AppendLine("/// ") - .AppendLine("public IEnumerable> GetTypeDocumentations() =>") - .OpenBlock(Blocks.Brackets with - { - PlaceDelimitersOnNewLine = true, - Indentation = options.DefaultIndentation - }); - - var sourceText = builder.CloseAllBlocks().ToString(); - const String hintName = "TypeDocumentationProvider.g.cs"; - var result = new FinalStepResult(hintName, sourceText); - - return result; - } - private static ExtractCommentStepResult ExtractCommentStep(GeneratorSyntaxContext context, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - var targetSymbol = context.SemanticModel.GetSymbolInfo(context.Node, cancellationToken).Symbol; - if(targetSymbol == null) - return default; - - cancellationToken.ThrowIfCancellationRequested(); - var rawComment = targetSymbol.GetDocumentationCommentXml(expandIncludes: true, cancellationToken: cancellationToken); - if(rawComment == null) - return default; - - cancellationToken.ThrowIfCancellationRequested(); - - throw new NotImplementedException("Comment parsing is not implemented yet."); - var parsedComment = new XmlDocument(); - - cancellationToken.ThrowIfCancellationRequested(); - var typeMetadataName = targetSymbol.MetadataName; - var result = new ExtractCommentStepResult(typeMetadataName, rawComment, parsedComment); - - return result; - } -} diff --git a/DocReflect/Generators/ExtractCommentStepResult.cs b/DocReflect/Generators/ExtractCommentStepResult.cs deleted file mode 100644 index 505554d..0000000 --- a/DocReflect/Generators/ExtractCommentStepResult.cs +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DocReflect.Generators; - -using System.Collections.Generic; -using System.Xml; - -internal readonly struct ExtractCommentStepResult(String typeMetadataName, String rawComment, XmlDocument parsedComment) - : IEquatable -{ - public readonly String TypeMetadataName { get; } = typeMetadataName; - private readonly String _rawComment = rawComment; - public readonly XmlDocument ParsedComment { get; } = parsedComment; - - public override Boolean Equals(Object? obj) => obj is ExtractCommentStepResult result && Equals(result); - public Boolean Equals(ExtractCommentStepResult other) => TypeMetadataName == other.TypeMetadataName && _rawComment == other._rawComment; - - public override Int32 GetHashCode() - { - var hashCode = -15450755; - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(TypeMetadataName); - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_rawComment); - return hashCode; - } - - public static Boolean operator ==(ExtractCommentStepResult left, ExtractCommentStepResult right) => left.Equals(right); - public static Boolean operator !=(ExtractCommentStepResult left, ExtractCommentStepResult right) => !(left == right); -} diff --git a/DocReflect/Generators/FinalStepResult.cs b/DocReflect/Generators/FinalStepResult.cs deleted file mode 100644 index 76739aa..0000000 --- a/DocReflect/Generators/FinalStepResult.cs +++ /dev/null @@ -1,5 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DocReflect.Generators; - -internal readonly record struct FinalStepResult(String HintName, String SourceText); diff --git a/DocReflect/Generators/ParseStepResult.cs b/DocReflect/Generators/ParseStepResult.cs deleted file mode 100644 index 5374248..0000000 --- a/DocReflect/Generators/ParseStepResult.cs +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DocReflect.Generators; - -using System.Collections.Generic; - -internal readonly struct ParseStepResult(String typeMetadataName, String rawComment, TypeDocumentation documentation) : IEquatable -{ - public readonly String TypeMetadataName { get; } = typeMetadataName; - private readonly String _rawComment = rawComment; - - public readonly TypeDocumentation Documentation { get; } = documentation; - - public override Boolean Equals(Object? obj) => obj is ParseStepResult result && Equals(result); - public Boolean Equals(ParseStepResult other) => TypeMetadataName == other.TypeMetadataName && _rawComment == other._rawComment; - - public override Int32 GetHashCode() - { - var hashCode = 462352912; - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(TypeMetadataName); - hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_rawComment); - return hashCode; - } - - public static Boolean operator ==(ParseStepResult left, ParseStepResult right) => left.Equals(right); - public static Boolean operator !=(ParseStepResult left, ParseStepResult right) => !(left == right); -} diff --git a/DocReflect/Library/Comments/CommentContents.Internal.cs b/DocReflect/Library/Comments/CommentContents.Internal.cs deleted file mode 100644 index e05e74e..0000000 --- a/DocReflect/Library/Comments/CommentContents.Internal.cs +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DocReflect.Comments; - -using RhoMicro.CodeAnalysis.Library.Text.SourceTexts; - -using static RhoMicro.CodeAnalysis.Library.Text.SourceTexts.IndentedStringBuilder.Appendables; - -public partial record struct CommentContents : IIndentedStringBuilderAppendable -{ - void IIndentedStringBuilderAppendable.AppendTo(IndentedStringBuilder builder) => _ = builder.Operators + - "new " + typeof(CommentContents).FullName + '(' + NewLine + "\"\"\"" + NewLine + Text + NewLine + "\"\"\"" + ')'; -} diff --git a/DocReflect/Library/Comments/DocumentationComment.Internal.cs b/DocReflect/Library/Comments/DocumentationComment.Internal.cs deleted file mode 100644 index 858102f..0000000 --- a/DocReflect/Library/Comments/DocumentationComment.Internal.cs +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DocReflect.Comments; - -using RhoMicro.CodeAnalysis.Library.Text.SourceTexts; - -public partial record DocumentationComment : IIndentedStringBuilderAppendable -{ - void IIndentedStringBuilderAppendable.AppendTo(IndentedStringBuilder builder) => _ = builder.Operators + - "new " + typeof(DocumentationComment).FullName + '(' + Contents + ')'; -} diff --git a/DocReflect/Library/TypeDocumentation.Internal.cs b/DocReflect/Library/TypeDocumentation.Internal.cs deleted file mode 100644 index ae1abbb..0000000 --- a/DocReflect/Library/TypeDocumentation.Internal.cs +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DocReflect; - -using RhoMicro.CodeAnalysis.Library.Text.SourceTexts; - -using static RhoMicro.CodeAnalysis.Library.Text.SourceTexts.IndentedStringBuilder.Appendables; - -public partial class Documentation : IIndentedStringBuilderAppendable -{ - void IIndentedStringBuilderAppendable.AppendTo(IndentedStringBuilder builder) => _ = builder.Operators + - "new " + GetType().FullName + OpenBlock(Blocks.Parentheses with - { - Indentation = builder.Options.DefaultIndentation, - PlaceDelimitersOnNewLine = true - }) + AppendJoin(TopLevelComments) + CloseBlock(); -} diff --git a/DocReflect/Properties/launchSettings.json b/DocReflect/Properties/launchSettings.json deleted file mode 100644 index 456d652..0000000 --- a/DocReflect/Properties/launchSettings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "profiles": { - "Profile 1": { - "commandName": "DebugRoslynComponent", - "targetProject": "..\\TestApp\\TestApp.csproj" - } - } -} \ No newline at end of file diff --git a/DocReflect/README.md b/DocReflect/README.md deleted file mode 100644 index 1837558..0000000 --- a/DocReflect/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# DocReflect - -Generates reflection-like API to examine member documentation. \ No newline at end of file diff --git a/DslGenerator.TestApp/DslGenerator.TestApp.csproj b/DslGenerator.TestApp/DslGenerator.TestApp.csproj deleted file mode 100644 index c3ded4c..0000000 --- a/DslGenerator.TestApp/DslGenerator.TestApp.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - - net9.0 - exe - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - diff --git a/DslGenerator.TestApp/Program.cs b/DslGenerator.TestApp/Program.cs deleted file mode 100644 index 95102e6..0000000 --- a/DslGenerator.TestApp/Program.cs +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace DslGenerator.TestApp; - -internal partial class Program -{ - private static void Main(String[] _) - { - - } -} diff --git a/DslGenerator.TestApp/README.md b/DslGenerator.TestApp/README.md deleted file mode 100644 index 9fd5017..0000000 --- a/DslGenerator.TestApp/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# DslGenerator.TestApp - -Test application for the dsl generator \ No newline at end of file diff --git a/DslGenerator.TestApp/Rmbnf.rmbnf b/DslGenerator.TestApp/Rmbnf.rmbnf deleted file mode 100644 index c38f4ec..0000000 --- a/DslGenerator.TestApp/Rmbnf.rmbnf +++ /dev/null @@ -1,32 +0,0 @@ -RhoMicroBackusNaurForm; - -RuleList = [Name ";"] *RuleDefinition; -RuleDefinition = Name "=" Rule ";"; - -Rule = Binary / Unary / Primary; - -Binary = Range / Concatenation / Alternative; -Concatenation = Unary / (Rule Whitespace Rule); -Alternative = Unary / (Rule "/" Rule); -Range = Unary / (SingleAlpha "-" SingleAlpha); - -Unary = VariableRepetition / SpecificRepetition; -VariableRepetition = Primary / ("*" Rule); -SpecificRepetition = Primary / (Digit Rule); - -Primary = Grouping / OptionalGrouping / TerminalOrName; -Grouping = TerminalOrName / ("(" Rule ")"); -OptionalGrouping = TerminalOrName / ("[" Rule "]"); -TerminalOrName = Terminal / Any / Name; -Name = Alpha; -Terminal = "\"" . "\""; -Any = "."; - -Trivia = Whitespace / NewLine; -Whitespace = Space / Tab *Whitespace; -Space = " "; -Tab = " "; -NewLine = "\n" / "\r\n" / "\r"; -SingleAlpha = "a"-"z" / "A"-"Z" / "_"; -Alpha = SingleAlpha *SingleAlpha; -Digit = "0"-"9" *Digit; diff --git a/DslGenerator.Tests/DslGenerator.Tests.csproj b/DslGenerator.Tests/DslGenerator.Tests.csproj deleted file mode 100644 index f542baa..0000000 --- a/DslGenerator.Tests/DslGenerator.Tests.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - net8.0 - enable - enable - RhoMicro.CodeAnalysis.DslGenerator.Tests - false - true - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - diff --git a/DslGenerator.Tests/GlobalUsings.cs b/DslGenerator.Tests/GlobalUsings.cs deleted file mode 100644 index cb9744e..0000000 --- a/DslGenerator.Tests/GlobalUsings.cs +++ /dev/null @@ -1,4 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -global using Xunit; -global using RhoMicro.CodeAnalysis.DslGenerator; diff --git a/DslGenerator.Tests/ParserTests.cs b/DslGenerator.Tests/ParserTests.cs deleted file mode 100644 index 2f4e3d7..0000000 --- a/DslGenerator.Tests/ParserTests.cs +++ /dev/null @@ -1,271 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -#pragma warning disable CA1819 // Properties should not return arrays -namespace RhoMicro.CodeAnalysis.DslGenerator.Tests; - -using RhoMicro.CodeAnalysis.DslGenerator.Analysis; -using RhoMicro.CodeAnalysis.DslGenerator.Grammar; -using RhoMicro.CodeAnalysis.DslGenerator.Lexing; -using RhoMicro.CodeAnalysis.DslGenerator.Parsing; - -using System; -using System.Diagnostics; -using System.Linq; - -public class ParserTests -{ - public static Object[][] Data => - [ - [ - "rule=rule;", - new RuleListBuilder().New("rule", b => b.Reference("rule")), - Array.Empty() - ], - [ - """rule="a"-"z";""", - new RuleListBuilder().New("rule", b => b.Range('a', 'z')), - Array.Empty() - ], - [ - """rule= other / "a"-"z";""", - new RuleListBuilder().New("rule", b => b.Alternative(b => b.Reference("other").Range('a', 'z'))), - Array.Empty() - ], - [ - "rule = rule; ", - new RuleListBuilder().New("rule", b => b.Reference("rule")), - Array.Empty() - ], - [ - """ - - rule = # some commented stuff - rule; - """, - new RuleListBuilder().New("rule", b => b.Reference("rule")), - Array.Empty() - ], - [ - """ - rule = [optionalRule]; - """, - new RuleListBuilder().New("rule", b => b.OptionalGrouping(b => b.Reference("optionalRule"))), - Array.Empty() - ], - [ - """ - rule = (groupedRule); - """, - new RuleListBuilder().New("rule", b => b.Grouping(b => b.Reference("groupedRule"))), - Array.Empty() - ], - [ - """ - testRuleList; - rule = (groupedRule); - """, - new RuleListBuilder("testRuleList").New("rule", b => b.Grouping(b => b.Reference("groupedRule"))), - Array.Empty() - ], - [ - "rule = (\"groupedTerminal\");", - new RuleListBuilder().New("rule", b => b.Grouping(b => b.Terminal("groupedTerminal"))), - Array.Empty() - ], - [ - "rule = [\"groupedTerminal\"];", - new RuleListBuilder().New("rule", b => b.OptionalGrouping(b => b.Terminal("groupedTerminal"))), - Array.Empty() - ], - [ - """ - testname = ["is this the only legal value?"] / # here is a comment - *"no, it's not :)"; # and another comment - """, - new RuleListBuilder().New("testname", b => - b.Alternative(b => - b.OptionalGrouping(b => - b.Terminal("is this the only legal value?")) - .VariableRepetition(b => - b.Terminal("no, it's not :)")) - )), - Array.Empty() - ], - [ - """ - testname /= ["is this the only legal value?"] / # here is a comment - *"no, it's not :)"; # and another comment - """, - new RuleListBuilder().Incremental("testname", b => - b.Alternative(b => - b.OptionalGrouping(b => - b.Terminal("is this the only legal value?")) - .VariableRepetition(b => - b.Terminal("no, it's not :)")) - )), - Array.Empty() - ], - [ - - "rule=\"\";", - new RuleListBuilder().New("rule", b => - b.Terminal(String.Empty) - ), - Array.Empty() - ], - [ - "Range = SingleAlpha \"-\" SingleAlpha;", - new RuleListBuilder().New("Range", b => - b.Reference("SingleAlpha").Terminal("-").Reference("SingleAlpha")), - Array.Empty() - ], - [ - """ - TestGrammarName; - testname = ["is this the only legal value?"] / # here is a comment - *"no, it's not :)"; # and another comment - testname /= 77here (is *some more) "ruley" [goodness]; # :) - more = defs; - testname /= more; - more /= no; - """, - new RuleListBuilder("TestGrammarName") - .New("testname", b => - b.Alternative(b => - b.OptionalGrouping(b => b.Terminal("is this the only legal value?")) - .VariableRepetition(b => b.Terminal("no, it's not :)")))) - .Incremental("testname", b => - b.SpecificRepetition(77, b => b.Reference("here")) - .Grouping(b => b.Reference("is").VariableRepetition(b => b.Reference("some")).Reference("more")) - .Terminal("ruley") - .OptionalGrouping(b => b.Reference("goodness"))) - .New("more", b => b.Reference("defs")) - .Incremental("testname", b => b.Reference("more")) - .Incremental("more", b => b.Reference("no")), - Array.Empty() - ], - [ - "TerminalValue = \"\\\"\" . \"\\\"\";", - new RuleListBuilder().New("TerminalValue", b => - b.Terminal("\\\"").Any().Terminal("\\\"")), - Array.Empty() - ], - [ - """ - RhoMicroBackusNaurForm; - - RuleList = [Name ";"] *RuleDefinition; - RuleDefinition = Name "=" Rule ";"; - - Rule = Binary / Unary / Primary; - - Binary = Unary / Range / Concatenation / Alternative; - Concatenation = Rule Whitespace Rule; - Alternative = Rule "/" Rule; - Range = SingleAlpha "-" SingleAlpha; - - Unary = Primary / VariableRepetition / SpecificRepetition; - VariableRepetition = "*" Rule; - SpecificRepetition = Digit Rule; - - Primary = Grouping / OptionalGrouping / TerminalOrNameOrAny; - Grouping = "(" Rule ")"; - OptionalGrouping = "[" Rule "]"; - TerminalOrNameOrAny = Terminal / Name / Any ; - Terminal = "\"" . "\""; - Name = Alpha; - Any = "."; - - Trivia = Whitespace / NewLine; - Whitespace = Space / Tab *Whitespace; - Space = " "; - Tab = " "; - NewLine = "\n" / "\r\n" / "\r"; - SingleAlpha = "a"-"z" / "A"-"Z" / "_"; - Alpha = SingleAlpha *SingleAlpha; - Digit = "0"-"9" *Digit; - - """, - new RuleListBuilder("RhoMicroBackusNaurForm") - - .New("RuleList", b => b.OptionalGrouping(b => b.Reference("Name").Terminal(";")).VariableRepetition("RuleDefinition")) - .New("RuleDefinition", b => b.Reference("Name").Terminal("=").Reference("Rule").Terminal(";")) - - .New("Rule", b => b.Alternative("Binary", "Unary", "Primary")) - - .New("Binary", b => b.Alternative("Unary", "Range", "Concatenation", "Alternative")) - .New("Concatenation", b => b.Concatenation("Rule", "Whitespace", "Rule")) - .New("Alternative", b => b.Reference("Rule").Terminal("/").Reference("Rule")) - .New("Range", b => b.Reference("SingleAlpha").Terminal("-").Reference("SingleAlpha")) - - .New("Unary", b => b.Alternative("Primary", "VariableRepetition", "SpecificRepetition")) - .New("VariableRepetition", b => b.Terminal("*").Reference("Rule")) - .New("SpecificRepetition", b => b.Concatenation("Digit", "Rule")) - - .New("Primary", b => b.Alternative("Grouping", "OptionalGrouping", "TerminalOrNameOrAny")) - .New("Grouping", b => b.Terminal("(").Reference("Rule").Terminal(")")) - .New("OptionalGrouping", b => b.Terminal("[").Reference("Rule").Terminal("]")) - .New("TerminalOrNameOrAny", b => b.Alternative("Terminal", "Name", "Any")) - .New("Terminal", b => b.Terminal("\\\"").Any().Terminal("\\\"")) - .New("Name", b => b.Reference("Alpha")) - .New("Any", b => b.Terminal(".")) - - .New("Trivia", b => b.Alternative("Whitespace", "NewLine")) - .New("Whitespace", b => b.Alternative("Space", "Tab").VariableRepetition("Whitespace")) - .New("Space", b => b.Terminal(" ")) - .New("Tab", b => b.Terminal("\t")) - .New("NewLine", b => b.Alternative(b => b.Terminal("\\n").Terminal("\\r\\n").Terminal("\\r"))) - .New("SingleAlpha", b => b.Alternative(b => b.Range('a', 'z').Range('A', 'Z').Terminal("_"))) - .New("Alpha", b => b.Reference("SingleAlpha").VariableRepetition("SingleAlpha")) - .New("Digit", b => b.Range('0', '9').VariableRepetition("Digit")), - - Array.Empty() - ] - ]; - - [Theory] - [MemberData(nameof(Data))] - public void ParsesCorrectly(String source, Object expectedWeak, Int32[] expectedDiagnosticIds) - { - //Arrange -#pragma warning disable CA1062 // Validate arguments of public methods - var expectedRuleList = ((RuleListBuilder)expectedWeak).Build(); -#pragma warning restore CA1062 // Validate arguments of public methods - var tokenizeResult = new Tokenizer().Tokenize(source, default, String.Empty); - var parser = new Parser(); - - //Act - var (actualRuleList, actualDiagnostics) = parser.Parse(tokenizeResult, default); - - //Assert - try - { - Assert.Equal(expectedRuleList, actualRuleList); - VerifyDiagnostics(expectedDiagnosticIds, actualDiagnostics); - } catch - { - var expected = expectedRuleList.ToDisplayString(default).Split('\n'); - var actual = actualRuleList.ToDisplayString(default).Split('\n'); - var comparisonStringParts = expected - .Select((e, i) => (e, i)) - .Where((t) => actual.ElementAtOrDefault(t.i) != t.e) - .Select((t, i) => $"{t.e} # EXPECTED\n{actual.ElementAtOrDefault(t.i) ?? "# MISSING"} # ACTUAL"); - var comparisonString = String.Join("\n\n", comparisonStringParts); - - Debugger.Break(); - _ = parser.Parse(tokenizeResult, default); - throw; - } - } - private static void VerifyDiagnostics(Int32[] expectedDiagnosticIds, DiagnosticsCollection actualDiagnostics) - { - var actualDiagnosticIdCounts = actualDiagnostics.GroupBy(d => d.Descriptor.Id) - .Select(g => (id: g.Key, count: g.Count())) - .ToDictionary(t => t.id, t => t.count); - foreach(var (id, expectedCount) in expectedDiagnosticIds.GroupBy(id => id).Select(g => (id: g.Key, count: g.Count()))) - { - var actualCount = actualDiagnosticIdCounts.TryGetValue(id, out var c) ? c : -1; - Assert.Equal(expectedCount, actualCount); - } - } -} diff --git a/DslGenerator.Tests/TokenizerTests.cs b/DslGenerator.Tests/TokenizerTests.cs deleted file mode 100644 index 6796bf6..0000000 --- a/DslGenerator.Tests/TokenizerTests.cs +++ /dev/null @@ -1,124 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -#pragma warning disable CA1861 // Avoid constant arrays as arguments -namespace DslGenerator.Tests; - -using RhoMicro.CodeAnalysis.DslGenerator.Analysis; -using RhoMicro.CodeAnalysis.DslGenerator.Lexing; - -using System.Collections.Immutable; -using System.Diagnostics; - -public class TokenizerTests -{ -#pragma warning disable RS1035 // Do not use APIs banned for analyzers - public static Object[][] TokenizerData => [ - ["name=name", new[] { "Name:name", "Equal:=", "Name:name" }, Array.Empty()], - ["name=\"\";", new[] { "Name:name", "Equal:=", "Terminal:", "Semicolon:;" }, Array.Empty()], - ["name=\"a\"-\"z\";", new[] { "Name:name", "Equal:=", "Terminal:a", "Dash:-", "Terminal:z", "Semicolon:;" }, Array.Empty()], - ["name", new[] { "Name:name" }, Array.Empty()], - ["=", new[] { "Equal:=" }, Array.Empty()], - [".", new[] { "Period:." }, Array.Empty()], - ["TerminalValue = \"\\\"\" . \"\\\"\";", - new[] { "Name:TerminalValue", "Whitespace: ", "Equal:=", "Whitespace: ", "Terminal:\\\"", "Whitespace: ", "Period:.", "Whitespace: ", "Terminal:\\\"", "Semicolon:;" }, - Array.Empty()], - ["-", new[] { "Dash:-" }, Array.Empty()], - ["\"a\"-\"z\"", new[] { "Terminal:a", "Dash:-", "Terminal:z" }, Array.Empty()], - ["\"ab\"-\"z\"", new[] { "Terminal:ab", "Dash:-", "Terminal:z" }, Array.Empty()], - [" ", new[] { "Whitespace: " }, Array.Empty()], - [" ", new[] { "Whitespace: " }, Array.Empty()], - ["\t", new[] { "Whitespace:\t" }, Array.Empty()], - ["\t\t\t", new[] { "Whitespace:\t\t\t" }, Array.Empty()], - ["\n", new[] { "NewLine:\n" }, Array.Empty()], - ["\n\n\n", new[] { "NewLine:\n", "NewLine:\n", "NewLine:\n" }, Array.Empty()], - ["\r", new[] { "NewLine:\r" }, Array.Empty()], - ["\r\r\r", new[] { "NewLine:\r", "NewLine:\r", "NewLine:\r" }, Array.Empty()], - ["\r\n", new[] { "NewLine:\r\n" }, Array.Empty()], - ["\r\n\r\n\r\n", new[] { "NewLine:\r\n", "NewLine:\r\n", "NewLine:\r\n" }, Array.Empty()], - ["\r\n\n\r\n", new[] { "NewLine:\r\n", "NewLine:\n", "NewLine:\r\n" }, Array.Empty()], - [";", new[] { "Semicolon:;" }, Array.Empty()], - ["\"", Array.Empty(), Array.Empty()], - ["/", new[] { "Slash:/" }, Array.Empty()], - ["#", Array.Empty(), Array.Empty()], - ["# this is a comment; 24224 ����", new[] { "Comment: this is a comment; 24224 ����" }, Array.Empty()], - ["/=", new[] { "SlashEqual:/=" }, Array.Empty()], - ["(", new[] { "ParenLeft:(" }, Array.Empty()], - [")", new[] { "ParenRight:)" }, Array.Empty()], - ["[", new[] { "BracketLeft:[" }, Array.Empty()], - ["]", new[] { "BracketRight:]" }, Array.Empty()], - ["32rule", new[] { "Number:32", "Name:rule" }, Array.Empty()], - ["&33+", new[] { "Unknown:&", "Number:33", "Unknown:+" }, Array.Empty()], - ["%%%", new[] { "Unknown:%%%" }, new[] { 1 }], - ["\"TerminalValue\"", new[] { "Terminal:TerminalValue" }, Array.Empty()], - ["\"TerminalValue", new[] { "Terminal:TerminalValue" }, new[] { 2 }], - ["\"TerminalValue;rule=somename;", new[] { "Terminal:TerminalValue;rule=somename;" }, new[] { 2 }], - ["\"TerminalValue;ru\\\"le=somename;\"", new[] { "Terminal:TerminalValue;ru\\\"le=somename;" }, Array.Empty()], - [ """ - testname = ["is this the 1 legal value?"] / # here is a comment - *"no, it's not :)"; # and another comment - - """, - new[] { "Name:testname", "Whitespace: ", "Equal:=", "Whitespace: ", - "BracketLeft:[", "Terminal:is this the 1 legal value?", "BracketRight:]", - "Whitespace: ","Slash:/","Whitespace: ", "Comment: here is a comment", $"NewLine:\n", - "Whitespace: ","Star:*","Terminal:no, it's not :)" , - "Semicolon:;", "Whitespace: ", "Comment: and another comment", $"NewLine:\n", - }, - Array.Empty()], - ["a=b;a/=c;", new[] { "Name:a", "Equal:=", "Name:b", "Semicolon:;", "Name:a", "SlashEqual:/=", "Name:c", "Semicolon:;" }, Array.Empty()] - ]; -#pragma warning restore RS1035 // Do not use APIs banned for analyzers - - [Theory(Timeout = 5000)] - [MemberData(nameof(TokenizerData))] - public async Task TokenizesCorrectly(String source, String[] rawTokens, Int32[] expectedDiagnosticIds) - { - //Arrange - var expectedTokens = rawTokens.Select(t => t.Split(':', 2)) - .Select(t => (Type: Enum.Parse(t[0]), Lexeme: (Lexeme)String.Concat(t[1..]))) - .Select(t => new Token(t.Type, t.Lexeme)) - .Append(new Token(TokenType.Eof, Lexeme.Empty)) - .ToImmutableArray(); - var tokenizer = new Tokenizer(); - - //Act - var (actualTokens, actualDiagnostics) = tokenizer.Tokenize(source, default, String.Empty); - - //Assert - try - { - VerifyTokens(expectedTokens, actualTokens); - VerifyDiagnostics(expectedDiagnosticIds, actualDiagnostics); - } catch - { - Debugger.Break(); - _ = Tokenizer.Instance.Tokenize(source, default); - throw; - } - } - - private static void VerifyDiagnostics(Int32[] expectedDiagnosticIds, DiagnosticsCollection actualDiagnostics) - { - var actualDiagnosticIdCounts = actualDiagnostics.GroupBy(d => d.Descriptor.Id) - .Select(g => (id: g.Key, count: g.Count())) - .ToDictionary(t => t.id, t => t.count); - foreach(var (id, expectedCount) in expectedDiagnosticIds.GroupBy(id => id).Select(g => (id: g.Key, count: g.Count()))) - { - var actualCount = actualDiagnosticIdCounts.TryGetValue(id, out var c) ? c : -1; - Assert.Equal(expectedCount, actualCount); - } - } - - private static void VerifyTokens(IReadOnlyList expectedTokens, IReadOnlyList actualTokens) - { - for(var i = 0; i < expectedTokens.Count; i++) - { - var expectedToken = expectedTokens[i]; - if(actualTokens.Count <= i) - Assert.Fail($"Not enough tokens produced; missing {expectedToken}"); - - var actualToken = actualTokens[i]; - Assert.Equal(expectedToken, actualToken); - } - } -} diff --git a/DslGenerator/Analysis/Diagnostic.cs b/DslGenerator/Analysis/Diagnostic.cs deleted file mode 100644 index 69662ce..0000000 --- a/DslGenerator/Analysis/Diagnostic.cs +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Analysis; -using System; - -using RhoMicro.CodeAnalysis.DslGenerator.Lexing; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -sealed record Diagnostic(DiagnosticDescriptor Descriptor, Location Location, IReadOnlyCollection MessageArgs) -{ - public static Diagnostic Create(DiagnosticDescriptor descriptor, Location location, params Object[] messageArgs) => - new(descriptor, location, messageArgs); -#if DSL_GENERATOR - public Microsoft.CodeAnalysis.Diagnostic ToMsDiagnostic() => - Microsoft.CodeAnalysis.Diagnostic.Create( - Descriptor.ToMsDescriptor(), - Location.ToMsLocation(), - MessageArgs.ToArray()); -#endif -} diff --git a/DslGenerator/Analysis/DiagnosticDescriptor.cs b/DslGenerator/Analysis/DiagnosticDescriptor.cs deleted file mode 100644 index 576d07a..0000000 --- a/DslGenerator/Analysis/DiagnosticDescriptor.cs +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Analysis; -using System; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -readonly record struct DiagnosticDescriptor(Int32 Id, String Title, String Message) -{ -#if DSL_GENERATOR - private const String _diagnosticsCategory = "RhoMicro.CodeAnalysis.DslGenerator"; - public Microsoft.CodeAnalysis.DiagnosticDescriptor ToMsDescriptor() => - new( - $"DSLG{Id:0000}", - Title, - Message, - _diagnosticsCategory, - Microsoft.CodeAnalysis.DiagnosticSeverity.Error, - isEnabledByDefault: true); -#endif -} diff --git a/DslGenerator/Analysis/DiagnosticDescriptors.cs b/DslGenerator/Analysis/DiagnosticDescriptors.cs deleted file mode 100644 index dc653e9..0000000 --- a/DslGenerator/Analysis/DiagnosticDescriptors.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Analysis; -using System; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -static class DiagnosticDescriptors -{ - private static DiagnosticDescriptor Create( - Int32 id, - String title, - String message) => - new(id, title, message); - - public static DiagnosticDescriptor UnexpectedCharacter { get; } = - Create(1, "Unexpected Character", "Encountered unexpected character."); - public static DiagnosticDescriptor UnterminatedTerminal { get; } = - Create(2, "Unterminated Terminal", "Encountered an unterminated terminal (missing closing quote)."); - public static DiagnosticDescriptor UnexpectedToken { get; } = - Create(3, "Unexpected Token", "Encountered an unexpected '{0}' token. {1}"); - public static DiagnosticDescriptor InvalidRangeToken { get; } = - Create(4, "Invalid Range", "Encountered a range token. Ranges must be surrounded by terminals of one character."); -} diff --git a/DslGenerator/Analysis/DiagnosticsCollection.cs b/DslGenerator/Analysis/DiagnosticsCollection.cs deleted file mode 100644 index af2caec..0000000 --- a/DslGenerator/Analysis/DiagnosticsCollection.cs +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Analysis; - -using RhoMicro.CodeAnalysis.DslGenerator.Lexing; - -using System; -using System.Collections; -using System.Collections.Generic; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -sealed class DiagnosticsCollection : IReadOnlyList -{ - private readonly List _diagnostics = []; - -#if DSL_GENERATOR - public void ReportToContext(Microsoft.CodeAnalysis.SourceProductionContext context) - { - foreach(var diagnostic in _diagnostics) - { - var msDiagnostic = diagnostic.ToMsDiagnostic(); - context.ReportDiagnostic(msDiagnostic); - } - } -#endif - public void Add(DiagnosticDescriptor descriptor, Location location, params Object[] messageArgs) => - _diagnostics.Add(new Diagnostic(descriptor, location, messageArgs)); - public void Add(DiagnosticsCollection diagnostics) => _diagnostics.AddRange(diagnostics); - - public Diagnostic this[Int32 index] => ((IReadOnlyList)_diagnostics)[index]; - - public Int32 Count => ((IReadOnlyCollection)_diagnostics).Count; - - public IEnumerator GetEnumerator() => ((IEnumerable)_diagnostics).GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_diagnostics).GetEnumerator(); -} diff --git a/DslGenerator/DslGenerator.csproj b/DslGenerator/DslGenerator.csproj deleted file mode 100644 index b109d5c..0000000 --- a/DslGenerator/DslGenerator.csproj +++ /dev/null @@ -1,56 +0,0 @@ - - - - - netstandard2.0 - false - true - true - true - enable - enable - - - - - - - - true - true - Generates utilities for lexing and parsing domain specific languages, little languages etc. - Source Generator - - - - $(DefineConstants);DSL_GENERATOR - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - diff --git a/DslGenerator/Generators/Attributes/GeneratedGrammarAttribute.cs b/DslGenerator/Generators/Attributes/GeneratedGrammarAttribute.cs deleted file mode 100644 index c6fd5b3..0000000 --- a/DslGenerator/Generators/Attributes/GeneratedGrammarAttribute.cs +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis; - -using System; - -#if DSL_GENERATOR -[GenerateFactory] -#endif -[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] -internal sealed partial class GeneratedGrammarAttribute : Attribute; diff --git a/DslGenerator/Generators/Attributes/GeneratedRuleListAttribute.cs b/DslGenerator/Generators/Attributes/GeneratedRuleListAttribute.cs deleted file mode 100644 index 74661dc..0000000 --- a/DslGenerator/Generators/Attributes/GeneratedRuleListAttribute.cs +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis; - -using System; - -#if DSL_GENERATOR -[GenerateFactory] -#endif -[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] -internal sealed partial class GeneratedRuleListAttribute(String source) : Attribute -{ - public String Source { get; } = source; -} diff --git a/DslGenerator/Generators/DslGenerator.cs b/DslGenerator/Generators/DslGenerator.cs deleted file mode 100644 index 1571198..0000000 --- a/DslGenerator/Generators/DslGenerator.cs +++ /dev/null @@ -1,102 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Generators; - -using Microsoft.CodeAnalysis; -using RhoMicro.CodeAnalysis.Generated; -using RhoMicro.CodeAnalysis.Library; -using RhoMicro.CodeAnalysis.DslGenerator.Lexing; -using RhoMicro.CodeAnalysis.DslGenerator.Parsing; -using RhoMicro.CodeAnalysis.DslGenerator.Grammar; -using RhoMicro.CodeAnalysis.DslGenerator.Analysis; -using System.Collections.Immutable; -using RhoMicro.CodeAnalysis.Library.Text; - -/// -/// Generates utilities for domain specific languages. -/// -[Generator(LanguageNames.CSharp)] -public sealed class DslGenerator : IIncrementalGenerator -{ - private const String _grammarFileExtension = ".rmbnf"; - private static readonly IEqualityComparer<(String source, DiagnosticsCollection diagnostics)> _sourceDiagnosticsTupleComparer = - new EqualityComparerStrategy<(String source, DiagnosticsCollection diagnostics)>( - (x, y) => x.source == y.source, - obj => obj.source.GetHashCode()); - private static readonly IEqualityComparer> _sourceDiagnosticsTupleArrayComparer = - new ImmutableArrayCollectionEqualityComparer<(String source, DiagnosticsCollection diagnostics)>(_sourceDiagnosticsTupleComparer); - - /// - public void Initialize(IncrementalGeneratorInitializationContext context) - { - var provider = context.AdditionalTextsProvider - .Where(text => Path.GetExtension(text.Path) == _grammarFileExtension) - .Select((text, ct) => (tokenizeResult: new Tokenizer().Tokenize(text.GetText(ct)?.ToString() ?? String.Empty, ct, text.Path), fileName: Path.GetFileNameWithoutExtension(text.Path))) - .Select((t, ct) => (parseResult: new Parser().Parse(t.tokenizeResult, ct), t.fileName)) - .Select((t, ct) => (t.parseResult, isNamed: t.parseResult.RuleList is NamedRuleList, t.fileName)) - .Select((t, ct) => ( - t.parseResult, - name: t.isNamed ? ((NamedRuleList)t.parseResult.RuleList).Name.ToDisplayString(ct) : t.fileName, - type: t.isNamed ? nameof(NamedRuleList) : nameof(RuleList))) - .Where(t => Microsoft.CodeAnalysis.CSharp.SyntaxFacts.IsValidIdentifier(t.name)) - .Select((t, ct) => - { - ct.ThrowIfCancellationRequested(); - - var (parseResult, name, type) = t; - var (ruleList, diagnostics) = parseResult; - var source = new IndentedStringBuilder() - .Indent() - .Comment.OpenSummary() - .Comment.OpenParagraph() - .Append("Matches the following grammar:") - .CloseBlock() // - .AppendCommentParagraphs(ruleList, ct) - .CloseBlock() // - .Append("public static ").Append(type).Append(' ').Append(name).Append(' ').AppendLine("{ get; } = ") - .OpenIndentBlock() - .AppendMetaString(ruleList, ct).AppendLine(";") - .CloseBlock() - .ToString(); - - return (source, diagnostics); - }) - .WithComparer(_sourceDiagnosticsTupleComparer) - .Collect() - .WithComparer(_sourceDiagnosticsTupleArrayComparer) - .Select((sourceTuples, ct) => - { - var diagnosticsAggregator = new DiagnosticsCollection(); - var sourceBuilder = new IndentedStringBuilder( - IndentedStringBuilderOptions.GeneratedFile with - { - GeneratorName = "RhoMicro.CodeAnalysis.DslGenerator" - }) - .AppendLine("namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar.Generated;") - .Append("static class GeneratedRuleLists") - .OpenBracesBlock(); - - for(var i = 0; i < sourceTuples.Length; i++) - { - var (source, diagnostics) = sourceTuples[i]; - _ = sourceBuilder.AppendLine(source); - diagnosticsAggregator.Add(diagnostics); - } - - var ruleListsSource = sourceBuilder.CloseAllBlocks().ToString(); - - return (source: ruleListsSource, diagnostics: diagnosticsAggregator); - }) - .WithComparer(new EqualityComparerStrategy<(String source, DiagnosticsCollection diagnostics)>( - (x, y) => x.source == y.source, - obj => obj.source.GetHashCode())); - - context.RegisterSourceOutput(provider, (ctx, data) => - { - var (source, diagnostics) = data; - ctx.AddSource("GeneratedRuleLists.g.cs", source); - diagnostics.ReportToContext(ctx); - }); - IncludedFileSources.RegisterToContext(context); - } -} diff --git a/DslGenerator/Generators/GeneratedRuleListGenerator.cs b/DslGenerator/Generators/GeneratedRuleListGenerator.cs deleted file mode 100644 index 6c4f357..0000000 --- a/DslGenerator/Generators/GeneratedRuleListGenerator.cs +++ /dev/null @@ -1,271 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Generators; - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -using RhoMicro.CodeAnalysis.DslGenerator.Lexing; -using RhoMicro.CodeAnalysis.DslGenerator.Parsing; - -using System.Collections.Immutable; -using System.Text; - -using InitialStepResult = (String unparsedList, String implTypeName, String partialTypeSourceText); -using TokenizeStepResult = (Lexing.TokenizeResult tokenizeResult, String implTypeName, String partialTypeSourceText); -using ParseStepResult = (Parsing.ParseResult parseResult, String implTypeName, String partialTypeSourceText); -using ImplTypeStepResult = (String implTypeSourceText, String partialTypeSourceText, RhoMicro.CodeAnalysis.DslGenerator.Analysis.DiagnosticsCollection diagnostics); -using FinalStepResult = (String source, RhoMicro.CodeAnalysis.DslGenerator.Analysis.DiagnosticsCollection diagnostics); -using RhoMicro.CodeAnalysis.DslGenerator.Analysis; - -[Generator(LanguageNames.CSharp)] -internal class GeneratedRuleListGenerator : IIncrementalGenerator -{ - public void Initialize(IncrementalGeneratorInitializationContext context) - { - var provider = context.SyntaxProvider.ForGeneratedRuleListAttribute( - IsGeneratorTarget, - InitialStep) - .Where(t => t != default) - .Select(TokenizeStep) - .Select(ParseStep) - .Select(ImplTypeStep) - .WithComparer(_implTypeStepResultComparer) - .Collect() - .WithComparer(_implTypeStepResultArrayComparer) - .Select(FinalStep) - .WithComparer(_finalStepResultComparer); - - context.RegisterSourceOutput(provider, (ctx, result) => - { - var (source, diagnostics) = result; - ctx.AddSource("GeneratedRuleLists.g.cs", source); - diagnostics.ReportToContext(ctx); //TODO: map to attribute span - }); - context.RegisterPostInitializationOutput(ctx => - ctx.AddSource($"{nameof(GeneratedRuleListAttribute)}.g.cs", GeneratedRuleListAttribute.SourceText)); - } - - private static readonly EqualityComparerStrategy _implTypeStepResultComparer = new( - (x, y) => x.implTypeSourceText == y.implTypeSourceText && - x.partialTypeSourceText == y.partialTypeSourceText, - obj => (obj.implTypeSourceText, obj.partialTypeSourceText).GetHashCode()); - private static readonly ImmutableArrayCollectionEqualityComparer _implTypeStepResultArrayComparer = - new(_implTypeStepResultComparer); - private static readonly EqualityComparerStrategy _finalStepResultComparer = new( - (x, y) => x.source == y.source, - obj => obj.source.GetHashCode()); - private static readonly SymbolDisplayFormat _typeSignatureNameFormat = - SymbolDisplayFormat.MinimallyQualifiedFormat.WithGenericsOptions(SymbolDisplayGenericsOptions.IncludeTypeParameters); - private static readonly SymbolDisplayFormat _namespaceFormat = - SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted); - - private static Boolean IsGeneratorTarget(Microsoft.CodeAnalysis.SyntaxNode node, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - if(node is not MethodDeclarationSyntax mds || mds.ParameterList.Parameters.Count != 0) - return false; - - cancellationToken.ThrowIfCancellationRequested(); - - var isStatic = false; - var isPartial = false; - for(var i = 0; i < mds.Modifiers.Count; i++) - { - cancellationToken.ThrowIfCancellationRequested(); - - if(mds.Modifiers[i].IsKind(SyntaxKind.StaticKeyword)) - { - isStatic = true; - if(isPartial) - break; - } else if(mds.Modifiers[i].IsKind(SyntaxKind.PartialKeyword)) - { - isPartial = true; - if(isStatic) - break; - } - } - - if(!(isStatic || isPartial)) - return false; - - cancellationToken.ThrowIfCancellationRequested(); - - var result = mds.Body == null; - - return result; - } - - private static InitialStepResult InitialStep(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var target = (IMethodSymbol)context.TargetSymbol; - - if(target.ReturnType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) != - "global::RhoMicro.CodeAnalysis.DslGenerator.Grammar.RuleList") - { - return default; - } - - if(context.Attributes[0].ConstructorArguments[0].Value is not String unparsedList) - return default; - - var implTypeName = GetImplTypeName(cancellationToken); - var partialTypeSourceText = GetPartialTypeSourceText(target, implTypeName, cancellationToken); - - var result = (unparsedList, implTypeName, partialTypeSourceText); - - return result; - } - - private static TokenizeStepResult TokenizeStep(InitialStepResult previous, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var tokenizeResult = Tokenizer.Instance.Tokenize(previous.unparsedList, cancellationToken); - - return (tokenizeResult, previous.implTypeName, previous.partialTypeSourceText); - } - - private static ParseStepResult ParseStep(TokenizeStepResult previous, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var parseResult = Parser.Instance.Parse(previous.tokenizeResult, cancellationToken); - - return (parseResult, previous.implTypeName, previous.partialTypeSourceText); - } - - private static ImplTypeStepResult ImplTypeStep(ParseStepResult previous, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var implTypeSourceText = new IndentedStringBuilder() - .Append("file static class ").AppendLine(previous.implTypeName) - .OpenBracesBlock() - .AppendLine("public static global::RhoMicro.CodeAnalysis.DslGenerator.Grammar.RuleList Instance { get; } =") - .OpenIndentBlock() - .AppendMetaString(previous.parseResult.RuleList, cancellationToken).AppendLine(';') - .CloseAllBlocks() - .ToString(); - - return (implTypeSourceText, previous.partialTypeSourceText, previous.parseResult.Diagnostics); - } - - private static FinalStepResult FinalStep(ImmutableArray results, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var resultBuilder = new StringBuilder(); - var diagnosticsAccumulator = new DiagnosticsCollection(); - _ = resultBuilder.AppendLine("// ") - .Append("// This file was last generated by the RhoMicro.CodeAnalysis.DslGenerator on ").AppendLine(DateTimeOffset.Now.ToString()) - .AppendLine("// ") - .AppendLine("#pragma warning disable"); - - for(var i = 0; i < results.Length; i++) - { - var (implTypeSourceText, partialTypeSourceText, diagnostics) = results[i]; - _ = resultBuilder.AppendLine(implTypeSourceText).AppendLine(partialTypeSourceText); - diagnosticsAccumulator.Add(diagnostics); - } - - var sourceText = resultBuilder.ToString(); - - return (sourceText, diagnosticsAccumulator); - } - - private static String GetPartialTypeSourceText(IMethodSymbol target, String implTypeName, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var builder = new StringBuilder(); - var containingType = target.ContainingType; - - AppendHead(containingType, builder, cancellationToken); - AppendMethodImplementation(target, implTypeName, builder, cancellationToken); - AppendTail(builder, containingType, cancellationToken); - - //result - cancellationToken.ThrowIfCancellationRequested(); - - var result = builder.ToString(); - return result; - } - - private static void AppendMethodImplementation(IMethodSymbol target, String implTypeName, StringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = builder.Append(" ") - .Append(SyntaxFacts.GetText(target.DeclaredAccessibility)) - .Append(" static partial global::RhoMicro.CodeAnalysis.DslGenerator.Grammar.RuleList ") - .Append(target.Name) - .Append("() => ") - .Append(implTypeName) - .AppendLine(".Instance;"); - } - - private static void AppendTail(StringBuilder builder, INamedTypeSymbol containingType, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var parent = containingType; - while(parent != null) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = builder.AppendLine("}"); - parent = parent.ContainingType; - } - - if(!containingType.ContainingNamespace.IsGlobalNamespace) - { - _ = builder.AppendLine("}"); - } - } - - private static void AppendHead(INamedTypeSymbol parent, StringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - if(parent.ContainingType != null) - { - AppendHead(parent.ContainingType, builder, cancellationToken); - } else if(!parent.ContainingNamespace.IsGlobalNamespace) - { - _ = builder - .Append("namespace ") - .AppendLine(parent.ContainingNamespace.ToDisplayString(_namespaceFormat)) - .AppendLine("{"); - } - - var typeModifier = parent.IsRecord ? - parent.IsReferenceType ? - "record" : - "record struct " : - parent.IsReferenceType ? - "class " : - "struct "; - - _ = builder - .Append("partial ") - .Append(typeModifier) - .AppendLine(parent.ToDisplayString(_typeSignatureNameFormat)) - .AppendLine("{"); - } - - private static String GetImplTypeName(CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var id = Guid.NewGuid().ToString().Replace('-', '_'); - var result = $"GeneratedRueList_{id}"; - - return result; - } -} diff --git a/DslGenerator/Grammar/DisplayStringVisitor.cs b/DslGenerator/Grammar/DisplayStringVisitor.cs deleted file mode 100644 index 2b74ebf..0000000 --- a/DslGenerator/Grammar/DisplayStringVisitor.cs +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; -using System; - -internal sealed class DisplayStringVisitor(IndentedStringBuilder builder) : SyntaxNodeVisitor -{ - public DisplayStringVisitor() : this(new()) { } - private readonly IndentedStringBuilder _builder = builder; - public override void Visit(Name name) => _builder.Append(name.Value); - public override void Visit(NamedRuleList namedRuleList) => throw new NotImplementedException(); - public override void Visit(Rule.Alternative alternative) => throw new NotImplementedException(); - public override void Visit(Rule.Any any) => throw new NotImplementedException(); - public override void Visit(Rule.Concatenation concatenation) => throw new NotImplementedException(); - public override void Visit(Rule.Grouping grouping) => throw new NotImplementedException(); - public override void Visit(Rule.OptionalGrouping optionalGrouping) => throw new NotImplementedException(); - public override void Visit(Rule.Range range) => throw new NotImplementedException(); - public override void Visit(Rule.Reference reference) => throw new NotImplementedException(); - public override void Visit(Rule.SpecificRepetition specificRepetition) => throw new NotImplementedException(); - public override void Visit(Rule.Terminal terminal) => throw new NotImplementedException(); - public override void Visit(Rule.VariableRepetition variableRepetition) => throw new NotImplementedException(); - public override void Visit(RuleDefinition.New @new) => throw new NotImplementedException(); - public override void Visit(RuleDefinition.Incremental incremental) => throw new NotImplementedException(); - public override void Visit(RuleList ruleList) => throw new NotImplementedException(); -} diff --git a/DslGenerator/Grammar/IndentedStringBuilder.cs b/DslGenerator/Grammar/IndentedStringBuilder.cs deleted file mode 100644 index d38f9e7..0000000 --- a/DslGenerator/Grammar/IndentedStringBuilder.cs +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.Library.Text; - -using RhoMicro.CodeAnalysis.DslGenerator.Grammar; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -partial class IndentedStringBuilder -{ - public IndentedStringBuilder AppendCommentParagraphs(RuleList ruleList, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - ruleList.AppendCommentParagraphsTo(this, cancellationToken); - - return this; - } - public IndentedStringBuilder AppendDisplayString(SyntaxNode node, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - node.AppendDisplayStringTo(this, cancellationToken); - - return this; - } - public IndentedStringBuilder AppendMetaString(SyntaxNode node, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - node.AppendMetaStringTo(this, cancellationToken); - - return this; - } -} diff --git a/DslGenerator/Grammar/ListExtensions.cs b/DslGenerator/Grammar/ListExtensions.cs deleted file mode 100644 index 856efa6..0000000 --- a/DslGenerator/Grammar/ListExtensions.cs +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; - -using System.Linq; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -static class ListExtensions -{ - public static IReadOnlyList Unify(this IReadOnlyList definitions) => - definitions.Aggregate(new Dictionary(), (defs, def) => - { - var (name, rule) = def; - //previous definition? - if(!defs.TryGetValue(name, out var previousRule)) - { - //new definition - defs[name] = rule; - return defs; - } - - //redefinition? - if(def is RuleDefinition.New) - { - //TODO: make illegal? - defs[name] = rule; - return defs; - } - - //incremental? - if(def is RuleDefinition.Incremental) - { - //make sure left is grouped to maintain semantics - // rule /= a / b / c - // rule = (rule) or a or b or c - var groupedPreviousRule = previousRule is Rule.Grouping or Rule.OptionalGrouping ? - previousRule : - new Rule.Grouping(previousRule); - - var alternative = new Rule.Alternative(groupedPreviousRule, rule); - defs[name] = alternative; - return defs; - } - - throw new InvalidOperationException($"Encountered unknown rule definition type: {def.GetType()}"); - }).Select(kvp => new RuleDefinition.New(kvp.Key, kvp.Value)).ToList(); -} diff --git a/DslGenerator/Grammar/Name.cs b/DslGenerator/Grammar/Name.cs deleted file mode 100644 index 488f21f..0000000 --- a/DslGenerator/Grammar/Name.cs +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; - -using RhoMicro.CodeAnalysis.DslGenerator.Lexing; - -using System.Diagnostics; - -#if DSL_GENERATOR -[IncludeFile] -#endif -[DebuggerDisplay("{ToDisplayString()}")] -internal sealed partial record Name : SyntaxNode -{ - public Name(Token token) : this(token.Lexeme) { } - public Name(Lexeme lexeme) : this(lexeme.ToString() ?? String.Empty) { } -#pragma warning disable IDE1006 // Naming Styles - public Name(String Value) -#pragma warning restore IDE1006 // Naming Styles - { - for(var i = 0; i < Value.Length; i++) - { - if(!Utils.IsValidNameChar(Value[i])) - { - throw new ArgumentException($"Invalid character at index {i}: '{Value[i]}'. Rule names must contain letters only.)", nameof(Value)); - } - } - - this.Value = Value; - } - - public override String ToString() => base.ToString(); - public String Value { get; } - public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = builder.Append(Value); - } - - protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = AppendCtorArg(builder, nameof(Value), Value, quoteValue: true, cancellationToken); - } - public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); -} diff --git a/DslGenerator/Grammar/NamedRuleList.cs b/DslGenerator/Grammar/NamedRuleList.cs deleted file mode 100644 index 3fa4be2..0000000 --- a/DslGenerator/Grammar/NamedRuleList.cs +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; - -using System; -using System.Collections.Generic; -using System.Diagnostics; - -#if DSL_GENERATOR -[IncludeFile] -#endif -[DebuggerDisplay("{ToDisplayString()}")] -internal record NamedRuleList(Name Name, IReadOnlyList Definitions) : RuleList(Definitions) -{ - public override String ToString() => base.ToString(); - public virtual Boolean Equals(NamedRuleList other) => Name.Equals(other.Name) && base.Equals(other); - public override Int32 GetHashCode() => base.GetHashCode() * -1521134295 + Name.GetHashCode(); - public override void AppendCommentParagraphsTo(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = builder.Comment.OpenParagraph() - .Append("Name: ") - .Comment.OpenCode() - .AppendDisplayString(Name, cancellationToken) - .CloseBlock() - .CloseBlock(); - - base.AppendCommentParagraphsTo(builder, cancellationToken); - } - public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) => - base.AppendDisplayStringTo(builder.AppendDisplayString(Name, cancellationToken).AppendLine(";"), cancellationToken); - protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) => - AppendCtorArg(AppendCtorArg(builder, nameof(Name), Name, cancellationToken).Append(", "), nameof(Definitions), Definitions, cancellationToken); - public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); -} diff --git a/DslGenerator/Grammar/Rule.Alternative.cs b/DslGenerator/Grammar/Rule.Alternative.cs deleted file mode 100644 index 6008463..0000000 --- a/DslGenerator/Grammar/Rule.Alternative.cs +++ /dev/null @@ -1,34 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; - -using System.Diagnostics; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -abstract partial record Rule -{ - [DebuggerDisplay("{ToDisplayString()}")] - public sealed record Alternative(Rule Left, Rule Right) : Rule - { - public override String ToString() => base.ToString(); - public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = builder.AppendDisplayString(Left, cancellationToken).Append(" / ").AppendDisplayString(Right, cancellationToken); - } - - protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = AppendCtorArg( - AppendCtorArg(builder, nameof(Left), Left, cancellationToken) - .Append(", "), nameof(Right), Right, cancellationToken); - } - public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); - } -} diff --git a/DslGenerator/Grammar/Rule.Any.cs b/DslGenerator/Grammar/Rule.Any.cs deleted file mode 100644 index b055c34..0000000 --- a/DslGenerator/Grammar/Rule.Any.cs +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; - -using System.Diagnostics; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -abstract partial record Rule -{ - [DebuggerDisplay("{ToDisplayString()}")] - public sealed partial record Any : Rule - { - public static Any Instance { get; } = new(); - public override String ToString() => base.ToString(); - public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = builder.Append('.'); - } - - protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) => - cancellationToken.ThrowIfCancellationRequested(); - public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); - } -} diff --git a/DslGenerator/Grammar/Rule.Concatenation.cs b/DslGenerator/Grammar/Rule.Concatenation.cs deleted file mode 100644 index 03859ef..0000000 --- a/DslGenerator/Grammar/Rule.Concatenation.cs +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; - -using System.Diagnostics; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -abstract partial record Rule -{ - [DebuggerDisplay("{ToDisplayString()}")] - public sealed record Concatenation(Rule Left, Rule Right) : Rule - { - public override String ToString() => base.ToString(); - public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = builder.AppendDisplayString(Left, cancellationToken).Append(' ').AppendDisplayString(Right, cancellationToken); - } - - protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = AppendCtorArg(AppendCtorArg(builder, nameof(Left), Left, cancellationToken).Append(", "), nameof(Right), Right, cancellationToken); - } - public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); - } -} diff --git a/DslGenerator/Grammar/Rule.Grouping.cs b/DslGenerator/Grammar/Rule.Grouping.cs deleted file mode 100644 index 97fb6d4..0000000 --- a/DslGenerator/Grammar/Rule.Grouping.cs +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; - -using System.Diagnostics; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -abstract partial record Rule -{ - [DebuggerDisplay("{ToDisplayString()}")] - public sealed record Grouping(Rule Rule) : Rule - { - public override String ToString() => base.ToString(); - public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = builder.Append('(').AppendDisplayString(Rule, cancellationToken).Append(')'); - } - - protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = AppendCtorArg(builder, nameof(Rule), Rule, cancellationToken); - } - public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); - } -} diff --git a/DslGenerator/Grammar/Rule.OptionalGrouping.cs b/DslGenerator/Grammar/Rule.OptionalGrouping.cs deleted file mode 100644 index 5a34206..0000000 --- a/DslGenerator/Grammar/Rule.OptionalGrouping.cs +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; - -using System.Diagnostics; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -abstract partial record Rule -{ - [DebuggerDisplay("{ToDisplayString()}")] - public sealed record OptionalGrouping(Rule Rule) : Rule - { - public override String ToString() => base.ToString(); - public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = builder.Append('[').AppendDisplayString(Rule, cancellationToken).Append(']'); - } - protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = AppendCtorArg(builder, nameof(Rule), Rule, cancellationToken); - } - public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); - } -} diff --git a/DslGenerator/Grammar/Rule.Range.cs b/DslGenerator/Grammar/Rule.Range.cs deleted file mode 100644 index 193a03d..0000000 --- a/DslGenerator/Grammar/Rule.Range.cs +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; - -using System.Diagnostics; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -abstract partial record Rule -{ - [DebuggerDisplay("{ToDisplayString()}")] - public sealed partial record Range(Terminal Start, Terminal End) : Rule - { - public override String ToString() => base.ToString(); - public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = builder.AppendDisplayString(Start, cancellationToken).Append('-').AppendDisplayString(End, cancellationToken); - } - protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = AppendCtorArg(AppendCtorArg(builder, nameof(Start), Start, cancellationToken).Append(", "), nameof(End), End, cancellationToken); - } - public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); - } -} diff --git a/DslGenerator/Grammar/Rule.Reference.cs b/DslGenerator/Grammar/Rule.Reference.cs deleted file mode 100644 index be169b7..0000000 --- a/DslGenerator/Grammar/Rule.Reference.cs +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; - -using System.Diagnostics; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -abstract partial record Rule -{ - [DebuggerDisplay("{ToDisplayString()}")] - public sealed partial record Reference(Name Name) : Rule - { - public override String ToString() => base.ToString(); - public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = builder.AppendDisplayString(Name, cancellationToken); - } - - protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = AppendCtorArg(builder, nameof(Name), Name, cancellationToken); - } - public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); - } -} diff --git a/DslGenerator/Grammar/Rule.SpecificRepetition.cs b/DslGenerator/Grammar/Rule.SpecificRepetition.cs deleted file mode 100644 index 7d1673d..0000000 --- a/DslGenerator/Grammar/Rule.SpecificRepetition.cs +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; - -using System.Diagnostics; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -abstract partial record Rule -{ - [DebuggerDisplay("{ToDisplayString()}")] - public sealed record SpecificRepetition(Int32 Count, Rule Rule) : Rule - { - public override String ToString() => base.ToString(); - public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = builder.Append(Count.ToString()).AppendDisplayString(Rule, cancellationToken); - } - protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = AppendCtorArg( - AppendCtorArg(builder, nameof(Count), Count.ToString(), quoteValue: false, cancellationToken) - .Append(", "), nameof(Rule), Rule, cancellationToken); - } - public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); - } -} diff --git a/DslGenerator/Grammar/Rule.Terminal.cs b/DslGenerator/Grammar/Rule.Terminal.cs deleted file mode 100644 index e2f9bb7..0000000 --- a/DslGenerator/Grammar/Rule.Terminal.cs +++ /dev/null @@ -1,41 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; - -using RhoMicro.CodeAnalysis.DslGenerator.Lexing; - -using System.Diagnostics; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -abstract partial record Rule -{ - [DebuggerDisplay("{ToDisplayString()}")] - public sealed partial record Terminal(String Value) : Rule - { - public override String ToString() => base.ToString(); - public Terminal(Token token) - : this(token.Type == TokenType.Terminal ? - token.Lexeme.ToString() ?? String.Empty : - throw new ArgumentOutOfRangeException(nameof(token), token.Type, "Token must be of terminal type.")) - { } - public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = builder.Append('"').Append(Value).Append('"'); - } - - protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = AppendCtorArg(builder, nameof(Value), Value, quoteValue: true, cancellationToken); - } - public Boolean Equals(Terminal other) => other is not null && Value == other.Value; - public override Int32 GetHashCode() => Value.GetHashCode(); - public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); - } -} diff --git a/DslGenerator/Grammar/Rule.VariableRepetition.cs b/DslGenerator/Grammar/Rule.VariableRepetition.cs deleted file mode 100644 index 673c609..0000000 --- a/DslGenerator/Grammar/Rule.VariableRepetition.cs +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; - -using System.Diagnostics; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -abstract partial record Rule -{ - [DebuggerDisplay("{ToDisplayString()}")] - public sealed record VariableRepetition(Rule Rule) : Rule - { - public override String ToString() => base.ToString(); - public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = builder.Append('*').AppendDisplayString(Rule, cancellationToken); - } - protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = AppendCtorArg(builder, nameof(Rule), Rule, cancellationToken); - } - public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); - } -} diff --git a/DslGenerator/Grammar/Rule.cs b/DslGenerator/Grammar/Rule.cs deleted file mode 100644 index 26f260e..0000000 --- a/DslGenerator/Grammar/Rule.cs +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; - -using System.Diagnostics; - -#if DSL_GENERATOR -[IncludeFile] -#endif -[DebuggerDisplay("{ToDisplayString()}")] -internal abstract partial record Rule : SyntaxNode -{ - public override String ToString() => base.ToString(); -} diff --git a/DslGenerator/Grammar/RuleBuilder.cs b/DslGenerator/Grammar/RuleBuilder.cs deleted file mode 100644 index 431022f..0000000 --- a/DslGenerator/Grammar/RuleBuilder.cs +++ /dev/null @@ -1,144 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; - -using System; -using System.Collections.Generic; -using System.Threading; - -using static Rule; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -readonly struct RuleBuilder -{ - public RuleBuilder() - : this(isAlternative: false) - { } - private RuleBuilder(Boolean isAlternative) => _isAlternative = isAlternative; - private readonly List _rules = []; - private readonly Boolean _isAlternative; - - public override String ToString() => _rules.Count == 0 ? GetType().ToString() : Build().ToString(); - public RuleBuilder Reference(String value) - { - _rules.Add(new Reference(new(value))); - return this; - } - public RuleBuilder Terminal(String value) - { - _rules.Add(new Terminal(value)); - return this; - } - public RuleBuilder Any() - { - _rules.Add(Rule.Any.Instance); - return this; - } - public RuleBuilder Range(Char start, Char end) - { - _rules.Add(new Rule.Range(new(start.ToString()), new(end.ToString()))); - return this; - } - public RuleBuilder Concatenation(params String[] referenceNames) => - Concatenation(b => _ = referenceNames.Aggregate(b, (b, n) => b.Reference(n))); - public RuleBuilder Concatenation(Action buildRule) => AppendBinary(buildAlternative: false, buildRule); - public RuleBuilder Alternative(params String[] referenceNames) => - Alternative(b => _ = referenceNames.Aggregate(b, (b, n) => b.Reference(n))); - public RuleBuilder Alternative(Action buildRule) => AppendBinary(buildAlternative: true, buildRule); - private RuleBuilder AppendBinary(Boolean buildAlternative, Action buildRule) - { - if(buildAlternative == _isAlternative) - { - buildRule.Invoke(this); - return this; - } - - var builder = new RuleBuilder(buildAlternative); - buildRule.Invoke(builder); - if(builder._rules.Count == 0) - { - return this; - } - - var rule = - builder._rules.Count > 1 ? - builder._rules.Aggregate((left, right) => new Alternative(left, right)) : - builder.Build(); - _rules.Add(rule); - - return this; - } - public RuleBuilder VariableRepetition(String referenceName) - { - var rule = new Reference(new(referenceName)); - var repeatedRule = new VariableRepetition(rule); - _rules.Add(repeatedRule); - - return this; - } - public RuleBuilder VariableRepetition(Action buildRule) - { - var rule = GetRule(buildRule); - var repeatedRule = new VariableRepetition(rule); - _rules.Add(repeatedRule); - - return this; - } - public RuleBuilder SpecificRepetition(Int32 count, String referenceName) - { - var rule = new Reference(new(referenceName)); - var repeatedRule = new SpecificRepetition(count, rule); - _rules.Add(repeatedRule); - - return this; - } - public RuleBuilder SpecificRepetition(Int32 count, Action buildRule) - { - var rule = GetRule(buildRule); - var repetition = new SpecificRepetition(count, rule); - _rules.Add(repetition); - - return this; - } - public RuleBuilder Grouping(Action buildRule) - { - var rule = GetRule(buildRule); - var grouping = new Grouping(rule); - _rules.Add(grouping); - - return this; - } - public RuleBuilder OptionalGrouping(String referenceName) - { - var rule = new Reference(new(referenceName)); - var grouping = new OptionalGrouping(rule); - _rules.Add(grouping); - - return this; - } - public RuleBuilder OptionalGrouping(Action buildRule) - { - var rule = GetRule(buildRule); - var grouping = new OptionalGrouping(rule); - _rules.Add(grouping); - - return this; - } - - private static Rule GetRule(Action buildRule) - { - var builder = new RuleBuilder(); - buildRule.Invoke(builder); - var rule = builder.Build(); - return rule; - } - - public Rule Build() => _rules.Count == 1 ? - _rules[0] : - _rules.Count > 1 ? - _rules.Aggregate((left, right) => new Concatenation(left, right)) : - throw new InvalidOperationException("Unable to build rule as no builder instruction has been called."); -} diff --git a/DslGenerator/Grammar/RuleDefinition.Incremental.cs b/DslGenerator/Grammar/RuleDefinition.Incremental.cs deleted file mode 100644 index 8c014fb..0000000 --- a/DslGenerator/Grammar/RuleDefinition.Incremental.cs +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; - -using System.Diagnostics; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -abstract partial record RuleDefinition -{ - [DebuggerDisplay("{ToDisplayString()}")] - public sealed record Incremental : RuleDefinition - { -#pragma warning disable IDE1006 // Naming Styles - public Incremental(Name Name, Rule Rule) : base(Name, Rule) { } -#pragma warning restore IDE1006 // Naming Styles - public override String ToString() => base.ToString(); - public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = builder.AppendDisplayString(Name, cancellationToken).Append(" /= ").AppendDisplayString(Rule, cancellationToken).Append(';'); - } - public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); - } -} diff --git a/DslGenerator/Grammar/RuleDefinition.New.cs b/DslGenerator/Grammar/RuleDefinition.New.cs deleted file mode 100644 index 8e7d863..0000000 --- a/DslGenerator/Grammar/RuleDefinition.New.cs +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; - -using System.Diagnostics; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -abstract partial record RuleDefinition -{ - [DebuggerDisplay("{ToDisplayString()}")] - public sealed record New : RuleDefinition - { -#pragma warning disable IDE1006 // Naming Styles - public New(Name Name, Rule Rule) : base(Name, Rule) { } -#pragma warning restore IDE1006 // Naming Styles - public override String ToString() => base.ToString(); - public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = builder.AppendDisplayString(Name, cancellationToken).Append(" = ") - .AppendDisplayString(Rule, cancellationToken).Append(';'); - } - public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); - } -} diff --git a/DslGenerator/Grammar/RuleDefinition.cs b/DslGenerator/Grammar/RuleDefinition.cs deleted file mode 100644 index d9e6843..0000000 --- a/DslGenerator/Grammar/RuleDefinition.cs +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; - -using System.Diagnostics; - -#if DSL_GENERATOR -[IncludeFile] -#endif -[DebuggerDisplay("{ToDisplayString()}")] -internal abstract partial record RuleDefinition(Name Name, Rule Rule) : SyntaxNode -{ - public override String ToString() => base.ToString(); - protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = AppendCtorArg(AppendCtorArg(builder, nameof(Name), Name, cancellationToken).Append(", "), nameof(Rule), Rule, cancellationToken); - } -} diff --git a/DslGenerator/Grammar/RuleList.cs b/DslGenerator/Grammar/RuleList.cs deleted file mode 100644 index e7af8e1..0000000 --- a/DslGenerator/Grammar/RuleList.cs +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; - -using System.Diagnostics; -using System.Linq; - -#if DSL_GENERATOR -[IncludeFile] -#endif -[DebuggerDisplay("{ToDisplayString()}")] -internal record RuleList(IReadOnlyList Definitions) : SyntaxNode -{ - public override String ToString() => base.ToString(); - public virtual Boolean Equals(RuleList other) => - other.Definitions.SequenceEqual(Definitions); - public override Int32 GetHashCode() => - Definitions.Aggregate(997021164, (hc, def) => hc * -1521134295 + def.GetHashCode()); - public virtual void AppendCommentParagraphsTo(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = Definitions.Aggregate(builder, (b, d) => - b.Comment.OpenParagraph() - .Comment.OpenCode() - .AppendDisplayString(d, cancellationToken) - .CloseBlock() - .CloseBlock()); - } - public override void Receive(SyntaxNodeVisitor visitor) => - visitor.Visit(this); - public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) => - _ = Definitions.Aggregate(builder, (sb, r) => sb.AppendDisplayString(r, cancellationToken).Append('\n')); - protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) => - AppendCtorArg(builder, nameof(Definitions), Definitions, cancellationToken); -} diff --git a/DslGenerator/Grammar/RuleListBuilder.cs b/DslGenerator/Grammar/RuleListBuilder.cs deleted file mode 100644 index c4c932b..0000000 --- a/DslGenerator/Grammar/RuleListBuilder.cs +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; - -using System; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -readonly struct RuleListBuilder -{ - public RuleListBuilder(String nameValue) => - _name = new(nameValue); - public RuleListBuilder() => - _name = null; - - private readonly List _definitions = []; - private readonly Name? _name; - - public override String ToString() => Build().ToString(); - public RuleListBuilder New(String nameValue, Action buildRule) - { - var rule = GetRule(buildRule); - var ruleDefinition = new RuleDefinition.New(new(nameValue), rule); - _definitions.Add(ruleDefinition); - - return this; - } - public RuleListBuilder Incremental(String nameValue, Action buildRule) - { - var rule = GetRule(buildRule); - var ruleDefinition = new RuleDefinition.Incremental(new(nameValue), rule); - _definitions.Add(ruleDefinition); - - return this; - } - private static Rule GetRule(Action buildRule) - { - var ruleBuilder = new RuleBuilder(); - buildRule.Invoke(ruleBuilder); - var rule = ruleBuilder.Build(); - - return rule; - } - public RuleList Build() => - _name != null ? - new NamedRuleList(_name, _definitions) : - new RuleList(_definitions); -} diff --git a/DslGenerator/Grammar/SyntaxNode.cs b/DslGenerator/Grammar/SyntaxNode.cs deleted file mode 100644 index a65735a..0000000 --- a/DslGenerator/Grammar/SyntaxNode.cs +++ /dev/null @@ -1,117 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; - -using RhoMicro.CodeAnalysis.Library.Text; - -using System.Text.RegularExpressions; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -abstract record SyntaxNode -{ - private static readonly Regex _quotePattern = new("(\"*)", RegexOptions.Compiled); - protected static IndentedStringBuilder AppendCtorArg( - IndentedStringBuilder builder, - String argName, - String argValue, - Boolean quoteValue, - CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = builder.Append(argName).Append(": "); - - var rawQuotes = quoteValue ? - String.Concat(Enumerable.Repeat('"', _quotePattern.Matches(argValue) - .Cast() - .Select(m => m.Length) - .Append(2) - .Max() + 1)) : - null; - - if(quoteValue) - { - _ = builder.Append('\n').Append(rawQuotes).Append('\n'); - } - - _ = builder.Append(argValue); - - if(quoteValue) - { - _ = builder.Append('\n').Append(rawQuotes).Append('\n'); - } - - return builder; - } - protected static IndentedStringBuilder AppendCtorArg( - IndentedStringBuilder builder, - String argName, - SyntaxNode argValue, - CancellationToken cancellationToken) => - builder.Append(argName).Append(": ").AppendMetaString(argValue, cancellationToken); - protected static IndentedStringBuilder AppendCtorArg( - IndentedStringBuilder builder, - String argName, - IEnumerable argValue, - CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - _ = builder.Append(argName).Append(": ["); - var enumerator = argValue.GetEnumerator(); - if(enumerator.MoveNext()) - { - var first = enumerator.Current; - _ = builder.AppendMetaString(first, cancellationToken); - while(enumerator.MoveNext()) - { - cancellationToken.ThrowIfCancellationRequested(); - - var next = enumerator.Current; - _ = builder.Append(", ").AppendMetaString(next, cancellationToken); - } - } else - { - _ = builder.Append(' '); - } - - _ = builder.Append(']'); - - return builder; - } - - public String ToDisplayString(CancellationToken cancellationToken) => - new IndentedStringBuilder().AppendDisplayString(this, cancellationToken).ToString(); - public String ToMetaString(CancellationToken cancellationToken) => - new IndentedStringBuilder().AppendMetaString(this, cancellationToken).ToString(); - public void AppendMetaStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var type = GetType(); - _ = builder.Append("new ").Append(type.Namespace); - append(type); - _ = builder.Append('('); - AppendCtorArgs(builder, cancellationToken); - _ = builder.Append(')'); - - void append(Type type) - { - cancellationToken.ThrowIfCancellationRequested(); - - if(type.DeclaringType != null) - { - append(type.DeclaringType); - } - - _ = builder.Append('.').Append(type.Name); - } - } - protected abstract void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken); - public abstract void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken); - public override String ToString() => ToDisplayString(default); - public abstract void Receive(SyntaxNodeVisitor visitor); -} diff --git a/DslGenerator/Grammar/SyntaxNodeVisitor.cs b/DslGenerator/Grammar/SyntaxNodeVisitor.cs deleted file mode 100644 index 706b99c..0000000 --- a/DslGenerator/Grammar/SyntaxNodeVisitor.cs +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; - -internal abstract class SyntaxNodeVisitor -{ - public abstract void Visit(Name name); - public abstract void Visit(NamedRuleList namedRuleList); - public abstract void Visit(Rule.Alternative alternative); - public abstract void Visit(Rule.Any any); - public abstract void Visit(Rule.Concatenation concatenation); - public abstract void Visit(Rule.Grouping grouping); - public abstract void Visit(Rule.OptionalGrouping optionalGrouping); - public abstract void Visit(Rule.Range range); - public abstract void Visit(Rule.Reference reference); - public abstract void Visit(Rule.SpecificRepetition specificRepetition); - public abstract void Visit(Rule.Terminal terminal); - public abstract void Visit(Rule.VariableRepetition variableRepetition); - public abstract void Visit(RuleDefinition.New @new); - public abstract void Visit(RuleDefinition.Incremental incremental); - public abstract void Visit(RuleList ruleList); -} diff --git a/DslGenerator/Grammar/Utils.cs b/DslGenerator/Grammar/Utils.cs deleted file mode 100644 index 7bfde28..0000000 --- a/DslGenerator/Grammar/Utils.cs +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -static class Utils -{ - [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] - public static Boolean IsValidNameChar(Char c) => - c is >= 'a' and <= 'z' or - >= 'A' and <= 'Z' or - '_'; -} diff --git a/DslGenerator/Lexing/Lexeme.cs b/DslGenerator/Lexing/Lexeme.cs deleted file mode 100644 index dede6dc..0000000 --- a/DslGenerator/Lexing/Lexeme.cs +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Lexing; - -using System.Text.RegularExpressions; - -#if DSL_GENERATOR -[IncludeFile] -#endif -[UnionType] -[UnionTypeSettings( - ToStringSetting = ToStringSetting.Simple, - Miscellaneous = MiscellaneousSettings.Default | MiscellaneousSettings.EmitGeneratedSourceCode)] -internal readonly partial struct Lexeme : IEquatable, IEquatable, IEquatable -{ - public Int32 Length => Match( - s => s.Length, - s => 1, - s => s.Length); - public static Lexeme Empty { get; } = String.Empty; - public Boolean Equals(Lexeme other) => - Match(other.Equals, other.Equals, other.Equals); - public override Int32 GetHashCode() => - Match(v => v.GetHashCode(), v => v.GetHashCode(), v => v.GetHashCode()); - public Boolean Equals(Char c) => - Match( - s => s.Length == 1 && s[0] == c, - thisChar => thisChar == c, - s => s.Equals(c)); - public Boolean Equals(String s) => - Match( - thisString => thisString == s, - c => s.Length == 1 && s[0] == c, - slice => slice.Equals(s)); - public Boolean Equals(StringSlice s) => - Match(s.Equals, s.Equals, s.Equals); - public String ToEscapedString() => - ToString()? - .Replace("\n", "\\n") - .Replace("\r", "\\r") - .Replace("\t", "\\t") - ?? String.Empty; -} diff --git a/DslGenerator/Lexing/Lexemes.cs b/DslGenerator/Lexing/Lexemes.cs deleted file mode 100644 index 7da2e26..0000000 --- a/DslGenerator/Lexing/Lexemes.cs +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Lexing; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -static class Lexemes -{ - public const Char Equal = '='; - public const Char Dash = '-'; - public const Char Period = '.'; - public const Char Alternative = '/'; - public const String IncrementalAlternative = "=/"; - public const Char GroupOpen = '('; - public const Char GroupClose = ')'; - public const Char VariableRepetition = '*'; - public const Char OptionalSequenceOpen = '['; - public const Char OptionalSequenceClose = ']'; - public const Char Semicolon = ';'; - public const Char Quote = '"'; - public const Char NewLine = '\n'; - public const Char Space = ' '; - public const Char Tab = '\t'; - public const Char CarriageReturn = '\r'; - public const Char Escape = '\\'; - public const Char Hash = '#'; - public const String Eof = ""; -} diff --git a/DslGenerator/Lexing/Location.cs b/DslGenerator/Lexing/Location.cs deleted file mode 100644 index cfcfd86..0000000 --- a/DslGenerator/Lexing/Location.cs +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Lexing; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -sealed record Location( - TextSpan TextSpan, - LinePositionSpan LinePositionSpan -#if DSL_GENERATOR - , String FilePath -#endif - ) -{ - public static Location None { get; } = new(default, default -#if DSL_GENERATOR - , String.Empty -#endif - ); - public static Location Create(Int32 line, Int32 character, Int32 position -#if DSL_GENERATOR - , String filePath -#endif - ) => - new(new(position, 0), new(new(line, character), new(line, character)) //TODO: implement location spans -#if DSL_GENERATOR - , filePath -#endif - ); -#if DSL_GENERATOR - public Microsoft.CodeAnalysis.Location ToMsLocation() => - Equals(None) ? - Microsoft.CodeAnalysis.Location.None : - Microsoft.CodeAnalysis.Location.Create( - FilePath, - TextSpan.ToMsTextSpan(), - LinePositionSpan.ToMsLinePositionSpan()); -#endif -} - -internal readonly record struct LinePosition(Int32 Line, Int32 Character) -{ -#if DSL_GENERATOR - public Microsoft.CodeAnalysis.Text.LinePosition ToMsLinePosition() => - new(Line, Character); -#endif -} - -internal readonly record struct LinePositionSpan(LinePosition Start, LinePosition End) -{ -#if DSL_GENERATOR - public Microsoft.CodeAnalysis.Text.LinePositionSpan ToMsLinePositionSpan() => - new(Start.ToMsLinePosition(), End.ToMsLinePosition()); -#endif -} - -internal readonly record struct TextSpan(Int32 Position, Int32 Length) -{ -#if DSL_GENERATOR - public Microsoft.CodeAnalysis.Text.TextSpan ToMsTextSpan() => - new(Position, Length); -#endif -} diff --git a/DslGenerator/Lexing/SourceText.cs b/DslGenerator/Lexing/SourceText.cs deleted file mode 100644 index 66090bc..0000000 --- a/DslGenerator/Lexing/SourceText.cs +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Lexing; - -using System; -using System.Text; - -#if DSL_GENERATOR -[IncludeFile] -#endif -[UnionType] -internal readonly partial struct SourceText : IDisposable -{ - public String ToString(CancellationToken cancellationToken) => - Match( - s => s, - s => - { - cancellationToken.ThrowIfCancellationRequested(); - var reader = new StreamReader(s); - var resultBuilder = new StringBuilder(); - var line = reader.ReadLine(); - while(line != null) - { - cancellationToken.ThrowIfCancellationRequested(); - _ = resultBuilder.AppendLine(line); - line = reader.ReadLine(); - } - - var result = resultBuilder.ToString(); - - return result; - }); - public static SourceText Empty { get; } = String.Empty; - public void Dispose() - { - if(TryAsStream(out var s)) - s.Dispose(); - } -} diff --git a/DslGenerator/Lexing/StringSlice.cs b/DslGenerator/Lexing/StringSlice.cs deleted file mode 100644 index 308dd55..0000000 --- a/DslGenerator/Lexing/StringSlice.cs +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Lexing; - -using System.Diagnostics; - -#if DSL_GENERATOR -[IncludeFile] -#endif -[DebuggerDisplay("{DebuggerDisplayString()}")] -internal readonly record struct StringSlice(String Source, Int32 Start, Int32 Length) : IEquatable, IEquatable -{ - public override String ToString() => Source.Substring(Start, Length); - private String DebuggerDisplayString() => ToString().Replace("\n", "\\n").Replace("\r", "\\r"); - public Boolean Equals(String other) - { - if(Length != other.Length) - return false; - for(var i = 0; i < Length; i++) - { - if(Source[i + Start] != other[i]) - return false; - } - - return true; - } - public Boolean Equals(Char other) => Length == 1 && Source[Start] == other; - public Boolean Equals(StringSlice other) - { - if(Length != other.Length) - return false; - for(var i = 0; i < Length; i++) - { - if(Source[i + Start] != other.Source[i + other.Start]) - return false; - } - - return true; - } - public override Int32 GetHashCode() - { - var result = EqualityComparer.Default.GetHashCode(Length); - for(var i = Start; i < Start + Length; i++) - { - result = result * -1521134295 + EqualityComparer.Default.GetHashCode(Source[i]); - } - - return result; - } -} diff --git a/DslGenerator/Lexing/Token.cs b/DslGenerator/Lexing/Token.cs deleted file mode 100644 index e9d532a..0000000 --- a/DslGenerator/Lexing/Token.cs +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Lexing; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -readonly record struct Token(TokenType Type, Lexeme Lexeme, Location Location) -{ - public Token(TokenType type, Lexeme lexeme) : this(type, lexeme, Location.None) { } - public override String ToString() => $"[{Type}:{Lexeme.ToEscapedString()}]"; - public static Token CreateUnknown(Lexeme lexeme) => new(TokenType.Unknown, lexeme); - public static Token CreateName(Lexeme lexeme) => new(TokenType.Name, lexeme); - public static Token CreateWhitespace(Lexeme lexeme) => new(TokenType.Whitespace, lexeme); - public static Token CreateSpecificRepetition(Lexeme lexeme) => new(TokenType.Number, lexeme); - public static Token CreateComment(Lexeme lexeme) => new(TokenType.Comment, lexeme); - public static Token CreateTerminal(Lexeme lexeme) => new(TokenType.Terminal, lexeme); - public Boolean Equals(Token other) => Type == other.Type && Lexeme == other.Lexeme; - public override Int32 GetHashCode() => (Type, Lexeme).GetHashCode(); -} - -internal readonly record struct Token(T Type, Lexeme Lexeme, Location Location) -{ - public Token(T type, Lexeme lexeme) : this(type, lexeme, Location.None) { } - public override String ToString() => $"[{Type}:{Lexeme.ToEscapedString()}]"; - public Boolean Equals(Token other) => (Type, Lexeme).Equals((other.Type, other.Lexeme)); - public override Int32 GetHashCode() => (Type, Lexeme).GetHashCode(); -} diff --git a/DslGenerator/Lexing/TokenType.cs b/DslGenerator/Lexing/TokenType.cs deleted file mode 100644 index a1a0c52..0000000 --- a/DslGenerator/Lexing/TokenType.cs +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Lexing; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -enum TokenType -{ - Unknown, - Name, - Equal, - Whitespace, - Slash, - SlashEqual, - ParenLeft, - ParenRight, - Star, - Number, - BracketLeft, - BracketRight, - Semicolon, - Comment, - NewLine, - Terminal, - Dash, - Period, - Eof -} diff --git a/DslGenerator/Lexing/TokenizeResult.cs b/DslGenerator/Lexing/TokenizeResult.cs deleted file mode 100644 index 0d97200..0000000 --- a/DslGenerator/Lexing/TokenizeResult.cs +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Lexing; - -using RhoMicro.CodeAnalysis.DslGenerator.Analysis; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -readonly record struct TokenizeResult(IReadOnlyList Tokens, DiagnosticsCollection Diagnostics) -{ - public Boolean Equals(TokenizeResult other) => Tokens.SequenceEqual(other.Tokens); - public override Int32 GetHashCode() => Tokens.Aggregate(997021164, (hc, t) => hc * -1521134295 + t.GetHashCode()); -} diff --git a/DslGenerator/Lexing/Tokenizer.cs b/DslGenerator/Lexing/Tokenizer.cs deleted file mode 100644 index f319888..0000000 --- a/DslGenerator/Lexing/Tokenizer.cs +++ /dev/null @@ -1,302 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Lexing; - -using static RhoMicro.CodeAnalysis.DslGenerator.Analysis.DiagnosticDescriptors; -using RhoMicro.CodeAnalysis.DslGenerator.Grammar; -using RhoMicro.CodeAnalysis.DslGenerator.Analysis; - -using static Lexemes; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -partial class Tokenizer -{ - [UnionType] - private readonly partial struct TokenOrType; - public static Tokenizer Instance { get; } = new(); - - public TokenizeResult Tokenize(SourceText sourceText, CancellationToken cancellationToken -#if DSL_GENERATOR - , String filePath = "" -#endif - ) - { - cancellationToken.ThrowIfCancellationRequested(); - - var tokens = new List(); - var diagnostics = new DiagnosticsCollection(); - var source = sourceText.ToString(cancellationToken); - var isUnknown = false; - var (start, current, line, character) = (0, 0, 0, 0); - - while(!isAtEnd()) - { - cancellationToken.ThrowIfCancellationRequested(); - scanToken(); - } - - addToken(Tokens.Eof); - - return new(tokens, diagnostics); - - void scanToken() - { - var c = advance(); - switch(c) - { - case Equal: - addToken(TokenType.Equal); - break; - case Dash: - addToken(TokenType.Dash); - break; - case Period: - addToken(TokenType.Period); - break; - case Alternative: - //check for incremental alternative "/=" - var type = match(Equal) ? - TokenType.SlashEqual : - TokenType.Slash; - addToken(type); - break; - case GroupOpen: - addToken(TokenType.ParenLeft); - break; - case GroupClose: - addToken(TokenType.ParenRight); - break; - case VariableRepetition: - addToken(TokenType.Star); - break; - case OptionalSequenceOpen: - addToken(TokenType.BracketLeft); - break; - case OptionalSequenceClose: - addToken(TokenType.BracketRight); - break; - case Semicolon: - addToken(TokenType.Semicolon); - break; - case Hash: - comment(); - break; - case Quote: - terminal(); - break; - case NewLine: - addNewLine(); - break; - case CarriageReturn: - closeUnknown(); - if(lookAhead() == NewLine) - advancePure(); - addNewLine(); - break; - case Space: - consumeWhitespace(Space); - break; - case Tab: - consumeWhitespace(Tab); - break; - default: - if(isAlpha(c)) - { - name(); - } else if(isDigit(c)) - { - specificRepetition(); - } else - { - openUnknown(); - } - - break; - } - - void addNewLine() - { - closeUnknown(); - line++; - character = 0; - addToken(TokenType.NewLine); - } - } - - Boolean isAtEnd(Int32 lookaheadOffset = 0) => current + lookaheadOffset >= source!.Length; - - Char advance() - { - advancePure(); - return source![current - 1]; - } - - void specificRepetition() - { - closeUnknown(); - - while(isDigit(lookAhead())) - advancePure(); - addToken(TokenType.Number); - } - - void advancePure() - { - character++; - current++; - } - - void regressPure() - { - character--; - current--; - } - - void openUnknown() => isUnknown = true; - - void closeUnknown() - { - if(isUnknown) - { - if(!isAtEnd()) - regressPure(); - isUnknown = false; - addToken(TokenType.Unknown); - diagnostics!.Add(UnexpectedCharacter, getLocation()); - if(!isAtEnd()) - advancePure(); - } - } - - void resetLexemeStart() => start = current; - - void addToken(TokenOrType tokenOrType) - { - closeUnknown(); - - var token = tokenOrType.Match( - token => token, - type => new Token(type, getLexeme(), getLocation())); - tokens!.Add(token); - resetLexemeStart(); - } - - void discardToken() - { - closeUnknown(); - resetLexemeStart(); - } - - Boolean isDigit(Char? c) => c is not null and >= '0' and <= '9'; - - Boolean isAlpha(Char? c) => c is not null && Utils.IsValidNameChar(c.Value); - - Lexeme getLexeme() => new StringSlice(source!, start, current - start); - - Char? lookAhead(Int32 lookAheadOffset = 0) => current + lookAheadOffset >= source!.Length ? null : source![current + lookAheadOffset]; - - Char? lookBehind(Int32 lookBehindOffset = 0) => current - lookBehindOffset < 1 ? null : source![current - 1 - lookBehindOffset]; - - Location getLocation() => Location.Create( - line, - character, - current -#if DSL_GENERATOR - , filePath -#endif - ); - - Boolean match(Char expected) - { - if(isAtEnd() || source![current] != expected) - return false; - current++; - return true; - } - - Boolean isAtNewLine() => - lookAhead() switch - { - NewLine => true, - CarriageReturn => true, - _ => false - }; - - void comment() - { - discardToken(); //discard hash token - advancePure(); //consume hash - - if(isAtNewLine() || isAtEnd()) - return; - - while(!isAtNewLine() && !isAtEnd(lookaheadOffset: 1)) - advancePure(); - - if(!isAtNewLine()) - advancePure(); //consume last comment char - - addToken(TokenType.Comment); - } - - void consumeWhitespace(Char expected) - { - closeUnknown(); - - while(lookAhead() == expected) - advancePure(); - - addToken(TokenType.Whitespace); - } - - void terminal() - { - discardToken(); //discard quote token - var containsCharacters = false; - while(( lookAhead() != Quote || lookBehind() == Escape ) && !isAtEnd()) - { - if(lookAhead() == NewLine) - { - line++; - } - - advancePure(); - containsCharacters = true; - } - - if(containsCharacters) - { - addToken(TokenType.Terminal); - } - - if(isAtEnd()) - { - diagnostics.Add(UnterminatedTerminal, getLocation(), getLexeme()); - return; - } - - //add empty token - if(!containsCharacters) - { - addToken(TokenType.Terminal); - } - - //consume terminating quote - advancePure(); - discardToken(); //discard quote token - } - - void name() - { - closeUnknown(); - - while(isAlpha(lookAhead())) - advancePure(); - - addToken(TokenType.Name); - } - } -} diff --git a/DslGenerator/Lexing/Tokens.cs b/DslGenerator/Lexing/Tokens.cs deleted file mode 100644 index c00e190..0000000 --- a/DslGenerator/Lexing/Tokens.cs +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Lexing; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -static class Tokens -{ - public static Token Equal { get; } = new(TokenType.Equal, Lexemes.Equal); - public static Token Period { get; } = new(TokenType.Period, Lexemes.Period); - public static Token Dash { get; } = new(TokenType.Dash, Lexemes.Dash); - public static Token Alternative { get; } = new(TokenType.Slash, Lexemes.Alternative); - public static Token IncrementalAlternative { get; } = new(TokenType.SlashEqual, Lexemes.IncrementalAlternative); - public static Token GroupOpen { get; } = new(TokenType.ParenLeft, Lexemes.GroupOpen); - public static Token GroupClose { get; } = new(TokenType.ParenRight, Lexemes.GroupClose); - public static Token VariableRepetition { get; } = new(TokenType.Star, Lexemes.VariableRepetition); - public static Token OptionalSequenceOpen { get; } = new(TokenType.BracketLeft, Lexemes.OptionalSequenceOpen); - public static Token OptionalSequenceClose { get; } = new(TokenType.BracketRight, Lexemes.OptionalSequenceClose); - public static Token Semicolon { get; } = new(TokenType.Semicolon, Lexemes.Semicolon); - public static Token NewLine { get; } = new(TokenType.NewLine, Lexemes.NewLine); - public static Token Eof { get; } = new(TokenType.Eof, Lexemes.Eof); -} diff --git a/DslGenerator/Parsing/ParseResult.cs b/DslGenerator/Parsing/ParseResult.cs deleted file mode 100644 index 88cb8bf..0000000 --- a/DslGenerator/Parsing/ParseResult.cs +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Parsing; - -using RhoMicro.CodeAnalysis.DslGenerator.Analysis; -using RhoMicro.CodeAnalysis.DslGenerator.Grammar; - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -readonly record struct ParseResult(RuleList RuleList, DiagnosticsCollection Diagnostics) -{ - public Boolean Equals(ParseResult other) => RuleList.Equals(other.RuleList); - public override Int32 GetHashCode() => RuleList.GetHashCode(); -} diff --git a/DslGenerator/Parsing/Parser.cs b/DslGenerator/Parsing/Parser.cs deleted file mode 100644 index 292a4a9..0000000 --- a/DslGenerator/Parsing/Parser.cs +++ /dev/null @@ -1,373 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace RhoMicro.CodeAnalysis.DslGenerator.Parsing; - -using RhoMicro.CodeAnalysis.DslGenerator.Analysis; -using RhoMicro.CodeAnalysis.DslGenerator.Grammar; -using RhoMicro.CodeAnalysis.DslGenerator.Lexing; -using static RhoMicro.CodeAnalysis.DslGenerator.Analysis.DiagnosticDescriptors; - -using System; -using static RhoMicro.CodeAnalysis.DslGenerator.Grammar.Rule; -using System.Text; - -/* -RhoMicroBackusNaurForm; - -RuleList = [Name ";"] *RuleDefinition; -RuleDefinition = Name "=" Rule ";"; - -Rule = Binary / Unary / Primary; - -Binary = Range / Concatenation / Alternative; -Concatenation = Unary / (Rule Whitespace Rule); -Alternative = Unary / (Rule "/" Rule); -Range = Unary / (SingleAlpha "-" SingleAlpha); - -Unary = VariableRepetition / SpecificRepetition; -VariableRepetition = Primary / ("*" Rule); -SpecificRepetition = Primary / (Digit Rule); - -Primary = Grouping / OptionalGrouping / TerminalOrName; -Grouping = TerminalOrName / ("(" Rule ")"); -OptionalGrouping = TerminalOrName / ("[" Rule "]"); -TerminalOrName = Terminal / Any / Name; -Name = Alpha; -Terminal = "\"" . "\""; -Any = "."; - -Trivia = Whitespace / NewLine; -Whitespace = Space / Tab *Whitespace; -Space = " "; -Tab = " "; -NewLine = "\n" / "\r\n" / "\r"; -SingleAlpha = "a"-"z" / "A"-"Z" / "_"; -Alpha = SingleAlpha *SingleAlpha; -Digit = "0"-"9" *Digit; - -*/ - -#if DSL_GENERATOR -[IncludeFile] -internal -#endif -sealed class Parser -{ - private sealed class ParseException : Exception { } - public static Parser Instance { get; } = new(); - public ParseResult Parse(SourceText sourceText, CancellationToken cancellationToken -#if DSL_GENERATOR - , String filePath = "" -#endif - ) => - Parse(Tokenizer.Instance.Tokenize(sourceText, cancellationToken -#if DSL_GENERATOR - , filePath -#endif - ), cancellationToken); - public ParseResult Parse(TokenizeResult tokenizeResult, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - var current = 0; - var (tokens, tokenizeDiagnostics) = tokenizeResult; - var diagnostics = new DiagnosticsCollection - { - tokenizeDiagnostics - }; - - var ruleList = parseRuleList(); - - return new(ruleList, diagnostics); - - RuleList parseRuleList() - { - cancellationToken.ThrowIfCancellationRequested(); - - //RuleList = Name ";" *RuleDefinition; - Name? name = null; - try - { - discardTrivia(); - if(match(TokenType.Name)) - { - var nameToken = previous(); - discardTrivia(); - if(match(TokenType.Semicolon)) - { - name = new(nameToken); - } else - { - current = 0; - } - } - } catch(ParseException) - { - synchronize(); - } - - var definitions = new List(); - while(!isAtEnd()) - { - cancellationToken.ThrowIfCancellationRequested(); - - if(tryParseDefinition(out var def)) - { - definitions.Add(def!); - } - } - - var result = name != null ? - new NamedRuleList(name, definitions) : - new RuleList(definitions); - - return result; - } - - Boolean tryParseDefinition(out RuleDefinition? definition) - { - cancellationToken.ThrowIfCancellationRequested(); - - try - { - discardTrivia(); - definition = parseDefinition(); - discardTrivia(); - return true; - } catch(ParseException) - { - synchronize(); - definition = default; - return false; - } - } - - RuleDefinition parseDefinition() - { - cancellationToken.ThrowIfCancellationRequested(); - - //RueDefinition = Name "=" Rule; - var name = consume(TokenType.Name, "Expected rule name.").Lexeme.ToString() ?? String.Empty; - discardTrivia(); - var isInremental = match(TokenType.SlashEqual); - if(!isInremental) - { - _ = consume(TokenType.Equal, "Expected '=' or '/=' after rule name."); - } - - discardTrivia(); - var rule = parseRule(); - discardTrivia(); - _ = consume(TokenType.Semicolon, "Expected ';' after rule definition."); - RuleDefinition result = isInremental ? - new RuleDefinition.Incremental(new(name), rule) : - new RuleDefinition.New(new(name), rule); - - return result; - } - - Rule parseRule() - { - cancellationToken.ThrowIfCancellationRequested(); - - //Rule = Binary / Unary / Primary; - return parseBinary(); - } - - Rule parseBinary() - { - cancellationToken.ThrowIfCancellationRequested(); - - //Binary = Range / Concatenation / Alternative; - //Concatenation = Rule Rule; - //Alternative = Rule "/" Rule; - var left = parseRange(); - discardTrivia(discardWhitespace: false); - while(match(TokenType.Slash, TokenType.Whitespace) && !check(TokenType.Semicolon)) - { - cancellationToken.ThrowIfCancellationRequested(); - - var isAlternative = previous().Type == TokenType.Slash || - match(TokenType.Slash); // in case we have [Whitespace: ][Slash:/] - - discardTrivia(); - var right = parseRange(); - left = isAlternative ? - new Rule.Alternative(left, right) : - new Rule.Concatenation(left, right); - discardTrivia(discardWhitespace: false); - } - - return left; - } - - Rule parseRange() - { - cancellationToken.ThrowIfCancellationRequested(); - - //Range = Unary / Char "-" Char; - //Char = "\"" any character, quotes must be escaped "\""; - if(!check(TokenType.Terminal, t => t.Lexeme.Length == 1)) - { - return parseUnary(); - } - - var start = advance(); - if(!match(TokenType.Dash)) - { - return new Terminal(start); - } - - discardTrivia(); - var end = consume(TokenType.Terminal, "Expected terminal token of length one.", t => t.Lexeme.Length == 1); - - return new Rule.Range(new(start), new(end)); - } - - Rule parseUnary() - { - cancellationToken.ThrowIfCancellationRequested(); - - //Unary = Primary / VariableRepetition / SpecificRepetition; - //VariableRepetition = "*" Rule; - //SpecificRepetition = NUMBER Rule; - if(match(TokenType.Star, TokenType.Number)) - { - var @operator = previous(); - var isVariable = @operator.Type == TokenType.Star; - discardTrivia(); - var operand = parsePrimary(); - return isVariable ? - new Rule.VariableRepetition(operand) : - new Rule.SpecificRepetition(Int32.Parse(@operator.Lexeme.ToString()), operand); - } - - discardTrivia(); - return parsePrimary(); - } - - Rule parsePrimary() - { - cancellationToken.ThrowIfCancellationRequested(); - - //Primary = Grouping / OptionalGrouping / TerminalOrName; - //Grouping = TerminalOrName / ("(" Rule ")"); - //OptionalGrouping = TerminalOrName / ("[" Rule "]"); - var isSequence = match(TokenType.ParenLeft); //if false, none will have been consumed - if(!(isSequence || match(TokenType.BracketLeft))) //short cicuit prevents double consumption otherwise - { - discardTrivia(); - return parseTerminalOrName(); - } - //is either sequence or optional sequence - discardTrivia(); - var rule = parseRule(); - discardTrivia(); - _ = isSequence ? - consume(TokenType.ParenRight, "Expected ')' after rule grouping.") : - consume(TokenType.BracketRight, "Expected ']' after optional rule grouping."); - Rule result = isSequence ? - new Rule.Grouping(rule) : - new Rule.OptionalGrouping(rule); - - return result; - } - - Rule parseTerminalOrName() - { - cancellationToken.ThrowIfCancellationRequested(); - - //TerminalOrName = Terminal / Any / Name; - //Name = Alpha; - //Terminal = "\"". "\""; - //Any = "."; - if(match(TokenType.Name)) - { - var name = previous(); - return new Reference(new(name)); - } - - if(match(TokenType.Period)) - { - return Any.Instance; - } - - discardTrivia(); - return parseTerminal(); - } - - Rule parseTerminal() - { - var terminalValue = consume(TokenType.Terminal, "Expected terminal."); - return new Terminal(terminalValue); - } - - Boolean match(params TokenType[] types) - { - foreach(var type in types) - { - if(check(type)) - { - _ = advance(); - return true; - } - } - - return false; - } - - Token consume(TokenType type, String message, Func? matchPredicate = null) - { - if(check(type, matchPredicate)) - return advance(); - - throw error(peek(), message); - } - - void discardTrivia(Boolean discardWhitespace = true) - { - while(match(TokenType.NewLine, TokenType.Comment) || discardWhitespace && match(TokenType.Whitespace)) - { } - } - - Boolean check(TokenType type, Func? matchPredicate = null) - { - if(isAtEnd() || matchPredicate != null && !matchPredicate.Invoke(peek())) - return false; - - return peek().Type == type; - } - - Token advance() - { - if(!isAtEnd()) - current++; - return previous(); - } - - Boolean isAtEnd() => peek().Type == TokenType.Eof; - - Token peek() => tokens![current]; - - Token previous() => tokens![current - 1]; - - ParseException error(Token token, String message) - { - diagnostics!.Add(UnexpectedToken, token.Location, token.Type, message); - return new ParseException(); - } - - void synchronize() - { - _ = advance(); - - while(!isAtEnd()) - { - if(previous().Type == TokenType.Semicolon) - return; - - _ = advance(); - } - } - } -} diff --git a/DslGenerator/Properties/launchSettings.json b/DslGenerator/Properties/launchSettings.json deleted file mode 100644 index 5e98b4d..0000000 --- a/DslGenerator/Properties/launchSettings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "profiles": { - "TestApp": { - "commandName": "DebugRoslynComponent", - "targetProject": "..\\DslGenerator.TestApp\\DslGenerator.TestApp.csproj" - } - } -} \ No newline at end of file diff --git a/DslGenerator/README.md b/DslGenerator/README.md deleted file mode 100644 index dc9854f..0000000 --- a/DslGenerator/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# DslGenerator - -Lexes, Parses Abnf and more. \ No newline at end of file diff --git a/DslGenerator/Usings.cs b/DslGenerator/Usings.cs deleted file mode 100644 index cba61db..0000000 --- a/DslGenerator/Usings.cs +++ /dev/null @@ -1,4 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -global using RhoMicro.CodeAnalysis.Library; -global using RhoMicro.CodeAnalysis.Library.Text; diff --git a/EnumConstStringGenerator/Generators/EnumConstStringGenerator.cs b/EnumConstStringGenerator/Generators/EnumConstStringGenerator.cs index 682e8c5..fd1efeb 100644 --- a/EnumConstStringGenerator/Generators/EnumConstStringGenerator.cs +++ b/EnumConstStringGenerator/Generators/EnumConstStringGenerator.cs @@ -33,7 +33,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) { ct.ThrowIfCancellationRequested(); if(ctx.TargetSymbol is not INamedTypeSymbol { TypeKind: TypeKind.Enum } target) + { return null; + } var members = target .GetMembers() @@ -96,7 +98,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) IndentedStringBuilderOptions.GeneratedFile with { AmbientCancellationToken = ct }); if(@namespace != String.Empty) + { builder.Append("namespace ").Append(@namespace).Append(';').AppendLineCore(); + } builder.Append("public static class ").Append(name).Append("Strings") .OpenBracesBlock() diff --git a/EnumStringTestApp/EnumStringTestApp.csproj b/EnumStringTestApp/EnumStringTestApp.csproj deleted file mode 100644 index 5f95c1c..0000000 --- a/EnumStringTestApp/EnumStringTestApp.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - Exe - net9.0 - enable - enable - - - - - - - diff --git a/EnumStringTestApp/GlobalSuppressions.cs b/EnumStringTestApp/GlobalSuppressions.cs deleted file mode 100644 index da7b10a..0000000 --- a/EnumStringTestApp/GlobalSuppressions.cs +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -// This file is used by Code Analysis to maintain SuppressMessage -// attributes that are applied to this project. -// Project-level suppressions either have no target or are given -// a specific target and scoped to a namespace, type, member, etc. - -using System.Diagnostics.CodeAnalysis; - -[assembly: SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "")] diff --git a/EnumStringTestApp/Program.cs b/EnumStringTestApp/Program.cs deleted file mode 100644 index d73d13d..0000000 --- a/EnumStringTestApp/Program.cs +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -Console.WriteLine(TestNamespace.FooStrings.Foo); //generated members -Console.WriteLine(TestNamespace.FooStrings.FooBar); -Console.WriteLine(TestNamespace.FooStrings.Bar); -Console.WriteLine(TestNamespace.FooStrings.Baz); - -namespace TestNamespace -{ - [RhoMicro.CodeAnalysis.GenerateMemberStringConstants] - internal enum Foo: Int64 - { - Foo, - FooBar, - Bar = 23, - Baz - } -} diff --git a/IndentedStringBuilderTestApp/IndentedStringBuilderTestApp.csproj b/IndentedStringBuilderTestApp/IndentedStringBuilderTestApp.csproj deleted file mode 100644 index a2df4f5..0000000 --- a/IndentedStringBuilderTestApp/IndentedStringBuilderTestApp.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - Exe - net9.0 - enable - enable - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - diff --git a/IndentedStringBuilderTestApp/Program.cs b/IndentedStringBuilderTestApp/Program.cs deleted file mode 100644 index 08afc55..0000000 --- a/IndentedStringBuilderTestApp/Program.cs +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace IndentedStringBuilderTestApp; - -using RhoMicro.CodeAnalysis.Library.Text.SourceTexts; - -using static RhoMicro.CodeAnalysis.Library.Text.SourceTexts.IndentedStringBuilder.Appendables; - -internal class Program -{ - public static void Main(String[] _0) - { - //confusing: mutating operand instead of returning mutated instance - var operators = new IndentedStringBuilder().Operators + - "Hello, World!" + AppendLine() + - "Here is a new line :)" + - OpenBracesBlock() + - "inside a block now..." + - CloseBlock() + - "and done :)"; - - var result = operators.Builder.ToString(); - - Console.WriteLine(result); - } - - private static void Test1() - { - var builder = new IndentedStringBuilder( - IndentedStringBuilderOptions.Default with { DefaultIndentation = " " }) - .Append("namespace ").Append("Some.Parameter.Namespace") - .OpenBracesBlock(); - _ = builder.Append("partial class ").Append("SomeParameterName") - .OpenBracesBlock() - .Comment.OpenSummary() - .Append("Sample method") - .CloseBlock() - .Comment.OpenParam("a") - .Append("Sample Argument.") - .CloseBlock() - .Append("public void Foo(Int32 a)") - .OpenBracesBlock() - .Append("String[] bar = ") - .OpenBracketsBlock() - .Append("\"foobar\"") - .CloseBlock() - .AppendLine(';'); - - using(builder.OpenBracesBlockScope()) - { - _ = builder.Append("_ = foobar();"); - } - - _ = builder - .AppendLine("throw new NotImplementedException();") - .CloseBlock() - .AppendLine("void Bar()"); - - using(builder.OpenBracesBlockScope()) - { - using(builder.CreateIndentScope()) - { - _ = builder.OpenBracesBlock().CloseBlock().Append("throw new NotImplementedException();"); - } - } - - var result = builder - .CloseAllBlocks() - .ToString(); - - Console.WriteLine(result); - } -} diff --git a/Janus.Analyzers/AnalyzerReleases.Shipped.md b/Janus.Analyzers/AnalyzerReleases.Shipped.md deleted file mode 100644 index f3c849d..0000000 --- a/Janus.Analyzers/AnalyzerReleases.Shipped.md +++ /dev/null @@ -1,25 +0,0 @@ -## Release 23.0.0 - -### New Rules - -Rule ID | Category | Severity | Notes ---------|----------|----------|-------------------- - RMJ0001 | Usage | Warning | `ToStringSetting` is ignored due to user defined `ToString` implementation - RMJ0002 | Usage | Error | `record` unions are disallowed - RMJ0003 | Usage | Error | Generic unions cannot be json serializable - RMJ0004 | Usage | Error | No more than 31 variant groups may be defined - RMJ0005 | Usage | Error | `static` unions are disallowed - RMJ0006 | Usage | Error | Duplicate variant names are disallowed - RMJ0007 | Design | Warning | At least one unmanaged or managed struct or nullable reference type variant must be defined for struct unions - RMJ0008 | Design | Warning | `interface` variants are excluded from conversion operators - RMJ0009 | Usage | Error | Duplicate variant types are disallowed - RMJ0010 | Usage | Error | `object` cannot be used as a variant - RMJ0012 | Usage | Error | Variants that are the union itself are disallowed - RMJ0013 | Usage | Error | Explicitly defined base classes are disallowed - RMJ0014 | Usage | Warning | Prefer `Nullable` over `IsNullable = true` - RMJ0015 | Usage | Error | `Nullable` and `T` variants for the same type `T` are disallowed - RMJ0016 | Usage | Warning | `UnionTypeSettings` are ignored if missing `UnionTypeAttribute` - RMJ0017 | Usage | Warning | Duplicate variant group names are ignored - RMJ0018 | Design | Warning | Class unions should be sealed - RMJ0019 | Usage | Error | `ValueType` cannot be used as a variant of struct unions - RMJ0020 | Usage | Error | `ref struct` cannot be union diff --git a/Janus.Analyzers/AnalyzerReleases.Unshipped.md b/Janus.Analyzers/AnalyzerReleases.Unshipped.md deleted file mode 100644 index e69de29..0000000 diff --git a/Janus.Analyzers/UnionTypeAttribute.cs b/Janus.Analyzers/Attributes/UnionTypeAttribute.cs similarity index 100% rename from Janus.Analyzers/UnionTypeAttribute.cs rename to Janus.Analyzers/Attributes/UnionTypeAttribute.cs diff --git a/Janus.Analyzers/UnionTypeSettingsAttribute.cs b/Janus.Analyzers/Attributes/UnionTypeSettingsAttribute.cs similarity index 98% rename from Janus.Analyzers/UnionTypeSettingsAttribute.cs rename to Janus.Analyzers/Attributes/UnionTypeSettingsAttribute.cs index ae3019e..86a3a6c 100644 --- a/Janus.Analyzers/UnionTypeSettingsAttribute.cs +++ b/Janus.Analyzers/Attributes/UnionTypeSettingsAttribute.cs @@ -80,13 +80,13 @@ enum JsonConverterSetting /// Inherit, /// - /// No JSON converter implementation is emitted. + /// A JSON converter implementation is emitted. /// - OmitJsonConverter, + EmitJsonConverter, /// - /// A JSON converter implementation is emitted. + /// No JSON converter implementation is emitted. /// - EmitJsonConverter + OmitJsonConverter } /// @@ -111,8 +111,6 @@ sealed partial class UnionTypeSettingsAttribute : global::System.Attribute /// reference equality for comparing reference union types via == or !=. /// public EqualityOperatorsSetting EqualityOperatorsSetting { get; set; } - - /// /// Gets or sets a value indicating whether to make the union type JSON serializable. /// diff --git a/Janus.Analyzers/Components/ConstructorComponent.cs b/Janus.Analyzers/Components/ConstructorComponent.cs index 70c5475..bb03836 100644 --- a/Janus.Analyzers/Components/ConstructorComponent.cs +++ b/Janus.Analyzers/Components/ConstructorComponent.cs @@ -34,7 +34,7 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation if (validate) { var isValid = true; - Validate(value, throwIfInvalid: true, ref isValid); + Validate{{variant.Name}}(value, throwIfInvalid: true, ref isValid); } {{Create(variant, static (v, b, ct) => diff --git a/Janus.Analyzers/Components/FactoriesComponent.cs b/Janus.Analyzers/Components/FactoriesComponent.cs index 2ee34ac..aaa2df4 100644 --- a/Janus.Analyzers/Components/FactoriesComponent.cs +++ b/Janus.Analyzers/Components/FactoriesComponent.cs @@ -149,7 +149,7 @@ public static bool TryCreate( b.Append($"{Langword("true")} if an instance of {Cref(m.DocsCommentId)} could be created; otherwise, {Langword("false")}."); })}} - public static bool TryCreate( + public static bool TryCreateFrom( {{new UnionTypeNameComponent(m)}} value, [{{typeof(NotNullWhenAttribute)}}(true)] out {{new UnionTypeNameComponent(m, RenderNullable: true)}} union) @@ -193,10 +193,74 @@ public static bool TryCreate( b.Append($"The new instance of {Cref(m.DocsCommentId)}."); })}} - public static {{new UnionTypeNameComponent(m)}} Create( + public static {{new UnionTypeNameComponent(m)}} CreateFrom( {{variant.Type.NullableName}} value) => new {{new UnionTypeNameComponent(m)}}(value, validate: true); + {{Summary(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Creates an instance of {Cref(m.DocsCommentId)}."); + })}} + {{Param("value", m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The value to create an instance of {Cref(m.DocsCommentId)} from."); + })}} + {{Returns(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The new instance of {Cref(m.DocsCommentId)}."); + })}} + public static {{new UnionTypeNameComponent(m)}} CreateFrom{{variant.Name}}( + {{variant.Type.NullableName}} value) + => new {{new UnionTypeNameComponent(m)}}(value, validate: true); + + {{Summary(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Attempts to create an instance of {Cref(m.DocsCommentId)}."); + })}} + {{Param("value", m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The value to create an instance of {Cref(m.DocsCommentId)} from."); + })}} + {{Param("union", m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $""" + The instance of {Cref(m.DocsCommentId)} if one could be created; otherwise, + {(m.TypeKind is UnionTypeKind.Class ? Langword("null") : Langword("default"))}. + """); + })}} + {{Returns(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"{Langword("true")} if an instance of {Cref(m.DocsCommentId)} could be created; otherwise, {Langword("false")}."); + })}} + public static bool TryCreateFrom( + {{variant.Type.NullableName}} value, + [{{typeof(NotNullWhenAttribute)}}(true)] + out {{new UnionTypeNameComponent(m, RenderNullable: true)}} union) + { + var isValid = true; + Validate{{variant.Name}}(value, throwIfInvalid: false, ref isValid); + union = isValid + ? new {{new UnionTypeNameComponent(m)}}(value, validate: false) + : default; + + return isValid; + } + {{Summary(m, static (m, b, ct) => { ct.ThrowIfCancellationRequested(); @@ -225,13 +289,13 @@ public static bool TryCreate( b.Append($"{Langword("true")} if an instance of {Cref(m.DocsCommentId)} could be created; otherwise, {Langword("false")}."); })}} - public static bool TryCreate( + public static bool TryCreateFrom{{variant.Name}}( {{variant.Type.NullableName}} value, [{{typeof(NotNullWhenAttribute)}}(true)] out {{new UnionTypeNameComponent(m, RenderNullable: true)}} union) { var isValid = true; - Validate(value, throwIfInvalid: false, ref isValid); + Validate{{variant.Name}}(value, throwIfInvalid: false, ref isValid); union = isValid ? new {{new UnionTypeNameComponent(m)}}(value, validate: false) : default; diff --git a/Janus.Analyzers/Components/FactoryComponent.cs b/Janus.Analyzers/Components/FactoryComponent.cs index 3dbbdcb..733a0ff 100644 --- a/Janus.Analyzers/Components/FactoryComponent.cs +++ b/Janus.Analyzers/Components/FactoryComponent.cs @@ -72,6 +72,8 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation { ct.ThrowIfCancellationRequested(); + var nullCaseHandled = false; + for (var i = 0; i < m.Variants.Count; i++) { ct.ThrowIfCancellationRequested(); @@ -82,7 +84,16 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation b.AppendLine(); } - b.Append($"case {variant.Type.Name} v: return {new UnionTypeNameComponent(m)}.TryCreate(v, out union);"); + b.Append($"case {variant.Type.Name} v: return {new UnionTypeNameComponent(m)}.TryCreateFrom{variant.Name}(v, out union);"); + + if (!variant.Type.IsNullable || nullCaseHandled) { + continue; + } + + b.AppendLine() + .Append($"case null: return {new UnionTypeNameComponent(m)}.TryCreateFrom{variant.Name}(({variant.Type.NullableName})null, out union);"); + + nullCaseHandled = true; } })}} case {{new UnionTypeNameComponent(m)}} v: @@ -133,6 +144,8 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation { ct.ThrowIfCancellationRequested(); + var nullCaseHandled = false; + for (var i = 0; i < m.Variants.Count; i++) { ct.ThrowIfCancellationRequested(); @@ -144,6 +157,15 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation } b.Append($"case {variant.Type.Name} v: return new {new UnionTypeNameComponent(m)}(v);"); + + if (!variant.Type.IsNullable || nullCaseHandled) { + continue; + } + + b.AppendLine() + .Append($"case null: return new {new UnionTypeNameComponent(m)}(({variant.Type.NullableName})null);"); + + nullCaseHandled = true; } })}} case {{new UnionTypeNameComponent(m)}} v: return new {{new UnionTypeNameComponent(m)}}(v); diff --git a/Janus.Analyzers/Components/OperatorsComponent.cs b/Janus.Analyzers/Components/OperatorsComponent.cs index 73dd739..50b0935 100644 --- a/Janus.Analyzers/Components/OperatorsComponent.cs +++ b/Janus.Analyzers/Components/OperatorsComponent.cs @@ -35,7 +35,7 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation b.Append( $""" {Inheritdoc()} - public static implicit operator {new UnionTypeNameComponent(m)}({variant.Type.NullableName} value) => Create(value); + public static implicit operator {new UnionTypeNameComponent(m)}({variant.Type.NullableName} value) => CreateFrom{variant.Name}(value); {Inheritdoc()} public static {(m.Variants.Count is 1 ? "implicit" : "explicit")} operator {variant.Type.NullableName}({new UnionTypeNameComponent(m)} union) => union.CastTo{variant.Name}; """ diff --git a/Janus.Analyzers/Components/ValidationComponent.cs b/Janus.Analyzers/Components/ValidationComponent.cs index a6c389a..863189d 100644 --- a/Janus.Analyzers/Components/ValidationComponent.cs +++ b/Janus.Analyzers/Components/ValidationComponent.cs @@ -58,7 +58,7 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation {Param("value", "The value to validate.")} {Param("throwIfInvalid", "Indicates whether to throw an exception if the value is invalid.")} {Param("isValid", "Indicates whether the value is valid.")} - static partial void Validate({v.Type.NullableName} value, bool throwIfInvalid, ref bool isValid); + static partial void Validate{v.Name}({v.Type.NullableName} value, bool throwIfInvalid, ref bool isValid); """ ); }, separator: "\n\n")}} diff --git a/Janus.Analyzers/DiagnosticDescriptors.cs b/Janus.Analyzers/DiagnosticDescriptors.cs index daeed37..c8db4db 100644 --- a/Janus.Analyzers/DiagnosticDescriptors.cs +++ b/Janus.Analyzers/DiagnosticDescriptors.cs @@ -163,4 +163,12 @@ internal static class DiagnosticDescriptors category: "Usage", defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true); + + public static DiagnosticDescriptor TypeParameterVariantsShouldBeNamed { get; } = new( + id: "RMJ0021", + title: "Type parameter variants should be named", + messageFormat: "Variant `{0}` of union `{1}` should be named explicitly", + category: "Design", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); } diff --git a/Janus.Analyzers/Janus.Analyzers.csproj b/Janus.Analyzers/Janus.Analyzers.csproj index 4786796..8505fef 100644 --- a/Janus.Analyzers/Janus.Analyzers.csproj +++ b/Janus.Analyzers/Janus.Analyzers.csproj @@ -32,9 +32,4 @@ - - - - - diff --git a/Janus.Analyzers/JanusAnalyzer.cs b/Janus.Analyzers/JanusAnalyzer.cs index 88ad9a7..cf4f820 100644 --- a/Janus.Analyzers/JanusAnalyzer.cs +++ b/Janus.Analyzers/JanusAnalyzer.cs @@ -41,6 +41,7 @@ public sealed partial class JanusAnalyzer : DiagnosticAnalyzer DiagnosticDescriptors.DuplicateVariantGroupNamesAreIgnored, DiagnosticDescriptors.ClassUnionsShouldBeSealed, DiagnosticDescriptors.UnionCannotBeRefStruct, + DiagnosticDescriptors.TypeParameterVariantsShouldBeNamed, ]; /// @@ -89,6 +90,92 @@ public override void Initialize(AnalysisContext context) ReportClassUnionsShouldBeSealed, SymbolKind.NamedType); context.RegisterSymbolAction( ReportUnionCannotBeRefStruct, SymbolKind.NamedType); + context.RegisterOperationAction( + ReportTypeParameterVariantsShouldBeNamed, OperationKind.Attribute); + } + + private static void ReportTypeParameterVariantsShouldBeNamed(OperationAnalysisContext ctx) + { + var ct = ctx.CancellationToken; + + ct.ThrowIfCancellationRequested(); + + if (!AttributeAnalysisContext.TryCreateForUnionTypeAttribute(ctx, out var attributeContext)) + { + return; + } + + if (attributeContext.TargetSymbol is not ITypeParameterSymbol) + { + return; + } + + var initializers = attributeContext.AttributeOperation.Initializer?.Initializers ?? []; + + var hasNameAssignment = false; + + foreach (var initializer in initializers) + { + ct.ThrowIfCancellationRequested(); + + if (initializer is ISimpleAssignmentOperation + { + Target: IPropertyReferenceOperation + { + Member.Name: nameof(UnionTypeAttribute.Name) + } + }) + { + hasNameAssignment = true; + break; + } + } + + if (hasNameAssignment) + { + return; + } + + if (!attributeContext.TryGetUnionTypeSymbol(out var union)) + { + return; + } + + var unioName = union.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat); + var variantName = attributeContext.TargetSymbol.Name; + foreach (var reference in union.DeclaringSyntaxReferences) + { + ct.ThrowIfCancellationRequested(); + + if (reference.GetSyntax(ct) is not TypeDeclarationSyntax + { + TypeParameterList.Parameters: [{ }, ..] typeParameters + }) + { + continue; + } + + foreach (var typeParameterSyntax in typeParameters) + { + ct.ThrowIfCancellationRequested(); + + if (typeParameterSyntax.Identifier.Text != variantName) + { + continue; + } + + var location = typeParameterSyntax.Identifier.GetLocation(); + var diagnostic = Diagnostic.Create( + DiagnosticDescriptors.TypeParameterVariantsShouldBeNamed, + location, + messageArgs: + [ + variantName, + unioName + ]); + ctx.ReportDiagnostic(diagnostic); + } + } } private static void ReportUnionCannotBeRefStruct(SymbolAnalysisContext ctx) diff --git a/Janus.Benchmarks/Directory.build.props b/Janus.Benchmarks/Directory.build.props new file mode 100644 index 0000000..adf14e9 --- /dev/null +++ b/Janus.Benchmarks/Directory.build.props @@ -0,0 +1,5 @@ + + + $(NoWarn);CS1591 + + diff --git a/Janus.Benchmarks/Janus.Benchmarks.csproj b/Janus.Benchmarks/Janus.Benchmarks.csproj new file mode 100644 index 0000000..364ba0e --- /dev/null +++ b/Janus.Benchmarks/Janus.Benchmarks.csproj @@ -0,0 +1,23 @@ + + + + Exe + net10.0 + preview + enable + enable + Janus.Benchmarks + + + + + + + + + + + + + + diff --git a/Janus.Benchmarks/Program.cs b/Janus.Benchmarks/Program.cs new file mode 100644 index 0000000..919e08f --- /dev/null +++ b/Janus.Benchmarks/Program.cs @@ -0,0 +1,6 @@ +// See https://aka.ms/new-console-template for more information + +using System.Security.Cryptography.X509Certificates; +using BenchmarkDotNet.Running; + +BenchmarkRunner.Run(typeof(Program).Assembly); diff --git a/Janus.Benchmarks/SwitchStateBenchmark.cs b/Janus.Benchmarks/SwitchStateBenchmark.cs new file mode 100644 index 0000000..9fbc8a1 --- /dev/null +++ b/Janus.Benchmarks/SwitchStateBenchmark.cs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MPL-2.0 + +using BenchmarkDotNet.Attributes; +using OneOf; +using RhoMicro.CodeAnalysis; + +[SimpleJob] +[MemoryDiagnoser] +public partial class SwitchStateBenchmark +{ + [UnionType] + partial struct JanusUnion; + + [Benchmark(Baseline = true)] + public String Janus() + { + var state = "State"; + + var result = new JanusUnion(32).Switch( + state, + onInt32: static (_, s) => s, + onSingle: static (_, s) => s); + + return result; + } + + [Benchmark] + public String OneOf() + { + var state = "State"; + + var result = OneOf.FromT0(32).Match( + f0: _ => state, + f1: _ => state); + + return result; + } +} diff --git a/Janus.Benchmarks/UnmanagedOverlayingBenchmark.cs b/Janus.Benchmarks/UnmanagedOverlayingBenchmark.cs new file mode 100644 index 0000000..a8253c1 --- /dev/null +++ b/Janus.Benchmarks/UnmanagedOverlayingBenchmark.cs @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MPL-2.0 + +using BenchmarkDotNet.Attributes; +using OneOf; +using RhoMicro.CodeAnalysis; + +[SimpleJob] +[MemoryDiagnoser] +public partial class UnmanagedOverlayingBenchmark +{ + [UnionType] + sealed partial class JanusUnion; + + class OneOfUnion : OneOfBase + { + public OneOfUnion(OneOf input) : base(input) + { + } + } + + [Benchmark(Baseline = true)] + public Object Janus() => new JanusUnion(0); + + [Benchmark] + public Object OneOf() => new OneOfUnion(0); +} diff --git a/Janus.TestApplication/Program.cs b/Janus.TestApplication/Program.cs index 7bfd698..e6d7ca8 100644 --- a/Janus.TestApplication/Program.cs +++ b/Janus.TestApplication/Program.cs @@ -11,6 +11,7 @@ PropertyNamingPolicy = new CustomNamingPolicy(), PropertyNameCaseInsensitive = true, }; +// ReSharper disable once RedundantAssignment var union = IntDoubleString.Create(42d); union = "foo"; union = 47; @@ -30,7 +31,12 @@ Console.WriteLine(union); var serialized = JsonSerializer.Serialize(union, options); Console.WriteLine(serialized); -serialized = serialized.ToLower().Replace("47", "[9,8,7]").Replace("2", "3"); +#pragma warning disable CA1308 +serialized = serialized + .ToLowerInvariant() +#pragma warning restore CA1308 + .Replace("47", "[9,8,7]", StringComparison.InvariantCulture) + .Replace("2", "3", StringComparison.InvariantCulture); Console.WriteLine(serialized); var deserialized = JsonSerializer.Deserialize( diff --git a/Janus.Tests.EndToEnd/IsPropertyTests.cs b/Janus.Tests.EndToEnd/IsPropertyTests.cs index 874b8b2..e877df3 100644 --- a/Janus.Tests.EndToEnd/IsPropertyTests.cs +++ b/Janus.Tests.EndToEnd/IsPropertyTests.cs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 +#pragma warning disable RMJ0021 #pragma warning disable IDE0059 // Unnecessary assignment of a value #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member namespace RhoMicro.CodeAnalysis.Janus.EndToEnd.Tests; diff --git a/Janus.Tests.EndToEnd/ReadMeAssertions.cs b/Janus.Tests.EndToEnd/ReadMeAssertions.cs index 9a1b755..bdd56f6 100644 --- a/Janus.Tests.EndToEnd/ReadMeAssertions.cs +++ b/Janus.Tests.EndToEnd/ReadMeAssertions.cs @@ -3,6 +3,8 @@ #pragma warning disable IDE0250 #pragma warning disable IDE0059 #pragma warning disable CS1591 +#pragma warning disable RMJ0021 + namespace RhoMicro.CodeAnalysis.Janus.EndToEnd.Tests; using System; diff --git a/Janus.Tests.EndToEnd/TryAsFunctionsTests.cs b/Janus.Tests.EndToEnd/TryAsFunctionsTests.cs index a05e142..6ac8b3c 100644 --- a/Janus.Tests.EndToEnd/TryAsFunctionsTests.cs +++ b/Janus.Tests.EndToEnd/TryAsFunctionsTests.cs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MPL-2.0 +#pragma warning disable RMJ0021 #pragma warning disable IDE0059 // Unnecessary assignment of a value #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member namespace RhoMicro.CodeAnalysis.Janus.EndToEnd.Tests; diff --git a/Janus.Tests/Janus.Tests.csproj b/Janus.Tests/Janus.Tests.csproj index 6685fb7..82973aa 100644 --- a/Janus.Tests/Janus.Tests.csproj +++ b/Janus.Tests/Janus.Tests.csproj @@ -6,6 +6,7 @@ Exe Janus.Tests net10.0 + $(NoWarn);CS1591;NU1510;NU1701 + - - netstandard2.0 - false - true - true - true - enable - enable - + + netstandard2.0 + false + true + true + true + enable + enable + - - true - true - Generates json schemata from class definitions. - Source Generator;json schema;json;jsonschema;appsettings - logo_256.png - + + true + true + Generates json schemata from class definitions. + Source Generator;json schema;json;jsonschema;appsettings + logo_256.png + - - - + + + - - $(DefineConstants);GENERATOR - + + $(DefineConstants);GENERATOR + - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + - - - - - - + + + + + + - - - true - TargetFramework=net9.0 - tool - tool - PreserveNewest - - + + + true + TargetFramework=net9.0 + tool + tool + PreserveNewest + + - - - PreserveNewest - - - PreserveNewest - - + + + PreserveNewest + + + PreserveNewest + + diff --git a/JsonSchemaGenerator/Models/Core/JsonListLikeModel.cs b/JsonSchemaGenerator/Models/Core/JsonListLikeModel.cs index 713ef49..37aaf6f 100644 --- a/JsonSchemaGenerator/Models/Core/JsonListLikeModel.cs +++ b/JsonSchemaGenerator/Models/Core/JsonListLikeModel.cs @@ -22,7 +22,9 @@ public override void AppendTo(StringBuilder sb, CancellationToken ct) { ct.ThrowIfCancellationRequested(); if(i > 0 && Value.Count > 1) + { _ = sb.Append(','); + } _ = sb.AppendModel(item, ct); i++; diff --git a/JsonSchemaGenerator/Models/Core/JsonNumberModel.cs b/JsonSchemaGenerator/Models/Core/JsonNumberModel.cs index 1b7dc95..b7a0d61 100644 --- a/JsonSchemaGenerator/Models/Core/JsonNumberModel.cs +++ b/JsonSchemaGenerator/Models/Core/JsonNumberModel.cs @@ -12,7 +12,7 @@ public JsonNumberModel(Number value) : base(value) { } public override void AppendTo(StringBuilder sb, CancellationToken ct) { ct.ThrowIfCancellationRequested(); - _ = sb.Append(Value.Match( + _ = sb.Append(Value.Switch( static d => d.ToString("0.#", CultureInfo.InvariantCulture), static l => l.ToString("0.#", CultureInfo.InvariantCulture), static ul => ul.ToString("0.#", CultureInfo.InvariantCulture))); diff --git a/JsonSchemaGenerator/Models/Core/JsonObjectModel.cs b/JsonSchemaGenerator/Models/Core/JsonObjectModel.cs index b26752d..d7627d6 100644 --- a/JsonSchemaGenerator/Models/Core/JsonObjectModel.cs +++ b/JsonSchemaGenerator/Models/Core/JsonObjectModel.cs @@ -22,7 +22,9 @@ public override void AppendTo(StringBuilder sb, CancellationToken ct) { ct.ThrowIfCancellationRequested(); if(i > 0 && Value.Count > 1) + { _ = sb.Append(','); + } _ = sb.Append('"').Append(key).Append("\":").AppendModel(value, ct); i++; diff --git a/JsonSchemaGenerator/Models/Core/JsonSchemaModel.cs b/JsonSchemaGenerator/Models/Core/JsonSchemaModel.cs index d8e9abf..0387c72 100644 --- a/JsonSchemaGenerator/Models/Core/JsonSchemaModel.cs +++ b/JsonSchemaGenerator/Models/Core/JsonSchemaModel.cs @@ -19,15 +19,21 @@ public SubSchemaModelBuilder SubSchema() public JsonObjectModel Build(Boolean includeId = false, CancellationToken ct = default) { if(_result is not null) + { return _result; + } while(Interlocked.CompareExchange(ref _buildLock, 1, 0) == 1) + { ct.ThrowIfCancellationRequested(); + } try { if(_result is not null) + { return _result; + } _result = BuildCore(includeId, ct); return _result; @@ -73,7 +79,9 @@ private JsonObjectModel BuildCore(Boolean includeId, CancellationToken ct) } if(!hasSimple) + { return anyOf; + } var result = new JsonObjectModel(); result.Array("anyOf").Value.Add(anyOf); diff --git a/JsonSchemaGenerator/Models/Wrappers/AdditionalPropertiesModel.cs b/JsonSchemaGenerator/Models/Wrappers/AdditionalPropertiesModel.cs index 20c0efe..2353757 100644 --- a/JsonSchemaGenerator/Models/Wrappers/AdditionalPropertiesModel.cs +++ b/JsonSchemaGenerator/Models/Wrappers/AdditionalPropertiesModel.cs @@ -12,7 +12,9 @@ public JsonValueModel ModelOrSetDefault get { if(Model.Value is JsonSchemaModel { Size: 0 }) + { False(); + } return Model.Value; } diff --git a/JsonSchemaGenerator/Models/Wrappers/AnnotationsBuilder.cs b/JsonSchemaGenerator/Models/Wrappers/AnnotationsBuilder.cs index f867664..b9cf7df 100644 --- a/JsonSchemaGenerator/Models/Wrappers/AnnotationsBuilder.cs +++ b/JsonSchemaGenerator/Models/Wrappers/AnnotationsBuilder.cs @@ -9,8 +9,13 @@ internal readonly record struct AnnotationsBuilder(JsonObjectModel Model) public void CopyTo(JsonObjectModel model) { if(Description.Value is { Length: > 0 } description) + { new AnnotationsBuilder(model).Description.Value = description; + } + if(Title.Value is { Length: > 0 } title) + { new AnnotationsBuilder(model).Title.Value = title; + } } } diff --git a/JsonSchemaGenerator/Models/Wrappers/Id.cs b/JsonSchemaGenerator/Models/Wrappers/Id.cs index c084110..9b99c45 100644 --- a/JsonSchemaGenerator/Models/Wrappers/Id.cs +++ b/JsonSchemaGenerator/Models/Wrappers/Id.cs @@ -39,7 +39,9 @@ public static Id Create(JsonSchemaAttribute? attribute, ISymbol target, Cancella var value = attribute?.Id?.Trim(); if(String.IsNullOrWhiteSpace(value)) + { return Create(target, ct); + } return Create(value!); } diff --git a/JsonSchemaGenerator/Models/Wrappers/SubSchemaModelBuilder.cs b/JsonSchemaGenerator/Models/Wrappers/SubSchemaModelBuilder.cs index c13a768..86b45d4 100644 --- a/JsonSchemaGenerator/Models/Wrappers/SubSchemaModelBuilder.cs +++ b/JsonSchemaGenerator/Models/Wrappers/SubSchemaModelBuilder.cs @@ -41,20 +41,35 @@ public void Build(List result, Boolean includeId, CancellationTo } else { if(simple.Items.IsValueCreated && simple.Items.Value.Build(includeId: false, ct) is { Value.Count: > 0 } items) + { simpleSchema.Value.SetProperty("items", items); + } + if(simple.Type.Model is { Value.Count: > 0 } types) + { simpleSchema.Value.SetProperty("type", types); + } + if(simple.Properties.Model is { Value.Count: > 0 } properties) + { simpleSchema.Value.SetProperty("properties", properties); + } + if(simple.Required.Model is { Value.Count: > 0 } required) + { simpleSchema.Value.SetProperty("required", required); + } } if(simple.Type.Model.Value.Any(t => t is JsonTypeModel { Value: JsonType.Object })) + { simpleSchema.Value.SetProperty("additionalProperties", simple.Additional.ModelOrSetDefault); + } if(includeId && simple.GetId() is { Value.Length: > 0 } id) + { simpleSchema.Value.SetProperty("$id", id); + } if(simpleSchema is { IsValueCreated: true, Value: { } schema }) { @@ -93,7 +108,9 @@ private void Populate( if(symbol is not INamedTypeSymbol target) { if(symbol is ITypeSymbol typeSymbol && populateNonProperties) + { PopulateNonProperties(rootId, typeSymbol, idCache, isKnownToBeRef: false, ct); + } return; } @@ -106,7 +123,9 @@ private void Populate( Ref.Value.Ref(id, rootId.Value); if(populateNonProperties) + { PopulateNonProperties(rootId, target, idCache, isKnownToBeRef: true, ct); + } return; } @@ -115,11 +134,15 @@ private void Populate( ct.ThrowIfCancellationRequested(); if(populateNonProperties) + { PopulateNonProperties(rootId, target, idCache, isKnownToBeRef: false, ct); + } ct.ThrowIfCancellationRequested(); if(Ref.IsValueCreated) + { return; + } ct.ThrowIfCancellationRequested(); // is schema? @@ -160,7 +183,9 @@ private void Populate( propSchema.Populate(rootId, propType, idCache, ct); if(propSymbol.IsRequired) + { Simple.Value.Required.Add(propName); + } if(propSymbol.TryGetFirstJsonSchemaPropertyAttribute(out var a)) { @@ -189,7 +214,10 @@ private void PopulateNonProperties( ct.ThrowIfCancellationRequested(); Simple.Value.Type.Add(JsonType.Null); if(typeArg is INamedTypeSymbol namedTypeArg) + { PopulateNonProperties(rootId, namedTypeArg, idCache, isKnownToBeRef, ct); + } + return; } // enum @@ -217,7 +245,9 @@ private void PopulateNonProperties( _ => null }; if(strongValue.HasValue) + { Enum.Value.Add(strongValue.Value); + } } Populate(rootId, underlyingType, idCache, ct); @@ -231,7 +261,9 @@ private void PopulateNonProperties( } if(isKnownToBeRef) + { return; + } ct.ThrowIfCancellationRequested(); var typeString = target.ToDisplayString(SymbolDisplayFormats.FullyQualifiedNoGlobalNamespaceFormat); diff --git a/Lyra/CSharpSourceBuilder.cs b/Lyra/CSharpSourceBuilder.cs index fe90d9b..a61338a 100644 --- a/Lyra/CSharpSourceBuilder.cs +++ b/Lyra/CSharpSourceBuilder.cs @@ -84,8 +84,8 @@ public CSharpSourceBuilder() : this(CSharpSourceBuilderOptions.Default) private IInterpolationIndentationDetector _detector; private Boolean _lastWasNewLine = true; - private Boolean _lastWasEmptyLine = false; - private Boolean _preludeWritten = false; + private Boolean _lastWasEmptyLine; + private Boolean _preludeWritten; private Char[] _buffer; public CancellationToken CancellationToken { get; private set; } diff --git a/OptionsGenerator.Tests/AnalyzerTests.cs b/OptionsGenerator.Tests/AnalyzerTests.cs index 47c51e7..4d96c40 100644 --- a/OptionsGenerator.Tests/AnalyzerTests.cs +++ b/OptionsGenerator.Tests/AnalyzerTests.cs @@ -36,7 +36,7 @@ protected override IEnumerable GetSourceGenerators() => } [Fact] - public Task NonTargetWritablePropertyDoesNotRaise_ROG0002() => + public Task NonTargetWritablePropertyDoesNotRaiseROG0002() => new OptionsGeneratorAnalyzerTest( $$""" public partial interface IFoo @@ -46,7 +46,7 @@ public partial interface IFoo """).RunAsync(TestContext.Current.CancellationToken); [Fact] - public Task ReadOnlyPropertyDoesNotRaise_ROG0002() => + public Task ReadOnlyPropertyDoesNotRaiseROG0002() => new OptionsGeneratorAnalyzerTest( $$""" using RhoMicro.CodeAnalysis; @@ -58,7 +58,7 @@ public partial interface IFoo } """).RunAsync(TestContext.Current.CancellationToken); [Fact] - public Task WritablePropertyRaises_ROG0002() => + public Task WritablePropertyRaisesROG0002() => new OptionsGeneratorAnalyzerTest( $$""" using RhoMicro.CodeAnalysis; @@ -76,13 +76,13 @@ public partial interface IFoo [InlineData("T, S")] [InlineData("TName")] [InlineData("TElement, TName")] - public Task NonTargetGenericInterfaceDoesNotRaise_ROG0001(String typeParameters) => + public Task NonTargetGenericInterfaceDoesNotRaiseROG0001(String typeParameters) => new OptionsGeneratorAnalyzerTest( $"public partial interface IFoo<{typeParameters}>;") .RunAsync(TestContext.Current.CancellationToken); [Fact] - public Task NestedTargetInterfaceRaises_ROG0003() => + public Task NestedTargetInterfaceRaisesROG0003() => new OptionsGeneratorAnalyzerTest( """ using RhoMicro.CodeAnalysis; @@ -99,7 +99,7 @@ public partial interface {|ROG0003:IFoo|}; [InlineData("T, S")] [InlineData("TName")] [InlineData("TElement, TName")] - public Task GenericTargetInterfaceRaises_ROG0001(String typeParameters) => + public Task GenericTargetInterfaceRaisesROG0001(String typeParameters) => new OptionsGeneratorAnalyzerTest( $$""" using RhoMicro.CodeAnalysis; @@ -109,7 +109,7 @@ public partial interface {|ROG0001:IFoo|}<{{typeParameters}}>; """).RunAsync(TestContext.Current.CancellationToken); [Fact] - public Task NonPartialTargetInterfaceRaises_CS0260() => + public Task NonPartialTargetInterfaceRaisesCS0260() => new OptionsGeneratorAnalyzerTest( $$""" using RhoMicro.CodeAnalysis; diff --git a/OptionsGenerator.Tests/OptionsGenerator.Tests.csproj b/OptionsGenerator.Tests/OptionsGenerator.Tests.csproj index 9b69846..b84585c 100644 --- a/OptionsGenerator.Tests/OptionsGenerator.Tests.csproj +++ b/OptionsGenerator.Tests/OptionsGenerator.Tests.csproj @@ -1,42 +1,42 @@ - - enable - enable - Exe - OptionsGenerator.Tests - net8.0 - - + + enable + enable + Exe + OptionsGenerator.Tests + net10.0 + $(NoWarn);CS1591;NU1510;NU1701 + + - - - + + + - - - + + + - - - - - - - - + + + + + + + + + + + - - - + + + diff --git a/OptionsGenerator/AnalyzerReleases.Shipped.md b/OptionsGenerator/AnalyzerReleases.Shipped.md deleted file mode 100644 index bea396b..0000000 --- a/OptionsGenerator/AnalyzerReleases.Shipped.md +++ /dev/null @@ -1,9 +0,0 @@ -## Release 20.1.4 - -### New Rules - -Rule ID | Category | Severity | Notes ---------|------------------|----------|-------------------- -ROG0001 | OptionsGenerator | Error | Option interfaces cannot be generic -ROG0002 | OptionsGenerator | Error | Option properties must be readonly -ROG0003 | OptionsGenerator | Error | Option interfaces cannot be nested diff --git a/OptionsGenerator/AnalyzerReleases.Unshipped.md b/OptionsGenerator/AnalyzerReleases.Unshipped.md deleted file mode 100644 index e69de29..0000000 diff --git a/OptionsGenerator/Analyzers/OptionsAnalyzer.cs b/OptionsGenerator/Analyzers/OptionsAnalyzer.cs index 9f6186d..4d8e0be 100644 --- a/OptionsGenerator/Analyzers/OptionsAnalyzer.cs +++ b/OptionsGenerator/Analyzers/OptionsAnalyzer.cs @@ -86,10 +86,14 @@ public override void Initialize(AnalysisContext context) private static void AnalyzeSymbol(SymbolAnalysisContext ctx) { if(AnalyzeNamedTypeSymbol(ctx)) + { return; + } if(AnalyzePropertySymbol(ctx)) + { return; + } } private static Boolean AnalyzePropertySymbol(SymbolAnalysisContext ctx) diff --git a/OptionsGenerator/Generators/OptionsGenerator.cs b/OptionsGenerator/Generators/OptionsGenerator.cs index f124c67..5ab56c3 100644 --- a/OptionsGenerator/Generators/OptionsGenerator.cs +++ b/OptionsGenerator/Generators/OptionsGenerator.cs @@ -44,7 +44,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) using var modelContext = ModelCreationContext.CreateDefault(ct); if(!OptionsModel.TryCreate(target, attribute, out var result, in modelContext)) + { return null; + } return result; }) diff --git a/OptionsGenerator/Generators/OptionsModel.cs b/OptionsGenerator/Generators/OptionsModel.cs index 3e77103..1c0821e 100644 --- a/OptionsGenerator/Generators/OptionsModel.cs +++ b/OptionsGenerator/Generators/OptionsModel.cs @@ -35,7 +35,9 @@ public static Boolean TryCreate( ctx.ThrowIfCancellationRequested(); if(!OptionsAnalyzer.IsTargetProperty(member, out var p)) + { continue; + } if(!OptionsAnalyzer.IsValidTargetProperty(p)) { diff --git a/OptionsGenerator/Generators/PropertyModel.cs b/OptionsGenerator/Generators/PropertyModel.cs index b7611e8..18466ce 100644 --- a/OptionsGenerator/Generators/PropertyModel.cs +++ b/OptionsGenerator/Generators/PropertyModel.cs @@ -104,7 +104,9 @@ private static String GetAttributeAnnotation(AttributeData data, CancellationTok ct.ThrowIfCancellationRequested(); if(i != 0) + { _ = resultBuilder.Append(", "); + } var arg = ctorArguments[i]; var param = ctorParameters[i]; @@ -123,7 +125,9 @@ private static String GetAttributeAnnotation(AttributeData data, CancellationTok ct.ThrowIfCancellationRequested(); if(ctorArguments.Length > 0 || i != 0) + { _ = resultBuilder.Append(", "); + } var arg = namedArguments[i]; @@ -178,7 +182,9 @@ StringBuilder appendArray() ct.ThrowIfCancellationRequested(); if(i != 0) + { _ = resultBuilder.Append(", "); + } var element = constant.Values[i]; appendFullyQualifiedCSharpString(element); diff --git a/OptionsGenerator/Generators/Templates/Configuration/ConfigurationFullyQualifiedNameTemplate.cs b/OptionsGenerator/Generators/Templates/Configuration/ConfigurationFullyQualifiedNameTemplate.cs index 743ebe7..23bf620 100644 --- a/OptionsGenerator/Generators/Templates/Configuration/ConfigurationFullyQualifiedNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Configuration/ConfigurationFullyQualifiedNameTemplate.cs @@ -2,5 +2,7 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template("(:model.FullyQualifiedNamespacePrefix:)(:model.Templates().TypeNames.Configuration:)"), NonEquatable] internal readonly partial struct ConfigurationFullyQualifiedNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/Configuration/ConfigurationNameTemplate.cs b/OptionsGenerator/Generators/Templates/Configuration/ConfigurationNameTemplate.cs index 13202c8..f20a96c 100644 --- a/OptionsGenerator/Generators/Templates/Configuration/ConfigurationNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Configuration/ConfigurationNameTemplate.cs @@ -2,5 +2,7 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template("(:model.NormalizedName:)Configuration"), NonEquatable] internal readonly partial struct ConfigurationNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/Configuration/ConfigurationTemplate.cs b/OptionsGenerator/Generators/Templates/Configuration/ConfigurationTemplate.cs index 7015429..c7c4619 100644 --- a/OptionsGenerator/Generators/Templates/Configuration/ConfigurationTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Configuration/ConfigurationTemplate.cs @@ -2,6 +2,8 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template( """ /// diff --git a/OptionsGenerator/Generators/Templates/Immutable/ImmutableOptionsFullyQualifiedNameTemplate.cs b/OptionsGenerator/Generators/Templates/Immutable/ImmutableOptionsFullyQualifiedNameTemplate.cs index 340c0b2..4e627b1 100644 --- a/OptionsGenerator/Generators/Templates/Immutable/ImmutableOptionsFullyQualifiedNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Immutable/ImmutableOptionsFullyQualifiedNameTemplate.cs @@ -2,5 +2,7 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template("(:model.FullyQualifiedNamespacePrefix:)(:model.Templates().TypeNames.Immutable:)"), NonEquatable] internal readonly partial struct ImmutableOptionsFullyQualifiedNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/Immutable/ImmutableOptionsNameTemplate.cs b/OptionsGenerator/Generators/Templates/Immutable/ImmutableOptionsNameTemplate.cs index a8c0a20..c587c00 100644 --- a/OptionsGenerator/Generators/Templates/Immutable/ImmutableOptionsNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Immutable/ImmutableOptionsNameTemplate.cs @@ -2,5 +2,7 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template("(:model.NormalizedName:)"), NonEquatable] internal readonly partial struct ImmutableOptionsNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/Immutable/ImmutableOptionsTemplate.cs b/OptionsGenerator/Generators/Templates/Immutable/ImmutableOptionsTemplate.cs index 6c5d49b..5c9053c 100644 --- a/OptionsGenerator/Generators/Templates/Immutable/ImmutableOptionsTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Immutable/ImmutableOptionsTemplate.cs @@ -2,6 +2,8 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template( """ /// diff --git a/OptionsGenerator/Generators/Templates/Immutable/ImmutablePropertyTemplate.cs b/OptionsGenerator/Generators/Templates/Immutable/ImmutablePropertyTemplate.cs index d3bd5c5..0a3c128 100644 --- a/OptionsGenerator/Generators/Templates/Immutable/ImmutablePropertyTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Immutable/ImmutablePropertyTemplate.cs @@ -2,6 +2,8 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template( """ /// diff --git a/OptionsGenerator/Generators/Templates/Interface/InterfaceOptionsFullyQualifiedNameTemplate.cs b/OptionsGenerator/Generators/Templates/Interface/InterfaceOptionsFullyQualifiedNameTemplate.cs index 3d368d4..beada86 100644 --- a/OptionsGenerator/Generators/Templates/Interface/InterfaceOptionsFullyQualifiedNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Interface/InterfaceOptionsFullyQualifiedNameTemplate.cs @@ -2,5 +2,7 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template("(:model.FullyQualifiedNamespacePrefix:)(:model.Templates().TypeNames.Interface:)"), NonEquatable] internal readonly partial struct InterfaceOptionsFullyQualifiedNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/Interface/InterfaceOptionsNameTemplate.cs b/OptionsGenerator/Generators/Templates/Interface/InterfaceOptionsNameTemplate.cs index 6990c1a..9489cd3 100644 --- a/OptionsGenerator/Generators/Templates/Interface/InterfaceOptionsNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Interface/InterfaceOptionsNameTemplate.cs @@ -2,5 +2,7 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template("(:model.Name:)"), NonEquatable] internal readonly partial struct InterfaceOptionsNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/Interface/InterfaceOptionsTemplate.cs b/OptionsGenerator/Generators/Templates/Interface/InterfaceOptionsTemplate.cs index 710d7b6..2f2b23d 100644 --- a/OptionsGenerator/Generators/Templates/Interface/InterfaceOptionsTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Interface/InterfaceOptionsTemplate.cs @@ -2,6 +2,8 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template( """ partial interface (:model.Templates().TypeNames.Interface:) diff --git a/OptionsGenerator/Generators/Templates/MonitorPropertyTemplate.cs b/OptionsGenerator/Generators/Templates/MonitorPropertyTemplate.cs index ea187a5..9ad81b3 100644 --- a/OptionsGenerator/Generators/Templates/MonitorPropertyTemplate.cs +++ b/OptionsGenerator/Generators/Templates/MonitorPropertyTemplate.cs @@ -2,6 +2,8 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template( """ (:new PropertyAnnotationsTemplate(model):)public (:model.Type:) (:model.Name:) => monitor.CurrentValue.(:model.Name:); diff --git a/OptionsGenerator/Generators/Templates/Mutable/MutableDefaultExpressionTemplate.cs b/OptionsGenerator/Generators/Templates/Mutable/MutableDefaultExpressionTemplate.cs index 5cb993b..7ec2806 100644 --- a/OptionsGenerator/Generators/Templates/Mutable/MutableDefaultExpressionTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Mutable/MutableDefaultExpressionTemplate.cs @@ -2,6 +2,8 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template( """ {: diff --git a/OptionsGenerator/Generators/Templates/Mutable/MutableOptionsFullyQualifiedNameTemplate.cs b/OptionsGenerator/Generators/Templates/Mutable/MutableOptionsFullyQualifiedNameTemplate.cs index e2394c9..d4d1efe 100644 --- a/OptionsGenerator/Generators/Templates/Mutable/MutableOptionsFullyQualifiedNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Mutable/MutableOptionsFullyQualifiedNameTemplate.cs @@ -2,5 +2,7 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template("(:model.FullyQualifiedNamespacePrefix:)(:model.Templates().TypeNames.Mutable:)"), NonEquatable] internal readonly partial struct MutableOptionsFullyQualifiedNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/Mutable/MutableOptionsNameTemplate.cs b/OptionsGenerator/Generators/Templates/Mutable/MutableOptionsNameTemplate.cs index bdcac95..487b67d 100644 --- a/OptionsGenerator/Generators/Templates/Mutable/MutableOptionsNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Mutable/MutableOptionsNameTemplate.cs @@ -2,5 +2,7 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template("Mutable(:model.NormalizedName:)"), NonEquatable] internal readonly partial struct MutableOptionsNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/Mutable/MutableOptionsTemplate.cs b/OptionsGenerator/Generators/Templates/Mutable/MutableOptionsTemplate.cs index 3fc53d6..20533b2 100644 --- a/OptionsGenerator/Generators/Templates/Mutable/MutableOptionsTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Mutable/MutableOptionsTemplate.cs @@ -2,6 +2,8 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template( """ /// diff --git a/OptionsGenerator/Generators/Templates/Mutable/MutablePropertyTemplate.cs b/OptionsGenerator/Generators/Templates/Mutable/MutablePropertyTemplate.cs index 48aa826..64e631a 100644 --- a/OptionsGenerator/Generators/Templates/Mutable/MutablePropertyTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Mutable/MutablePropertyTemplate.cs @@ -2,6 +2,8 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template( """ /// diff --git a/OptionsGenerator/Generators/Templates/Passthrough/DefaultOptionsFullyQualifiedNameTemplate.cs b/OptionsGenerator/Generators/Templates/Passthrough/DefaultOptionsFullyQualifiedNameTemplate.cs index 2a60966..bbc7452 100644 --- a/OptionsGenerator/Generators/Templates/Passthrough/DefaultOptionsFullyQualifiedNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Passthrough/DefaultOptionsFullyQualifiedNameTemplate.cs @@ -2,5 +2,7 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template("(:model.FullyQualifiedNamespacePrefix:)(:model.Templates().TypeNames.Default:)"), NonEquatable] internal readonly partial struct DefaultOptionsFullyQualifiedNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/Passthrough/DefaultOptionsNameTemplate.cs b/OptionsGenerator/Generators/Templates/Passthrough/DefaultOptionsNameTemplate.cs index 6491138..1a834c1 100644 --- a/OptionsGenerator/Generators/Templates/Passthrough/DefaultOptionsNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Passthrough/DefaultOptionsNameTemplate.cs @@ -2,5 +2,7 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template("Default(:model.NormalizedName:)"), NonEquatable] internal readonly partial struct DefaultOptionsNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/Passthrough/MonitorOptionsFullyQualifiedNameTemplate.cs b/OptionsGenerator/Generators/Templates/Passthrough/MonitorOptionsFullyQualifiedNameTemplate.cs index 474b690..657ffbf 100644 --- a/OptionsGenerator/Generators/Templates/Passthrough/MonitorOptionsFullyQualifiedNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Passthrough/MonitorOptionsFullyQualifiedNameTemplate.cs @@ -2,5 +2,7 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template("(:model.FullyQualifiedNamespacePrefix:)(:model.Templates().TypeNames.Monitor:)"), NonEquatable] internal readonly partial struct MonitorOptionsFullyQualifiedNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/Passthrough/MonitorOptionsNameTemplate.cs b/OptionsGenerator/Generators/Templates/Passthrough/MonitorOptionsNameTemplate.cs index 986a272..c7186b6 100644 --- a/OptionsGenerator/Generators/Templates/Passthrough/MonitorOptionsNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Passthrough/MonitorOptionsNameTemplate.cs @@ -2,5 +2,7 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template("Monitor(:model.NormalizedName:)"), NonEquatable] internal readonly partial struct MonitorOptionsNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/Passthrough/PassthroughOptionsImplementationTemplate.cs b/OptionsGenerator/Generators/Templates/Passthrough/PassthroughOptionsImplementationTemplate.cs index 59329c5..4bf3cd7 100644 --- a/OptionsGenerator/Generators/Templates/Passthrough/PassthroughOptionsImplementationTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Passthrough/PassthroughOptionsImplementationTemplate.cs @@ -2,6 +2,8 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + using RhoMicro.CodeAnalysis.Library.Text.Templating; [Template( diff --git a/OptionsGenerator/Generators/Templates/Passthrough/PassthroughPropertyTemplate.cs b/OptionsGenerator/Generators/Templates/Passthrough/PassthroughPropertyTemplate.cs index aa98362..17c3b74 100644 --- a/OptionsGenerator/Generators/Templates/Passthrough/PassthroughPropertyTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Passthrough/PassthroughPropertyTemplate.cs @@ -2,6 +2,8 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template( """ /// diff --git a/OptionsGenerator/Generators/Templates/Passthrough/SnapshotOptionsFullyQualifiedNameTemplate.cs b/OptionsGenerator/Generators/Templates/Passthrough/SnapshotOptionsFullyQualifiedNameTemplate.cs index 7849dbc..cd26d3a 100644 --- a/OptionsGenerator/Generators/Templates/Passthrough/SnapshotOptionsFullyQualifiedNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Passthrough/SnapshotOptionsFullyQualifiedNameTemplate.cs @@ -2,5 +2,7 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template("(:model.FullyQualifiedNamespacePrefix:)(:model.Templates().TypeNames.Snapshot:)"), NonEquatable] internal readonly partial struct SnapshotOptionsFullyQualifiedNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/Passthrough/SnapshotOptionsNameTemplate.cs b/OptionsGenerator/Generators/Templates/Passthrough/SnapshotOptionsNameTemplate.cs index 1058489..456a37e 100644 --- a/OptionsGenerator/Generators/Templates/Passthrough/SnapshotOptionsNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Passthrough/SnapshotOptionsNameTemplate.cs @@ -2,5 +2,7 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template("Snapshot(:model.NormalizedName:)"), NonEquatable] internal readonly partial struct SnapshotOptionsNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/PropertyAnnotationsTemplate.cs b/OptionsGenerator/Generators/Templates/PropertyAnnotationsTemplate.cs index aa77fb5..f883f04 100644 --- a/OptionsGenerator/Generators/Templates/PropertyAnnotationsTemplate.cs +++ b/OptionsGenerator/Generators/Templates/PropertyAnnotationsTemplate.cs @@ -2,6 +2,8 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template( """ {: diff --git a/OptionsGenerator/Generators/Templates/RegistrationStrategy/RegistrationStrategyFullyQualifiedNameTemplate.cs b/OptionsGenerator/Generators/Templates/RegistrationStrategy/RegistrationStrategyFullyQualifiedNameTemplate.cs index 39a6d01..d7f2951 100644 --- a/OptionsGenerator/Generators/Templates/RegistrationStrategy/RegistrationStrategyFullyQualifiedNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/RegistrationStrategy/RegistrationStrategyFullyQualifiedNameTemplate.cs @@ -2,5 +2,7 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template("(:model.FullyQualifiedNamespacePrefix:)(:model.Templates().TypeNames.RegistrationStrategy:)"), NonEquatable] internal readonly partial struct RegistrationStrategyFullyQualifiedNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/RegistrationStrategy/RegistrationStrategyNameTemplate.cs b/OptionsGenerator/Generators/Templates/RegistrationStrategy/RegistrationStrategyNameTemplate.cs index d8b2a5a..0922062 100644 --- a/OptionsGenerator/Generators/Templates/RegistrationStrategy/RegistrationStrategyNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/RegistrationStrategy/RegistrationStrategyNameTemplate.cs @@ -2,5 +2,7 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template("(:model.NormalizedName:)RegistrationStrategy"), NonEquatable] internal readonly partial struct RegistrationStrategyNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/RegistrationStrategy/RegistrationStrategyTemplate.cs b/OptionsGenerator/Generators/Templates/RegistrationStrategy/RegistrationStrategyTemplate.cs index ae2b8c0..8e3f9de 100644 --- a/OptionsGenerator/Generators/Templates/RegistrationStrategy/RegistrationStrategyTemplate.cs +++ b/OptionsGenerator/Generators/Templates/RegistrationStrategy/RegistrationStrategyTemplate.cs @@ -2,6 +2,8 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template( """ /// diff --git a/OptionsGenerator/Generators/Templates/RootNamespaceTemplate.cs b/OptionsGenerator/Generators/Templates/RootNamespaceTemplate.cs index e1b4d07..00a16d5 100644 --- a/OptionsGenerator/Generators/Templates/RootNamespaceTemplate.cs +++ b/OptionsGenerator/Generators/Templates/RootNamespaceTemplate.cs @@ -2,6 +2,8 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template( """ {: diff --git a/OptionsGenerator/Generators/Templates/RootTemplate.cs b/OptionsGenerator/Generators/Templates/RootTemplate.cs index c5df67b..01c7dba 100644 --- a/OptionsGenerator/Generators/Templates/RootTemplate.cs +++ b/OptionsGenerator/Generators/Templates/RootTemplate.cs @@ -2,6 +2,8 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template( """ (:new RootNamespaceTemplate(model):) diff --git a/OptionsGenerator/Generators/Templates/ServiceCollectionExtensionsTemplate.cs b/OptionsGenerator/Generators/Templates/ServiceCollectionExtensionsTemplate.cs index e576211..8bd8901 100644 --- a/OptionsGenerator/Generators/Templates/ServiceCollectionExtensionsTemplate.cs +++ b/OptionsGenerator/Generators/Templates/ServiceCollectionExtensionsTemplate.cs @@ -2,6 +2,8 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; +#pragma warning disable CS9113 + [Template( """ /// diff --git a/OptionsGenerator/OptionsGenerator.csproj b/OptionsGenerator/OptionsGenerator.csproj index 82e5f12..aebd1e6 100644 --- a/OptionsGenerator/OptionsGenerator.csproj +++ b/OptionsGenerator/OptionsGenerator.csproj @@ -41,4 +41,9 @@ + + + + + diff --git a/Project1/Program.cs b/Project1/Program.cs deleted file mode 100644 index 8a695a0..0000000 --- a/Project1/Program.cs +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -using System.Diagnostics.Metrics; -using System.Globalization; -using System.Text; -using System.Text.RegularExpressions; - -using DiffPlex; -using DiffPlex.Chunkers; -using DiffPlex.DiffBuilder; -using DiffPlex.DiffBuilder.Model; - -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; - -using Microsoft.CodeAnalysis.CSharp.Syntax; - -using RhoMicro.CodeAnalysis; -using RhoMicro.CodeAnalysis.Library.Text.Templating; - -internal partial class Program -{ - private static void Main(String[] _) - { - Console.WriteLine( - new Letter( - new Header("Greetings,"), - new Footer("Sincerely,"), - "TebBeCo")); - } - - [Template("(:Salutation:) (:Name:).", BodyParameterName = "Name")] - readonly partial record struct Header(String Salutation); - - [Template("(:Salutation:) (:Name:).", BodyParameterName = "Name")] - readonly partial record struct Footer(String Salutation); - - [Template( - """ - {: - (:Header:)<:(:Name:):> - (:" Why is your name ":)(:Name:)(:"? ":) - static (:Footer:)<:user:> - :} - - get some rizz like fr - """)] - partial record Letter(Header Header, Footer Footer, String Name); -} diff --git a/Project1/Project1.csproj b/Project1/Project1.csproj deleted file mode 100644 index 3c38e19..0000000 --- a/Project1/Project1.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - Exe - net9.0 - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Project1/README.md b/Project1/README.md deleted file mode 100644 index f1a9c27..0000000 --- a/Project1/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Project1 - -- \ No newline at end of file diff --git a/RhoMicro.CodeAnalysis.slnx b/RhoMicro.CodeAnalysis.slnx index f8eb793..538ea28 100644 --- a/RhoMicro.CodeAnalysis.slnx +++ b/RhoMicro.CodeAnalysis.slnx @@ -1,44 +1,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -47,4 +7,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TestApp/Program.cs b/TestApp/Program.cs deleted file mode 100644 index d0a51d6..0000000 --- a/TestApp/Program.cs +++ /dev/null @@ -1,216 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -namespace VisitorExample; - -using System.Globalization; -using System.Linq.Expressions; -using System.Reflection; - -using RhoMicro.CodeAnalysis; - - -internal class Program -{ - private static void Main() - { - var program = new StatementList() - { - Statements = - [ - new ExpressionStatement() - { - Expression = new AssignmentExpression() - { - Name = "foo", - Expression = new AdditionExpression() - { - Lhs = new LiteralExpression() { Value = 6 }, - Rhs = new LiteralExpression() { Value = 5 } - } - } - }, - new ExpressionStatement() - { - Expression = new AssignmentExpression(){ - Name = "bar", - Expression =new SubtractionExpression(){ - Lhs = new DivisionExpression() - { - Lhs = new VariableExpression(){Name = "foo"}, - Rhs = new MultiplicationExpression(){ - Lhs = new LiteralExpression(){ Value = 6 }, - Rhs = new LiteralExpression(){ Value = 0.5 } - } - }, - Rhs = new DivisionExpression(){ - Lhs= new LiteralExpression(){ Value = 2}, - Rhs = new LiteralExpression(){Value = 3} - } - } - } - } - ] - }; - - program.Accept(Printer.Instance); - - var result = program.Accept(new Interpreter()); - - Console.WriteLine(result); - - program.Accept(Printer.Instance); - - result = program.Accept(new Interpreter()); - - Console.WriteLine(result); - } -} - -internal sealed class Printer : SyntaxNodeVisitor -{ - private Printer() { } - - public static Printer Instance { get; } = new(); - - public override void OnBeforeVisitAdditionExpression(AdditionExpression target, CancellationToken cancellationToken = default) - => Console.Write('('); - public override void TraverseAdditionExpression(AdditionExpression target, CancellationToken cancellationToken = default) - { - target.Lhs.Accept(this, cancellationToken); - Console.Write(" + "); - target.Rhs.Accept(this, cancellationToken); - } - public override void OnAfterVisitAdditionExpression(AdditionExpression target, CancellationToken cancellationToken = default) - => Console.Write(')'); - - public override void OnBeforeVisitSubtractionExpression(SubtractionExpression target, CancellationToken cancellationToken = default) - => Console.Write('('); - public override void TraverseSubtractionExpression(SubtractionExpression target, CancellationToken cancellationToken = default) - { - target.Lhs.Accept(this, cancellationToken); - Console.Write(" - "); - target.Rhs.Accept(this, cancellationToken); - } - public override void OnAfterVisitSubtractionExpression(SubtractionExpression target, CancellationToken cancellationToken = default) - => Console.Write(')'); - - public override void OnBeforeVisitMultiplicationExpression(MultiplicationExpression target, CancellationToken cancellationToken = default) - => Console.Write('('); - public override void TraverseMultiplicationExpression(MultiplicationExpression target, CancellationToken cancellationToken = default) - { - target.Lhs.Accept(this, cancellationToken); - Console.Write(" * "); - target.Rhs.Accept(this, cancellationToken); - } - public override void OnAfterVisitMultiplicationExpression(MultiplicationExpression target, CancellationToken cancellationToken = default) - => Console.Write(')'); - - public override void OnBeforeVisitDivisionExpression(DivisionExpression target, CancellationToken cancellationToken = default) - => Console.Write('('); - public override void TraverseDivisionExpression(DivisionExpression target, CancellationToken cancellationToken = default) - { - target.Lhs.Accept(this, cancellationToken); - Console.Write(" / "); - target.Rhs.Accept(this, cancellationToken); - } - public override void OnAfterVisitDivisionExpression(DivisionExpression target, CancellationToken cancellationToken = default) - => Console.Write(')'); - - public override void VisitLiteralExpression(LiteralExpression target, CancellationToken cancellationToken = default) - => Console.Write(target.Value.ToString(CultureInfo.InvariantCulture)); - public override void OnAfterVisitExpressionStatement(ExpressionStatement target, CancellationToken cancellationToken = default) - => Console.WriteLine(); - public override void VisitAssignmentExpression(AssignmentExpression target, CancellationToken cancellationToken = default) - { - Console.Write($"{target.Name} = "); - target.Expression.Accept(this, cancellationToken); - } - public override void VisitVariableExpression(VariableExpression target, CancellationToken cancellationToken = default) - => Console.Write(target.Name); -} - -internal sealed class Interpreter : SyntaxNodeVisitor -{ - protected override Double GetDefault() => 0; - - private readonly Dictionary _variables = []; - private Double _lastStatementValue; - - public override Double VisitLiteralExpression(LiteralExpression target, CancellationToken cancellationToken = default) - => target.Value; - public override Double VisitAdditionExpression(AdditionExpression target, CancellationToken cancellationToken = default) - => target.Lhs.Accept(this, cancellationToken) + target.Rhs.Accept(this, cancellationToken); - public override Double VisitSubtractionExpression(SubtractionExpression target, CancellationToken cancellationToken = default) - => target.Lhs.Accept(this, cancellationToken) - target.Rhs.Accept(this, cancellationToken); - public override Double VisitMultiplicationExpression(MultiplicationExpression target, CancellationToken cancellationToken = default) - => target.Lhs.Accept(this, cancellationToken) * target.Rhs.Accept(this, cancellationToken); - public override Double VisitDivisionExpression(DivisionExpression target, CancellationToken cancellationToken = default) - => target.Lhs.Accept(this, cancellationToken) / target.Rhs.Accept(this, cancellationToken); - public override Double VisitAssignmentExpression(AssignmentExpression target, CancellationToken cancellationToken = default) - => _variables[target.Name] = target.Expression.Accept(this, cancellationToken); - public override Double VisitVariableExpression(VariableExpression target, CancellationToken cancellationToken = default) - => _variables.GetValueOrDefault(target.Name); - public override Double VisitExpressionStatement(ExpressionStatement target, CancellationToken cancellationToken = default) - => _lastStatementValue = target.Expression.Accept(this, cancellationToken); - public override Double VisitStatementList(StatementList target, CancellationToken cancellationToken = default) - { - foreach (var statement in target.Statements) - _lastStatementValue = statement.Accept(this, cancellationToken); - - return _lastStatementValue; - } -} - -internal sealed partial class AssignmentExpression : Expression -{ - public required String Name { get; init; } - public required Expression Expression { get; init; } -} - -internal sealed partial class ExpressionStatement : Statement -{ - public required Expression Expression { get; init; } -} - -internal sealed partial class VariableExpression : Expression -{ - public required String Name { get; init; } -} - -internal sealed partial class LiteralExpression : Expression -{ - public required Double Value { get; init; } -} - -internal sealed partial class AdditionExpression : BinaryExpression; -internal sealed partial class SubtractionExpression : BinaryExpression; -internal sealed partial class MultiplicationExpression : BinaryExpression; -internal sealed partial class DivisionExpression : BinaryExpression; - -internal abstract partial class BinaryExpression : Expression -{ - public required Expression Lhs { get; init; } - public required Expression Rhs { get; init; } -} - -internal sealed partial class StatementList : SyntaxNode -{ - public required IEnumerable Statements { get; init; } -} - -internal abstract partial class Expression : SyntaxNode; - -internal abstract partial class Statement : SyntaxNode; - -[GenerateVisitor< - AdditionExpression, - SubtractionExpression, - MultiplicationExpression, - DivisionExpression, - LiteralExpression, - AssignmentExpression, - VariableExpression, - StatementList>, - GenerateVisitor< - ExpressionStatement>] -internal abstract partial class SyntaxNode; diff --git a/TestApp/TestApp.csproj b/TestApp/TestApp.csproj deleted file mode 100644 index 668e625..0000000 --- a/TestApp/TestApp.csproj +++ /dev/null @@ -1,38 +0,0 @@ - - - - Exe - net9.0 - - - - $(MSBuildProjectName) - $(SolutionName).$(MSBuildProjectName) - - $(MSBuildProjectName.Replace(" ", "_")) - $(SolutionName).$(MSBuildProjectName.Replace(".", "_")) - - $(WarningsAsErrors);NU1605;1591;1573;1712;nullable - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/TestApp/appsettings.json b/TestApp/appsettings.json deleted file mode 100644 index 8306e68..0000000 --- a/TestApp/appsettings.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "./appsettings.schema.json", - "Settings": { - "ArrayProp": [ - { - "ListProp": [ - { - "IntProp": 0 - } - ] - } - ] - } -} \ No newline at end of file diff --git a/TestApp/appsettings.schema.json b/TestApp/appsettings.schema.json deleted file mode 100644 index bd71716..0000000 --- a/TestApp/appsettings.schema.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "$id": "appsettings_schema", - "properties": { - "Settings": { - "$ref": "./schemata/Settings.json" - } - }, - "additionalProperties": true -} \ No newline at end of file diff --git a/UtilityGenerators.Benchmarks/BlockTerminatorsBenchmark.cs b/UtilityGenerators.Benchmarks/BlockTerminatorsBenchmark.cs index 3eadd57..50e03bd 100644 --- a/UtilityGenerators.Benchmarks/BlockTerminatorsBenchmark.cs +++ b/UtilityGenerators.Benchmarks/BlockTerminatorsBenchmark.cs @@ -55,7 +55,9 @@ public static Boolean LinearSearch(Char c) for(var i = 0; i < _orderedChars.Length; i++) { if(_orderedChars[i] == c) + { return true; + } } return false; @@ -63,22 +65,34 @@ public static Boolean LinearSearch(Char c) public static Boolean LinearSearchUnrolled(Char c) { if(c == '(') + { return true; + } if(c == ')') + { return true; + } if(c == '<') + { return true; + } if(c == '>') + { return true; + } if(c == '{') + { return true; + } if(c == '}') + { return true; + } return false; } @@ -87,22 +101,34 @@ public static Boolean LinearSearchUnrolledConditions(Char c) var result = false; if(c == '(') + { result = true; + } if(c == ')') + { result = true; + } if(c == '<') + { result = true; + } if(c == '>') + { result = true; + } if(c == '{') + { result = true; + } if(c == '}') + { result = true; + } return result; } @@ -196,7 +222,10 @@ public Int32 IsBlockTerminator() { var result = 0; foreach(var c in _characters) + { result += BlockTerminators.IsBlockTerminator(c) ? 1 : 0; + } + return result; } [Benchmark] @@ -204,7 +233,10 @@ public Int32 BinarySearch() { var result = 0; foreach(var c in _characters) + { result += BlockTerminators.BinarySearch(c) ? 1 : 0; + } + return result; } [Benchmark] @@ -212,7 +244,10 @@ public Int32 LinearSearch() { var result = 0; foreach(var c in _characters) + { result += BlockTerminators.LinearSearch(c) ? 1 : 0; + } + return result; } [Benchmark] @@ -220,7 +255,10 @@ public Int32 LinearSearchUnrolled() { var result = 0; foreach(var c in _characters) + { result += BlockTerminators.LinearSearchUnrolled(c) ? 1 : 0; + } + return result; } [Benchmark] @@ -228,7 +266,10 @@ public Int32 LinearSearchUnrolledConditions() { var result = 0; foreach(var c in _characters) + { result += BlockTerminators.LinearSearchUnrolledConditions(c) ? 1 : 0; + } + return result; } [Benchmark] @@ -236,7 +277,10 @@ public Int32 LinearSearchUnrolledShortCircuiting() { var result = 0; foreach(var c in _characters) + { result += BlockTerminators.LinearSearchUnrolledShortCircuiting(c) ? 1 : 0; + } + return result; } [Benchmark] @@ -244,7 +288,10 @@ public Int32 LinearSearchUnrolledPatternMatching() { var result = 0; foreach(var c in _characters) + { result += BlockTerminators.LinearSearchUnrolledPatternMatching(c) ? 1 : 0; + } + return result; } [Benchmark(Baseline = true)] @@ -252,7 +299,10 @@ public Int32 LinearSearchUnrolledTernary() { var result = 0; foreach(var c in _characters) + { result += BlockTerminators.LinearSearchUnrolledTernary(c) ? 1 : 0; + } + return result; } [Benchmark] @@ -260,7 +310,10 @@ public Int32 LinearSearchUnrolledTernary2() { var result = 0; foreach(var c in _characters) + { result += BlockTerminators.LinearSearchUnrolledTernary2(c) ? 1 : 0; + } + return result; } [Benchmark] @@ -268,7 +321,10 @@ public Int32 LinearSearchUnrolledTernary3() { var result = 0; foreach(var c in _characters) + { result += BlockTerminators.LinearSearchUnrolledTernary3(c) ? 1 : 0; + } + return result; } } diff --git a/UtilityGenerators.Benchmarks/ParserMatchBenchmark.cs b/UtilityGenerators.Benchmarks/ParserMatchBenchmark.cs index d388644..87ba1e1 100644 --- a/UtilityGenerators.Benchmarks/ParserMatchBenchmark.cs +++ b/UtilityGenerators.Benchmarks/ParserMatchBenchmark.cs @@ -22,7 +22,7 @@ public enum TokenType [Orderer(SummaryOrderPolicy.FastestToSlowest)] public class ParserMatchBenchmark { - private TokenType[] _types; + private TokenType[] _types = []; [GlobalSetup] public void GlobalSetup() => _types = [A, B, C, D]; @@ -35,19 +35,39 @@ public Int32 MatchAnyNaive() foreach(var peek in _types) { if(MatchAnyNaive(peek, _types[0])) + { result++; + } + if(MatchAnyNaive(peek, _types[1])) + { result++; + } + if(MatchAnyNaive(peek, _types[2])) + { result++; + } + if(MatchAnyNaive(peek, _types[3])) + { result++; + } + if(MatchAnyNaive(peek, _types[0], _types[1])) + { result++; + } + if(MatchAnyNaive(peek, _types[2], _types[3])) + { result++; + } + if(MatchAnyNaive(peek, _types[0], _types[1], _types[2], _types[3])) + { result++; + } } return result; @@ -60,19 +80,39 @@ public Int32 MatchAnyTernary() foreach(var peek in _types) { if(MatchAnyTernary(peek, _types[0])) + { result++; + } + if(MatchAnyTernary(peek, _types[1])) + { result++; + } + if(MatchAnyTernary(peek, _types[2])) + { result++; + } + if(MatchAnyTernary(peek, _types[3])) + { result++; + } + if(MatchAnyTernary(peek, _types[0], _types[1])) + { result++; + } + if(MatchAnyTernary(peek, _types[2], _types[3])) + { result++; + } + if(MatchAnyTernary(peek, _types[0], _types[1], _types[2], _types[3])) + { result++; + } } return result; @@ -83,7 +123,9 @@ private Boolean MatchAnyNaive(TokenType peek, params ReadOnlySpan typ foreach(var t in types) { if(t == peek) + { return true; + } } return false; diff --git a/UtilityGenerators.Benchmarks/TemplateBenchmark.cs b/UtilityGenerators.Benchmarks/TemplateBenchmark.cs index 9949bc8..c6d7634 100644 --- a/UtilityGenerators.Benchmarks/TemplateBenchmark.cs +++ b/UtilityGenerators.Benchmarks/TemplateBenchmark.cs @@ -67,7 +67,9 @@ public void AppendTo(StringBuilder builder) { builder.Append("Surfers gameplay at the bottom Sussy imposter Pibby glitch in real life No").Append('\n'); for(var i = 0; i < 10; i++) + { builder.Append(Parameter); + } } } private readonly record struct StringBuilderTextRender(String Parameter) diff --git a/UtilityGenerators.Benchmarks/UtilityGenerators.Benchmarks.csproj b/UtilityGenerators.Benchmarks/UtilityGenerators.Benchmarks.csproj index 48fd120..d2bd626 100644 --- a/UtilityGenerators.Benchmarks/UtilityGenerators.Benchmarks.csproj +++ b/UtilityGenerators.Benchmarks/UtilityGenerators.Benchmarks.csproj @@ -19,4 +19,10 @@ + + + GlobalUsings.cs + + + diff --git a/UtilityGenerators.Dev/UtilityGenerators.Dev.csproj b/UtilityGenerators.Dev/UtilityGenerators.Dev.csproj index ce7bf6e..412d87f 100644 --- a/UtilityGenerators.Dev/UtilityGenerators.Dev.csproj +++ b/UtilityGenerators.Dev/UtilityGenerators.Dev.csproj @@ -7,6 +7,7 @@ true true ..\dist\dev + $(NoWarn);NU5128 @@ -117,7 +118,9 @@ - + + Templating\TemplateAttribute.Model.cs + @@ -132,8 +135,4 @@ - - - - diff --git a/UtilityGenerators.Tests.E2E/TestAttribute.cs b/UtilityGenerators.Tests.E2E/TestAttribute.cs index d4856c2..57554ee 100644 --- a/UtilityGenerators.Tests.E2E/TestAttribute.cs +++ b/UtilityGenerators.Tests.E2E/TestAttribute.cs @@ -3,7 +3,7 @@ namespace RhoMicro.CodeAnalysis.Tests.E2E; [GenerateFactory] -partial class TestAttribute +sealed partial class TestAttribute { public Type[] TypeArrayProperty { get; set; } = []; public Type?[] NullableTypeArrayProperty { get; set; } = []; diff --git a/UtilityGenerators.Tests.E2E/UtilityGenerators.Tests.E2E.csproj b/UtilityGenerators.Tests.E2E/UtilityGenerators.Tests.E2E.csproj index f669d64..3de922b 100644 --- a/UtilityGenerators.Tests.E2E/UtilityGenerators.Tests.E2E.csproj +++ b/UtilityGenerators.Tests.E2E/UtilityGenerators.Tests.E2E.csproj @@ -25,4 +25,10 @@ + + + GlobalUsings.cs + + + diff --git a/UtilityGenerators.Tests/Library/Collections/EquatableDictionaryTests.cs b/UtilityGenerators.Tests/Library/Collections/EquatableDictionaryTests.cs index 7ee79d7..fbdf87f 100644 --- a/UtilityGenerators.Tests/Library/Collections/EquatableDictionaryTests.cs +++ b/UtilityGenerators.Tests/Library/Collections/EquatableDictionaryTests.cs @@ -21,7 +21,7 @@ public class EquatableDictionaryTests public void ValueSemantics_VerifyEquality(Dictionary values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var dict1 = factory.CreateDictionary(); var dict2 = factory.CreateDictionary(); @@ -42,7 +42,7 @@ public void ValueSemantics_VerifyEquality(Dictionary values) public void ValueSemantics_VerifyInequality(Int32[] initialKeys, String[] initialValues, Int32[] modifiedKeys, String[] modifiedValues) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var dict1 = factory.CreateDictionary(); var dict2 = factory.CreateDictionary(); @@ -65,7 +65,7 @@ public void ValueSemantics_VerifyInequality(Int32[] initialKeys, String[] initia public void Immutability_AddThrowsAfterSetImmutable(Dictionary values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var dict = factory.CreateDictionary(); foreach(var kvp in values) @@ -85,7 +85,7 @@ public void Immutability_AddThrowsAfterSetImmutable(Dictionary va public void Immutability_ClearThrowsAfterSetImmutable(Dictionary values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var dict = factory.CreateDictionary(); foreach(var kvp in values) @@ -105,7 +105,7 @@ public void Immutability_ClearThrowsAfterSetImmutable(Dictionary public void Immutability_RemoveThrowsAfterSetImmutable(Dictionary values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var dict = factory.CreateDictionary(); foreach(var kvp in values) @@ -125,7 +125,7 @@ public void Immutability_RemoveThrowsAfterSetImmutable(Dictionary public void Mutability_AddSucceedsBeforeSetImmutable(Dictionary values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var dict = factory.CreateDictionary(); // Act @@ -147,7 +147,7 @@ public void Mutability_AddSucceedsBeforeSetImmutable(Dictionary v public void Mutability_ClearSucceedsBeforeSetImmutable(Dictionary values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var dict = factory.CreateDictionary(); foreach(var kvp in values) @@ -167,7 +167,7 @@ public void Mutability_ClearSucceedsBeforeSetImmutable(Dictionary public void Mutability_RemoveSucceedsBeforeSetImmutable(Dictionary values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var dict = factory.CreateDictionary(); foreach(var kvp in values) diff --git a/UtilityGenerators.Tests/Library/Collections/EquatableListTests.cs b/UtilityGenerators.Tests/Library/Collections/EquatableListTests.cs index a326e1f..5497fac 100644 --- a/UtilityGenerators.Tests/Library/Collections/EquatableListTests.cs +++ b/UtilityGenerators.Tests/Library/Collections/EquatableListTests.cs @@ -22,7 +22,7 @@ public class EquatableListTests public void ValueSemantics_VerifyEquality(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var list1 = factory.CreateList(); var list2 = factory.CreateList(); @@ -45,7 +45,7 @@ public void ValueSemantics_VerifyEquality(Int32[] values) public void ValueSemantics_VerifyInequality(Int32[] initialValues, Int32[] modifiedValues) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var list1 = factory.CreateList(); var list2 = factory.CreateList(); @@ -68,7 +68,7 @@ public void ValueSemantics_VerifyInequality(Int32[] initialValues, Int32[] modif public void Immutability_AddThrowsAfterSetImmutable(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateList(); foreach(var value in values) @@ -88,7 +88,7 @@ public void Immutability_AddThrowsAfterSetImmutable(Int32[] values) public void Immutability_RemoveThrowsAfterSetImmutable(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateList(); foreach(var value in values) @@ -108,7 +108,7 @@ public void Immutability_RemoveThrowsAfterSetImmutable(Int32[] values) public void Immutability_ClearThrowsAfterSetImmutable(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateList(); foreach(var value in values) @@ -127,7 +127,7 @@ public void Immutability_ClearThrowsAfterSetImmutable(Int32[] values) public void Immutability_InsertThrowsAfterSetImmutable(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateList(); foreach(var value in values) @@ -147,10 +147,12 @@ public void Immutability_InsertThrowsAfterSetImmutable(Int32[] values) public void Immutability_RemoveAtThrowsAfterSetImmutable(Int32[] values) { if(values.Length == 0) + { return; + } // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateList(); foreach(var value in values) @@ -170,7 +172,7 @@ public void Immutability_RemoveAtThrowsAfterSetImmutable(Int32[] values) public void Mutability_AddSucceedsBeforeSetImmutable(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateList(); // Act @@ -191,10 +193,12 @@ public void Mutability_AddSucceedsBeforeSetImmutable(Int32[] values) public void Mutability_RemoveSucceedsBeforeSetImmutable(Int32[] values) { if(values.Length == 0) + { return; + } // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateList(); foreach(var value in values) @@ -214,7 +218,7 @@ public void Mutability_RemoveSucceedsBeforeSetImmutable(Int32[] values) public void Mutability_ClearSucceedsBeforeSetImmutable(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateList(); foreach(var value in values) @@ -233,7 +237,7 @@ public void Mutability_ClearSucceedsBeforeSetImmutable(Int32[] values) public void Mutability_InsertSucceedsBeforeSetImmutable(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateList(); foreach(var value in values) @@ -253,10 +257,12 @@ public void Mutability_InsertSucceedsBeforeSetImmutable(Int32[] values) public void Mutability_RemoveAtSucceedsBeforeSetImmutable(Int32[] values) { if(values.Length == 0) + { return; + } // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateList(); foreach(var value in values) diff --git a/UtilityGenerators.Tests/Library/Collections/EquatableSetTests.cs b/UtilityGenerators.Tests/Library/Collections/EquatableSetTests.cs index 6af59dc..e8cf7e3 100644 --- a/UtilityGenerators.Tests/Library/Collections/EquatableSetTests.cs +++ b/UtilityGenerators.Tests/Library/Collections/EquatableSetTests.cs @@ -22,7 +22,7 @@ public class EquatableSetTests public void ValueSemantics_VerifyEquality(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var set1 = factory.CreateSet(); var set2 = factory.CreateSet(); @@ -45,7 +45,7 @@ public void ValueSemantics_VerifyEquality(Int32[] values) public void ValueSemantics_VerifyInequality(Int32[] initialValues, Int32[] modifiedValues) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var set1 = factory.CreateSet(); var set2 = factory.CreateSet(); @@ -68,7 +68,7 @@ public void ValueSemantics_VerifyInequality(Int32[] initialValues, Int32[] modif public void Immutability_AddThrowsAfterSetImmutable(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var set = factory.CreateSet(); foreach(var value in values) @@ -88,7 +88,7 @@ public void Immutability_AddThrowsAfterSetImmutable(Int32[] values) public void Immutability_ClearThrowsAfterSetImmutable(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var set = factory.CreateSet(); foreach(var value in values) @@ -108,7 +108,7 @@ public void Immutability_ClearThrowsAfterSetImmutable(Int32[] values) public void Immutability_ExceptWithThrowsAfterSetImmutable(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var set = factory.CreateSet(); foreach(var value in values) @@ -128,7 +128,7 @@ public void Immutability_ExceptWithThrowsAfterSetImmutable(Int32[] values) public void Immutability_IntersectWithThrowsAfterSetImmutable(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var set = factory.CreateSet(); foreach(var value in values) @@ -148,7 +148,7 @@ public void Immutability_IntersectWithThrowsAfterSetImmutable(Int32[] values) public void Immutability_SymmetricExceptWithThrowsAfterSetImmutable(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var set = factory.CreateSet(); foreach(var value in values) @@ -168,7 +168,7 @@ public void Immutability_SymmetricExceptWithThrowsAfterSetImmutable(Int32[] valu public void Immutability_UnionWithThrowsAfterSetImmutable(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var set = factory.CreateSet(); foreach(var value in values) @@ -188,7 +188,7 @@ public void Immutability_UnionWithThrowsAfterSetImmutable(Int32[] values) public void Mutability_AddSucceedsBeforeSetImmutable(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var set = factory.CreateSet(); // Act @@ -209,7 +209,7 @@ public void Mutability_AddSucceedsBeforeSetImmutable(Int32[] values) public void Mutability_ClearSucceedsBeforeSetImmutable(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var set = factory.CreateSet(); foreach(var value in values) @@ -229,7 +229,7 @@ public void Mutability_ClearSucceedsBeforeSetImmutable(Int32[] values) public void Mutability_ExceptWithSucceedsBeforeSetImmutable(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var set = factory.CreateSet(); foreach(var value in values) @@ -249,7 +249,7 @@ public void Mutability_ExceptWithSucceedsBeforeSetImmutable(Int32[] values) public void Mutability_IntersectWithSucceedsBeforeSetImmutable(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var set = factory.CreateSet(); foreach(var value in values) @@ -269,7 +269,7 @@ public void Mutability_IntersectWithSucceedsBeforeSetImmutable(Int32[] values) public void Mutability_SymmetricExceptWithSucceedsBeforeSetImmutable(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var set = factory.CreateSet(); foreach(var value in values) @@ -291,7 +291,7 @@ public void Mutability_SymmetricExceptWithSucceedsBeforeSetImmutable(Int32[] val public void Mutability_UnionWithSucceedsBeforeSetImmutable(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var set = factory.CreateSet(); foreach(var value in values) diff --git a/UtilityGenerators.Tests/Library/Collections/LazyEquatableDictionaryTests.cs b/UtilityGenerators.Tests/Library/Collections/LazyEquatableDictionaryTests.cs index 9dc8614..a78bb16 100644 --- a/UtilityGenerators.Tests/Library/Collections/LazyEquatableDictionaryTests.cs +++ b/UtilityGenerators.Tests/Library/Collections/LazyEquatableDictionaryTests.cs @@ -21,7 +21,7 @@ public class LazyEquatableDictionaryTests public void ValueSemantics_VerifyEquality(Dictionary values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var dict1 = factory.CreateLazyDictionary(); var dict2 = factory.CreateLazyDictionary(); @@ -42,7 +42,7 @@ public void ValueSemantics_VerifyEquality(Dictionary values) public void ValueSemantics_VerifyInequality(Int32[] initialKeys, String[] initialValues, Int32[] modifiedKeys, String[] modifiedValues) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var dict1 = factory.CreateLazyDictionary(); var dict2 = factory.CreateLazyDictionary(); @@ -65,7 +65,7 @@ public void ValueSemantics_VerifyInequality(Int32[] initialKeys, String[] initia public void Immutability_AddThrowsAfterSetImmutable(Dictionary values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var dict = factory.CreateLazyDictionary(); foreach(var kvp in values) @@ -85,7 +85,7 @@ public void Immutability_AddThrowsAfterSetImmutable(Dictionary va public void Immutability_ClearThrowsAfterSetImmutable(Dictionary values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var dict = factory.CreateLazyDictionary(); foreach(var kvp in values) @@ -105,7 +105,7 @@ public void Immutability_ClearThrowsAfterSetImmutable(Dictionary public void Immutability_RemoveThrowsAfterSetImmutable(Dictionary values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var dict = factory.CreateLazyDictionary(); foreach(var kvp in values) @@ -125,7 +125,7 @@ public void Immutability_RemoveThrowsAfterSetImmutable(Dictionary public void Mutability_AddSucceedsBeforeSetImmutable(Dictionary values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var dict = factory.CreateLazyDictionary(); // Act @@ -147,7 +147,7 @@ public void Mutability_AddSucceedsBeforeSetImmutable(Dictionary v public void Mutability_ClearSucceedsBeforeSetImmutable(Dictionary values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var dict = factory.CreateLazyDictionary(); foreach(var kvp in values) @@ -167,7 +167,7 @@ public void Mutability_ClearSucceedsBeforeSetImmutable(Dictionary public void Mutability_RemoveSucceedsBeforeSetImmutable(Dictionary values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var dict = factory.CreateLazyDictionary(); foreach(var kvp in values) diff --git a/UtilityGenerators.Tests/Library/Collections/LazyEquatableListTests.cs b/UtilityGenerators.Tests/Library/Collections/LazyEquatableListTests.cs index 41e052f..d004939 100644 --- a/UtilityGenerators.Tests/Library/Collections/LazyEquatableListTests.cs +++ b/UtilityGenerators.Tests/Library/Collections/LazyEquatableListTests.cs @@ -22,7 +22,7 @@ public class LazyEquatableListTests public void ValueSemantics_VerifyEquality(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var list1 = factory.CreateLazyList(); var list2 = factory.CreateLazyList(); @@ -45,7 +45,7 @@ public void ValueSemantics_VerifyEquality(Int32[] values) public void ValueSemantics_VerifyInequality(Int32[] initialValues, Int32[] modifiedValues) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var list1 = factory.CreateLazyList(); var list2 = factory.CreateLazyList(); @@ -68,7 +68,7 @@ public void ValueSemantics_VerifyInequality(Int32[] initialValues, Int32[] modif public void Immutability_AddThrowsAfterSetImmutable(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateLazyList(); foreach(var value in values) @@ -88,7 +88,7 @@ public void Immutability_AddThrowsAfterSetImmutable(Int32[] values) public void Immutability_RemoveThrowsAfterSetImmutable(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateLazyList(); foreach(var value in values) @@ -108,7 +108,7 @@ public void Immutability_RemoveThrowsAfterSetImmutable(Int32[] values) public void Immutability_ClearThrowsAfterSetImmutable(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateLazyList(); foreach(var value in values) @@ -127,7 +127,7 @@ public void Immutability_ClearThrowsAfterSetImmutable(Int32[] values) public void Immutability_InsertThrowsAfterSetImmutable(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateLazyList(); foreach(var value in values) @@ -147,10 +147,12 @@ public void Immutability_InsertThrowsAfterSetImmutable(Int32[] values) public void Immutability_RemoveAtThrowsAfterSetImmutable(Int32[] values) { if(values.Length == 0) + { return; + } // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateLazyList(); foreach(var value in values) @@ -170,7 +172,7 @@ public void Immutability_RemoveAtThrowsAfterSetImmutable(Int32[] values) public void Mutability_AddSucceedsBeforeSetImmutable(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateLazyList(); // Act @@ -191,10 +193,12 @@ public void Mutability_AddSucceedsBeforeSetImmutable(Int32[] values) public void Mutability_RemoveSucceedsBeforeSetImmutable(Int32[] values) { if(values.Length == 0) + { return; + } // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateLazyList(); foreach(var value in values) @@ -214,7 +218,7 @@ public void Mutability_RemoveSucceedsBeforeSetImmutable(Int32[] values) public void Mutability_ClearSucceedsBeforeSetImmutable(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateLazyList(); foreach(var value in values) @@ -233,7 +237,7 @@ public void Mutability_ClearSucceedsBeforeSetImmutable(Int32[] values) public void Mutability_InsertSucceedsBeforeSetImmutable(Int32[] values) { // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateLazyList(); foreach(var value in values) @@ -253,10 +257,12 @@ public void Mutability_InsertSucceedsBeforeSetImmutable(Int32[] values) public void Mutability_RemoveAtSucceedsBeforeSetImmutable(Int32[] values) { if(values.Length == 0) + { return; + } // Arrange - var factory = EquatableCollectionFactory.CreateDefault(); + using var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateLazyList(); foreach(var value in values) diff --git a/UtilityGenerators.Tests/Library/Text/Templating/TemplateRendererTests.cs b/UtilityGenerators.Tests/Library/Text/Templating/TemplateRendererTests.cs index 7d8706c..33633b7 100644 --- a/UtilityGenerators.Tests/Library/Text/Templating/TemplateRendererTests.cs +++ b/UtilityGenerators.Tests/Library/Text/Templating/TemplateRendererTests.cs @@ -34,17 +34,23 @@ public void IndentsCorrectly(String[] header, String[] body, String[] footer, St // Act foreach(var h in header) + { renderer.Render(h); + } renderer.Indent(indentation); foreach(var b in body) + { renderer.Render(b); + } renderer.Detent(indentation.Length); foreach(var f in footer) + { renderer.Render(f); + } var actual = renderer.ToString(); diff --git a/UtilityGenerators.Tests/Templating/LexerTests.cs b/UtilityGenerators.Tests/Templating/LexerTests.cs index d3d18f5..3ddcf57 100644 --- a/UtilityGenerators.Tests/Templating/LexerTests.cs +++ b/UtilityGenerators.Tests/Templating/LexerTests.cs @@ -123,7 +123,9 @@ public void LexerRecognizesRequiredQuotes(String sourceText, Int32 expected) // Act using(var context = ModelCreationContext.CreateDefault(TestContext.Current.CancellationToken)) + { actual = Lexer.Scan(templateString, newlineLength: 1, in context).RequiredQuotes; + } // Assert Assert.Equal(expected, actual); diff --git a/UtilityGenerators.Tests/Templating/ParserTests.cs b/UtilityGenerators.Tests/Templating/ParserTests.cs index ffe9745..eef43d6 100644 --- a/UtilityGenerators.Tests/Templating/ParserTests.cs +++ b/UtilityGenerators.Tests/Templating/ParserTests.cs @@ -38,10 +38,12 @@ .MultiLineRawStringLiteralToken or ScanResult scanResult; using (var ctx = ModelCreationContext.CreateDefault(TestContext.Current.CancellationToken)) + { scanResult = Lexer.Scan( - templateString, - newlineLength, - in ctx); + templateString, + newlineLength, + in ctx); + } var nullableExpectedSyntax = syntaxFactory?.Invoke(new(scanResult.Tokens)); if (nullableExpectedSyntax is { } s) @@ -58,9 +60,11 @@ .MultiLineRawStringLiteralToken or // Act using (var context = ModelCreationContext.CreateDefault(TestContext.Current.CancellationToken)) + { (actualSyntax, _, actualDiagnostics) = Parser.Parse( - scanResult, - in context); + scanResult, + in context); + } // Assert if (nullableExpectedSyntax is { } expectedSyntax) diff --git a/UtilityGenerators.Tests/Templating/TemplateStringTests.cs b/UtilityGenerators.Tests/Templating/TemplateStringTests.cs index 5fd3579..c084232 100644 --- a/UtilityGenerators.Tests/Templating/TemplateStringTests.cs +++ b/UtilityGenerators.Tests/Templating/TemplateStringTests.cs @@ -316,8 +316,8 @@ public void SourceTextIsCorrectlyCreated(String sourceText, Int32 startLine, Int { // Arrange var token = CSharpSyntaxTree - .ParseText(sourceText) - .GetRoot() + .ParseText(sourceText, cancellationToken: TestContext.Current.CancellationToken) + .GetRoot(TestContext.Current.CancellationToken) .DescendantTokens(_ => true) .Single(t => t.RawKind is (Int32)SyntaxKind.StringLiteralToken or diff --git a/UtilityGenerators.Tests/Templating/TestHelpers.cs b/UtilityGenerators.Tests/Templating/TestHelpers.cs index c2c22b8..de870f7 100644 --- a/UtilityGenerators.Tests/Templating/TestHelpers.cs +++ b/UtilityGenerators.Tests/Templating/TestHelpers.cs @@ -68,7 +68,9 @@ public static String GetDiff(String expected, String? actual, Int32 columnWidth) static String getFormattedText(DiffPiece? line, Int32 columnWidth) { if(line?.Text == null) + { return "".PadRight(columnWidth); // Blank space for missing lines + } // Return the line's text truncated or padded to fit the column width return line.Text.PadRight(columnWidth)[..columnWidth]; diff --git a/UtilityGenerators.Tests/TestBase.cs b/UtilityGenerators.Tests/TestBase.cs index fa6f98d..d7a8a86 100644 --- a/UtilityGenerators.Tests/TestBase.cs +++ b/UtilityGenerators.Tests/TestBase.cs @@ -41,7 +41,9 @@ protected void TestFactory(String source, String assertion, CancellationToken ct var runResult = RunGenerator(ref compilation, ct); if(runResult.Diagnostics.Where(d => d.IsWarningAsError || d.Severity is DiagnosticSeverity.Error).Any()) + { FailDiagnostics("Generator run produced diagnostics:", runResult.Diagnostics); + } } protected GeneratorDriverRunResult RunGenerator(ref Compilation compilation, CancellationToken ct) { @@ -59,7 +61,9 @@ protected GeneratorDriverRunResult RunGenerator(ref Compilation compilation, Can cancellationToken: ct); if(!diagnostics.IsEmpty) + { FailDiagnostics("Generator produced diagnostics:", diagnostics); + } var aggregateDiagnostics = compilation .GetDiagnostics(ct) @@ -80,7 +84,9 @@ protected GeneratorDriverRunResult RunGenerator(ref Compilation compilation, Can _ = messageBuilder.Append(diagnosticGroup.diagnostics.Length).Append(" diagnostics in ").AppendLine(diagnosticGroup.key); foreach(var diagnostic in diagnosticGroup.diagnostics) + { _ = messageBuilder.AppendLine(diagnostic.ToString().Split(".g.cs").Last()); + } if(trees.TryGetValue(diagnosticGroup.key, out var tree)) { @@ -112,7 +118,7 @@ private static void FailDiagnostics(String message, IEnumerable diag .OrderBy(d => d.Location.GetLineSpan().StartLinePosition.Line) .ThenBy(d => d.Location.GetLineSpan().StartLinePosition.Character))}"); } - private static Int32 _sourceIndex = 0; + private static Int32 _sourceIndex; protected CSharpCompilation CreateCompilation(CancellationToken ct, params String[] sources) { var options = CreateCompilationOptions(); diff --git a/UtilityGenerators.Tests/UtilityGenerators.Tests.csproj b/UtilityGenerators.Tests/UtilityGenerators.Tests.csproj index af0bed4..63eadc3 100644 --- a/UtilityGenerators.Tests/UtilityGenerators.Tests.csproj +++ b/UtilityGenerators.Tests/UtilityGenerators.Tests.csproj @@ -1,14 +1,15 @@ - - enable - enable - Exe - RhoMicro.CodeAnalysis.UtilityGenerators.Tests - RhoMicro.CodeAnalysis.UtilityGenerators.Tests - net9.0 - false - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/UtilityGenerators/AttributeFactory/AttributeFactoryGenerator.cs b/UtilityGenerators/AttributeFactory/AttributeFactoryGenerator.cs index 2c9de73..74d7e43 100644 --- a/UtilityGenerators/AttributeFactory/AttributeFactoryGenerator.cs +++ b/UtilityGenerators/AttributeFactory/AttributeFactoryGenerator.cs @@ -52,7 +52,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) } if(attributeModel is null) + { return null; + } using var modelCreationContext = ModelCreationContext.CreateDefault(ct); var model = InitializationMethodModel.Create(target, attributeModel.Value, in modelCreationContext); @@ -91,7 +93,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) ct.ThrowIfCancellationRequested(); if(ctx is not { Attributes: [{ } attribute], TargetSymbol: INamedTypeSymbol target }) + { return null; + } using var modelCreationContext = ModelCreationContext.CreateDefault(ct); var model = AttributeFactoryModel.Create(target, attribute, in modelCreationContext); @@ -204,7 +208,9 @@ private static void AppendPropertyIdType(in SourceBuildingContext ctx) .AppendCore(mappedProperty.Name); if(i < ctx.Model.MappedProperties.Count - 1 || ctx.Model.UnmappedProperties.Count > 0) + { ctx.SourceBuilder.Append(',').AppendLineCore(); + } } _ = ctx.SourceBuilder.CloseBlock() @@ -226,7 +232,9 @@ private static void AppendPropertyIdType(in SourceBuildingContext ctx) .AppendCore(unmappedProperty.Name); if(i < ctx.Model.UnmappedProperties.Count - 1) + { ctx.SourceBuilder.Append(',').AppendLineCore(); + } } ctx.SourceBuilder @@ -372,7 +380,9 @@ private static void AppendConstructorAccessorFieldsAndProperties(in SourceBuildi ctx.ThrowIfCancellationRequested(); if(j > 0) + { ctx.SourceBuilder.Append(',').AppendLineCore(); + } var parameterIndex = ctor.Mappings[ctx.Model.MappedProperties[j].Name]?.ParameterIndex ?? -1; var parameterIndexString = parameterIndex.ToString(); @@ -404,7 +414,9 @@ private static void AppendConstructorAccessorFieldsAndProperties(in SourceBuildi var ctor = ctx.Model.Constructors[i]; if(ctor.Parameters.Count == 0) + { continue; + } ctx.SourceBuilder.AppendCore('['); @@ -413,7 +425,9 @@ private static void AppendConstructorAccessorFieldsAndProperties(in SourceBuildi ctx.ThrowIfCancellationRequested(); if(j > 0) + { ctx.SourceBuilder.AppendCore(", "); + } ctx.SourceBuilder.AppendCore(ctor.Parameters[j].Pattern); } @@ -1175,13 +1189,17 @@ private static void AppendPropertyAccessorFieldsAndProperties(in SourceBuildingC var property = ctx.Model.MappedProperties[i]; if(i > 0) + { ctx.SourceBuilder.Append(',').AppendLineCore(); + } ctx.SourceBuilder.Append("nameof(").Append(property.Name).AppendCore(')'); } if(ctx.Model.UnmappedProperties.Count > 0) + { ctx.SourceBuilder.Append(',').AppendLineCore(); + } ctx.SourceBuilder.CloseBlockCore(); } @@ -1197,7 +1215,9 @@ private static void AppendPropertyAccessorFieldsAndProperties(in SourceBuildingC var property = ctx.Model.UnmappedProperties[i]; if(i > 0) + { ctx.SourceBuilder.Append(',').AppendLineCore(); + } ctx.SourceBuilder.Append("nameof(").Append(property.Name).AppendCore(')'); } @@ -1230,7 +1250,9 @@ void appendSettablePropertyNames(in SourceBuildingContext ctx, IList 0) + { ctx.SourceBuilder.Append(',').AppendLineCore(); + } ctx.SourceBuilder.Append(ctx.Model.AttributeModel.PropertyIdTypeName).Append(".").AppendCore(property.Name); @@ -2027,7 +2049,9 @@ private static void AppendModelType(in SourceBuildingContext ctx) AppendModelProperties(in ctx); if(!ctx.Model.IsEquatable) + { AppendEquality(in ctx, $"{ctx.DisplayString}.{ctx.Model.AttributeModel.ModelTypeName}"); + } ctx.SourceBuilder.CloseBlockCore(); } @@ -2052,7 +2076,9 @@ private static void OpenModel(in SourceBuildingContext ctx) // If not equatable, we implement NonEquatable, it provides the impl for IEquatable. if(!ctx.Model.IsEquatable) + { ctx.SourceBuilder.Append(" : IEquatable<").Append(ctx.Model.AttributeModel.ModelTypeName).AppendCore('>'); + } ctx.SourceBuilder.OpenBracesBlock() .Append(accessibility).Append(ctx.Model.AttributeModel.ModelTypeName).Append("()") @@ -2064,7 +2090,9 @@ private static void AppendModelDefaultInstance(in SourceBuildingContext ctx) ctx.CancellationToken.ThrowIfCancellationRequested(); if(!ctx.Model.AttributeModel.GenerateDefaultInstance) + { return; + } ctx.SourceBuilder .Comment @@ -2148,7 +2176,9 @@ static void appendFactory(in SourceBuildingContext ctx, InitializationMethodMode ctx.ThrowIfCancellationRequested(); if(i != 0) + { ctx.SourceBuilder.AppendCore(", "); + } var parameter = initializationMethod.Parameters[i]; ctx.SourceBuilder.Append(parameter.Name).Append(": state.").AppendCore(parameter.PropertyName); @@ -2157,7 +2187,9 @@ static void appendFactory(in SourceBuildingContext ctx, InitializationMethodMode if(initializationMethod.CancellationTokenParameterName is { } name) { if(i != 0) + { ctx.SourceBuilder.AppendCore(", "); + } ctx.SourceBuilder.Append(name).AppendCore(": cancellationToken"); } @@ -2193,7 +2225,9 @@ private static void AppendModelApplyConstructorArguments(in SourceBuildingContex var kind = property.Type.Kind; if(kind.HasAnyFlagFast(AttributeParameterTypeKind.ValueType, AttributeParameterTypeKind.Enum)) + { ctx.SourceBuilder.AppendCore(".Value"); + } ctx.SourceBuilder.Append(';').AppendLineCore(); } @@ -2230,7 +2264,9 @@ static void append(in SourceBuildingContext ctx, IList properties ctx.ThrowIfCancellationRequested(); if(!property.HasSetter) + { continue; + } ctx.SourceBuilder .Append("case nameof(").Append(ctx.DisplayString).Append('.').Append(property.Name).Append("):") @@ -2239,7 +2275,9 @@ static void append(in SourceBuildingContext ctx, IList properties .Indent().Append(property.Name).AppendCore(" = value"); if(property.Type.Kind.HasAnyFlagFast(AttributeParameterTypeKind.ValueType, AttributeParameterTypeKind.Enum, AttributeParameterTypeKind.TypeArray, AttributeParameterTypeKind.NullableTypeArray)) + { ctx.SourceBuilder.AppendCore(".Value"); + } ctx.SourceBuilder.AppendLine(';').Detent() .AppendLine("break;") @@ -2266,7 +2304,9 @@ static void append(in SourceBuildingContext ctx, IList properties ctx.ThrowIfCancellationRequested(); if(property is { HasSetter: false, Mappings: [] }) + { continue; + } ctx.SourceBuilder.Comment .OpenSummary() @@ -2301,7 +2341,9 @@ private static void AppendFactoryStateTypes(in SourceBuildingContext ctx) ctx.ThrowIfCancellationRequested(); if(i != 0) + { ctx.SourceBuilder.AppendCore(", "); + } var parameter = initMethod.Parameters[i]; ctx.SourceBuilder.Append(parameter.Type).Append(' ').AppendCore(parameter.PropertyName); @@ -2514,7 +2556,9 @@ static void appendExtension(in SourceBuildingContext ctx, InitializationMethodMo .Append(ctx.DisplayString).Append('.').AppendCore(ctx.Model.AttributeModel.ModelTypeName); if(!ctx.Model.AttributeModel.GenerateModelTypeAsStruct) + { ctx.SourceBuilder.AppendCore('?'); + } ctx.SourceBuilder .Append(" model, bool checkType = true, global::System.Threading.CancellationToken cancellationToken = default)") @@ -2525,7 +2569,9 @@ static void appendExtension(in SourceBuildingContext ctx, InitializationMethodMo .Append("model = ").Append(ctx.DisplayString).Append('.').Append(ctx.Model.AttributeModel.ModelTypeName).AppendCore(".Create(data"); if(initializationMethod is { Parameters.Count: > 0 }) + { ctx.SourceBuilder.AppendCore(", in state"); + } ctx.SourceBuilder .Append(", cancellationToken);") @@ -2570,7 +2616,9 @@ static void appendExtension(in SourceBuildingContext ctx, InitializationMethodMo .AppendCore(" data"); if(initializationMethod is { Parameters.Count: > 0 }) + { ctx.SourceBuilder.Append(", in ").Append(initializationMethod.StateTypeDisplayString).AppendCore(" state"); + } ctx.SourceBuilder.Append(", global::System.Threading.CancellationToken cancellationToken = default)") .OpenBracesBlock() @@ -2578,7 +2626,9 @@ static void appendExtension(in SourceBuildingContext ctx, InitializationMethodMo .Append("return ").Append(ctx.DisplayString).Append('.').Append(ctx.Model.AttributeModel.ModelTypeName).AppendCore(".Create(data"); if(initializationMethod is { Parameters.Count: > 0 }) + { ctx.SourceBuilder.AppendCore(", in state"); + } ctx.SourceBuilder .Append(", cancellationToken);") @@ -2631,7 +2681,9 @@ static void appendExtension(in SourceBuildingContext ctx, InitializationMethodMo .AppendCore("(this global::System.Collections.Immutable.ImmutableArray data"); if(initializationMethod is { Parameters.Count: > 0 }) + { ctx.SourceBuilder.Append(", ").Append(initializationMethod.StateTypeDisplayString).AppendCore(" state"); + } ctx.SourceBuilder.Append(", global::System.Threading.CancellationToken cancellationToken = default)") .OpenBracesBlock() @@ -2643,7 +2695,9 @@ static void appendExtension(in SourceBuildingContext ctx, InitializationMethodMo .Indent().Append("yield return Get").Append(ctx.Model.Signature.Name).AppendCore("Model(datum"); if(initializationMethod is { Parameters.Count: > 0 }) + { ctx.SourceBuilder.AppendCore(", in state"); + } ctx.SourceBuilder.Append(", cancellationToken);").Detent() .CloseBlock() @@ -2676,7 +2730,9 @@ static void appendExtension(in SourceBuildingContext ctx, InitializationMethodMo .AppendCore("(this global::System.Collections.Generic.IEnumerable data"); if(initializationMethod is { Parameters.Count: > 0 }) + { ctx.SourceBuilder.Append(", ").Append(initializationMethod.StateTypeDisplayString).AppendCore(" state"); + } ctx.SourceBuilder.Append(", global::System.Threading.CancellationToken cancellationToken = default)") .OpenBracesBlock() @@ -2688,7 +2744,9 @@ static void appendExtension(in SourceBuildingContext ctx, InitializationMethodMo .Indent().Append("yield return Get").Append(ctx.Model.Signature.Name).AppendCore("Model(datum"); if(initializationMethod is { Parameters.Count: > 0 }) + { ctx.SourceBuilder.AppendCore(", in state"); + } ctx.SourceBuilder.Append(", cancellationToken);").Detent() .CloseBlock() diff --git a/UtilityGenerators/AttributeFactory/AttributeFactoryModel.cs b/UtilityGenerators/AttributeFactory/AttributeFactoryModel.cs index 00c8125..4468239 100644 --- a/UtilityGenerators/AttributeFactory/AttributeFactoryModel.cs +++ b/UtilityGenerators/AttributeFactory/AttributeFactoryModel.cs @@ -140,7 +140,9 @@ private static void GetConstructorModels(INamedTypeSymbol target, EquatableList< ctx.ThrowIfCancellationRequested(); if(mapping is { } m) + { propertyMappings[m.PropertyName].Add(m); + } } } } diff --git a/UtilityGenerators/AttributeFactory/ConstructorModel.cs b/UtilityGenerators/AttributeFactory/ConstructorModel.cs index 3671263..12c9be3 100644 --- a/UtilityGenerators/AttributeFactory/ConstructorModel.cs +++ b/UtilityGenerators/AttributeFactory/ConstructorModel.cs @@ -39,7 +39,9 @@ public static ConstructorModel Create( var model = ParameterModel.Create(parameter, parameterIndex, in ctx); if(model is { MappedProperty: { } propertyName, Name: { } parameterName }) + { mappings[propertyName] = new ParameterMapping(index, parameterIndex, ParameterName: parameterName, PropertyName: propertyName); + } parameters.Add(model); } @@ -60,21 +62,29 @@ public void AppendConstructorExpressionComment(in SourceBuildingContext ctx, Int ctx.ThrowIfCancellationRequested(); if(i > 0) + { ctx.SourceBuilder.AppendCore(", "); + } var param = Parameters[i]; _ = ctx.SourceBuilder.Comment.SeeCRef(param.Type.ElementDisplayString); if(param.Type.Kind.HasAnyFlagFast(AttributeParameterTypeKind.Array)) + { ctx.SourceBuilder.AppendCore("[]"); + } ctx.SourceBuilder.AppendCore(' '); if(( i == highlightIndex || highlightIndex == -1 ) && param.MappedProperty is { } mappedProperty) + { ctx.SourceBuilder.Comment.OpenEmphasis().Append(param.Name).Append("->").Append(mappedProperty).CloseBlockCore(); + } else + { ctx.SourceBuilder.AppendCore(param.Name); + } } ctx.SourceBuilder.AppendCore(')'); @@ -92,21 +102,29 @@ public void AppendConstructorExpression(in SourceBuildingContext ctx, Int32 high ctx.ThrowIfCancellationRequested(); if(i > 0) + { ctx.SourceBuilder.AppendCore(", "); + } var param = Parameters[i]; _ = ctx.SourceBuilder.Append(param.Type.ElementDisplayString); if(param.Type.Kind.HasAnyFlagFast(AttributeParameterTypeKind.Array)) + { ctx.SourceBuilder.AppendCore("[]"); + } ctx.SourceBuilder.AppendCore(' '); if(( i == highlightIndex || highlightIndex == -1 ) && param.MappedProperty is not null) + { ctx.SourceBuilder.Append('<').Append(param.Name).AppendCore('>'); + } else + { ctx.SourceBuilder.AppendCore(param.Name); + } } ctx.SourceBuilder.AppendCore(')'); diff --git a/UtilityGenerators/AttributeFactory/InitializationMethodModel.cs b/UtilityGenerators/AttributeFactory/InitializationMethodModel.cs index 5e3b99a..f1325dd 100644 --- a/UtilityGenerators/AttributeFactory/InitializationMethodModel.cs +++ b/UtilityGenerators/AttributeFactory/InitializationMethodModel.cs @@ -67,7 +67,9 @@ private static void GetParameters(IMethodSymbol method, IList Mappings) { - [TypeSymbolPattern(typeof(String[]))] - private static partial Boolean IsStringArray(ITypeSymbol? type); - public static PropertyModel Create(IPropertySymbol property, LazyEquatableDictionary> propertyMappings, in ModelCreationContext ctx) + public static PropertyModel Create(IPropertySymbol property, + LazyEquatableDictionary> + propertyMappings, in ModelCreationContext ctx) { ctx.ThrowIfCancellationRequested(); @@ -30,13 +28,16 @@ public static PropertyModel Create(IPropertySymbol property, LazyEquatableDictio String? defaultValueExpression = null; - foreach(var attribute in property.GetAttributes()) + foreach (var attribute in property.GetAttributes()) { ctx.ThrowIfCancellationRequested(); - if(attribute.IsDefaultValueAttribute() && attribute.ConstructorArguments is [{ } defaultValue]) + if (attribute.IsDefaultValueAttribute() && attribute.ConstructorArguments is [{ } defaultValue]) { - defaultValueExpression = IsStringArray(defaultValue.Type) + defaultValueExpression = defaultValue.Type is IArrayTypeSymbol + { + ElementType.SpecialType: SpecialType.System_String + } ? $"[{String.Join(", ", defaultValue.Values.Select(c => c.ToCSharpString()))}]" : defaultValue.ToCSharpString(); } @@ -54,5 +55,6 @@ public static PropertyModel Create(IPropertySymbol property, LazyEquatableDictio return result; } - public override String ToString() => $"{Name}{{g{( HasSetter ? "/s" : "" )}}}<-[{String.Join(", ", Mappings)}]"; + + public override String ToString() => $"{Name}{{g{(HasSetter ? "/s" : "")}}}<-[{String.Join(", ", Mappings)}]"; } diff --git a/UtilityGenerators/GlobalUsings.cs b/UtilityGenerators/GlobalUsings.cs index 3eb00db..8f73aad 100644 --- a/UtilityGenerators/GlobalUsings.cs +++ b/UtilityGenerators/GlobalUsings.cs @@ -2,3 +2,4 @@ global using System.Diagnostics; global using System.Diagnostics.CodeAnalysis; +global using static RhoMicro.CodeAnalysis.Library.Text.Templating.Indentations; diff --git a/UtilityGenerators/IndentedStringBuilderAppendables/IndentedStringBuilderAppendablesGenerator.cs b/UtilityGenerators/IndentedStringBuilderAppendables/IndentedStringBuilderAppendablesGenerator.cs index 7c6beeb..4f70670 100644 --- a/UtilityGenerators/IndentedStringBuilderAppendables/IndentedStringBuilderAppendablesGenerator.cs +++ b/UtilityGenerators/IndentedStringBuilderAppendables/IndentedStringBuilderAppendablesGenerator.cs @@ -46,7 +46,9 @@ private static String GetArgumentsText(ParameterListSyntax parameterList) { var parameters = parameterList.Parameters; if(parameters.Count == 0) + { return String.Empty; + } var resultBuilder = new StringBuilder(); var i = 0; @@ -75,7 +77,9 @@ private static IList GetConstraintsText( var result = ctx.CollectionFactory.CreateList(); if(clauses.Count == 0) + { return result; + } var constraintBuilder = new StringBuilder(); @@ -138,7 +142,9 @@ Boolean tryAppend() .Type; if(type == null) + { return false; + } var fullName = type.ToDisplayString(_fullyQualifiedFormat); _ = constraintBuilder.Append(fullName); @@ -160,19 +166,25 @@ private static String GetParametersText( var enumerator = parameterList.Parameters.GetEnumerator(); if(!enumerator.MoveNext()) + { return String.Empty; + } var resultBuilder = new StringBuilder(); if(!tryAppend()) + { return String.Empty; + } while(enumerator.MoveNext()) { cancellationToken.ThrowIfCancellationRequested(); _ = resultBuilder.Append(", "); if(!tryAppend()) + { return String.Empty; + } } return resultBuilder.ToString(); @@ -186,7 +198,9 @@ Boolean tryAppend() null; if(type == null) + { return false; + } if(enumerator.Current.Modifiers.Count > 0) { @@ -199,7 +213,9 @@ Boolean tryAppend() .Append(enumerator.Current.Identifier.Text); if(enumerator.Current.Default != null) + { _ = resultBuilder.Append(enumerator.Current.Default.ToString()); + } return true; } @@ -254,12 +270,16 @@ private static String FinalStep(ImmutableHashSet signatures, Cancella .Indent(); if(constraints.Count > 0) + { _ = builder.AppendLine().AppendJoinLines(String.Empty, constraints); + } _ = builder.Append(" =>"); if(parameters == String.Empty && constraints.Count == 0) + { _ = builder.Append(" StaticAppendableInstances.").Append(fieldName).AppendLine(';'); + } _ = ( parameters == String.Empty && constraints.Count == 0 ? fieldsBuilder : builder ).AppendLine() .Append("new") @@ -284,7 +304,9 @@ private static ImmutableArray GetSignaturesStep(GeneratorSyntaxContex var symbol = context.SemanticModel.GetDeclaredSymbol(context.Node, ct); if(!IsTargetSymbol(symbol)) + { return _emptySignaturesArray; + } using var ctx = ModelCreationContext.CreateDefault(ct); var cds = (ClassDeclarationSyntax)context.Node; diff --git a/UtilityGenerators/Library/External/System/Buffers/ArrayBufferWriter.cs b/UtilityGenerators/Library/External/System/Buffers/ArrayBufferWriter.cs index 6b91094..e3f10bd 100644 --- a/UtilityGenerators/Library/External/System/Buffers/ArrayBufferWriter.cs +++ b/UtilityGenerators/Library/External/System/Buffers/ArrayBufferWriter.cs @@ -49,7 +49,9 @@ public ArrayBufferWriter() public ArrayBufferWriter(int initialCapacity) { if(initialCapacity <= 0) + { throw new ArgumentException(null, nameof(initialCapacity)); + } _buffer = new T[initialCapacity]; _index = 0; @@ -129,10 +131,14 @@ public void Clear() public void Advance(int count) { if(count < 0) + { throw new ArgumentException(null, nameof(count)); + } if(_index > _buffer.Length - count) + { ThrowInvalidOperationException_AdvancedTooFar(_buffer.Length); + } _index += count; } @@ -202,7 +208,9 @@ public Span GetSpan(int sizeHint = 0) private void CheckAndResizeBuffer(int sizeHint) { if(sizeHint < 0) + { throw new ArgumentException(nameof(sizeHint)); + } if(sizeHint == 0) { diff --git a/UtilityGenerators/Library/External/System/HashCode.cs b/UtilityGenerators/Library/External/System/HashCode.cs index 28652ef..c1682f6 100644 --- a/UtilityGenerators/Library/External/System/HashCode.cs +++ b/UtilityGenerators/Library/External/System/HashCode.cs @@ -346,15 +346,23 @@ private void Add(int value) // Switch can't be inlined. if(position == 0) + { _queue1 = val; + } else if(position == 1) + { _queue2 = val; + } else if(position == 2) + { _queue3 = val; + } else // position == 3 { if(previousLength == 3) + { Initialize(out _v1, out _v2, out _v3, out _v4); + } _v1 = Round(_v1, _queue1); _v2 = Round(_v2, _queue2); @@ -396,7 +404,9 @@ public int ToHashCode() { hash = QueueRound(hash, _queue2); if(position > 2) + { hash = QueueRound(hash, _queue3); + } } } diff --git a/UtilityGenerators/Library/External/System/LocalAppContextSwitches.cs b/UtilityGenerators/Library/External/System/LocalAppContextSwitches.cs index 71bbb46..ec11dbf 100644 --- a/UtilityGenerators/Library/External/System/LocalAppContextSwitches.cs +++ b/UtilityGenerators/Library/External/System/LocalAppContextSwitches.cs @@ -30,9 +30,14 @@ internal static bool GetCachedSwitchValue(string switchName, ref int cachedSwitc { // The cached switch value has 3 states: 0 - unknown, 1 - true, -1 - false if(cachedSwitchValue < 0) + { return false; + } + if(cachedSwitchValue > 0) + { return true; + } return GetCachedSwitchValueInternal(switchName, ref cachedSwitchValue); } diff --git a/UtilityGenerators/Library/Models/AttributeParameterTypeKind.cs b/UtilityGenerators/Library/Models/AttributeParameterTypeKind.cs index 26ba2f3..5188154 100644 --- a/UtilityGenerators/Library/Models/AttributeParameterTypeKind.cs +++ b/UtilityGenerators/Library/Models/AttributeParameterTypeKind.cs @@ -43,7 +43,9 @@ public static Boolean HasAnyFlagFast(this AttributeParameterTypeKind value, para foreach(var flag in flags) { if(( value & flag ) == flag) + { return true; + } } return false; diff --git a/UtilityGenerators/Library/Models/Collections/DictionaryEqualityComparer.cs b/UtilityGenerators/Library/Models/Collections/DictionaryEqualityComparer.cs index 047edd2..55defa7 100644 --- a/UtilityGenerators/Library/Models/Collections/DictionaryEqualityComparer.cs +++ b/UtilityGenerators/Library/Models/Collections/DictionaryEqualityComparer.cs @@ -28,12 +28,16 @@ public Boolean Equals(TDictionaryX x, TDictionaryY y where TDictionaryY : IDictionary { if(x.Count != y.Count) + { return false; + } foreach(var kvp in x) { if(!y.TryGetValue(kvp.Key, out var value) || !valueComparer.Equals(kvp.Value, value)) + { return false; + } } return true; diff --git a/UtilityGenerators/Library/Models/Collections/EnumerableEqualityComparer.cs b/UtilityGenerators/Library/Models/Collections/EnumerableEqualityComparer.cs index 340aff2..55c3d17 100644 --- a/UtilityGenerators/Library/Models/Collections/EnumerableEqualityComparer.cs +++ b/UtilityGenerators/Library/Models/Collections/EnumerableEqualityComparer.cs @@ -17,7 +17,9 @@ public static Int32 GetHashCode(TEnumerable obj, IEqualityCompar var hc = new HashCode(); foreach(var element in obj) + { hc.Add(element, elementComparer); + } return hc.ToHashCode(); } @@ -37,7 +39,9 @@ public Boolean Equals(TEnumerableX x, TEnumerableY y where TEnumerableY : IEnumerable { if(x.GetType() != y.GetType()) + { return false; + } var result = x.SequenceEqual(y, elementComparer); diff --git a/UtilityGenerators/Library/Models/Collections/EquatableList.cs b/UtilityGenerators/Library/Models/Collections/EquatableList.cs index 73c215e..e2e6809 100644 --- a/UtilityGenerators/Library/Models/Collections/EquatableList.cs +++ b/UtilityGenerators/Library/Models/Collections/EquatableList.cs @@ -56,7 +56,9 @@ public static EquatableList Create(ReadOnlySpan elements) var result = ctx.CollectionFactory.CreateList(); foreach(var element in elements) + { result.Add(element); + } return result; } diff --git a/UtilityGenerators/Library/Models/Collections/EquatableSet.cs b/UtilityGenerators/Library/Models/Collections/EquatableSet.cs index 23a5a71..30a34b9 100644 --- a/UtilityGenerators/Library/Models/Collections/EquatableSet.cs +++ b/UtilityGenerators/Library/Models/Collections/EquatableSet.cs @@ -66,7 +66,9 @@ public static EquatableSet Create(ReadOnlySpan elements) var result = ctx.CollectionFactory.CreateSet(); foreach(var element in elements) + { _ = result.Add(element); + } return result; } diff --git a/UtilityGenerators/Library/Models/Collections/LazyEquatableList.cs b/UtilityGenerators/Library/Models/Collections/LazyEquatableList.cs index 6dcb987..c61a618 100644 --- a/UtilityGenerators/Library/Models/Collections/LazyEquatableList.cs +++ b/UtilityGenerators/Library/Models/Collections/LazyEquatableList.cs @@ -80,7 +80,9 @@ private void FillUpToIndex(Int32 index) { MutabilityContext.ThrowIfReadOnly(); while(Count <= index) + { Add(Factory.Invoke(Count, this)); + } } } @@ -92,7 +94,9 @@ file static class Builder var result = ctx.CollectionFactory.CreateLazyList(); foreach(var element in elements) + { result.Add(element); + } return result; } diff --git a/UtilityGenerators/Library/Models/Collections/MutabilityContext.cs b/UtilityGenerators/Library/Models/Collections/MutabilityContext.cs index 2de0ca5..f3c0fac 100644 --- a/UtilityGenerators/Library/Models/Collections/MutabilityContext.cs +++ b/UtilityGenerators/Library/Models/Collections/MutabilityContext.cs @@ -19,7 +19,9 @@ internal sealed partial class MutabilityContext : IDisposable public void ThrowIfReadOnly([CallerMemberName] String? callerName = null) { if(IsImmutable) + { throw new InvalidOperationException($"Unable to mutate using '{callerName}' after its mutability context was set to immutable."); + } } public void Dispose() => SetImmutable(); } diff --git a/UtilityGenerators/Library/Models/Collections/SetEqualityComparer.cs b/UtilityGenerators/Library/Models/Collections/SetEqualityComparer.cs index b379471..3015dc2 100644 --- a/UtilityGenerators/Library/Models/Collections/SetEqualityComparer.cs +++ b/UtilityGenerators/Library/Models/Collections/SetEqualityComparer.cs @@ -25,12 +25,16 @@ public Boolean Equals(TSetX x, TSetY y) where TSetY : ISet { if(x.Count != y.Count) + { return false; + } foreach(var element in x) { if(!y.Contains(element, elementComparer)) + { return false; + } } return true; diff --git a/UtilityGenerators/Library/Models/NamedTypeModel.cs b/UtilityGenerators/Library/Models/NamedTypeModel.cs index cbf48c9..677faa1 100644 --- a/UtilityGenerators/Library/Models/NamedTypeModel.cs +++ b/UtilityGenerators/Library/Models/NamedTypeModel.cs @@ -61,7 +61,9 @@ private static void AddContainingTypes(INamedTypeSymbol? containingType, Equatab ctx.ThrowIfCancellationRequested(); if(containingType is null) + { return; + } AddContainingTypes(containingType.ContainingType, containingTypes, in ctx); @@ -77,20 +79,26 @@ public String GetHintName(CancellationToken ct) var resultBuilder = new StringBuilder(); foreach(var part in NamespaceParts) + { _ = resultBuilder.Append(part).Append('.'); + } foreach(var containingType in ContainingTypes) { _ = resultBuilder.Append(containingType.Name).Append('.'); if(containingType.TypeArguments is { Count: > 0 and var innerCount }) + { _ = resultBuilder.Append(innerCount).Append('.'); + } } _ = resultBuilder.Append(Name).Append('.'); if(TypeArguments is { Count: > 0 and var outerCount }) + { _ = resultBuilder.Append(outerCount).Append('.'); + } var result = resultBuilder.Append("g.cs").ToString(); @@ -119,7 +127,9 @@ private void Append(StringBuilder resultBuilder, in AppendTokenInfo tokenInfo, C AppendContainingTypes(resultBuilder, in tokenInfo, ct); if(!tokenInfo.SeparateParts && ( ContainingTypes.Count > 0 || NamespaceParts.Count > 0 )) + { _ = resultBuilder.Append(tokenInfo.SeparatorToken); + } AppendName(resultBuilder, Name, ct); AppendTypeArguments(resultBuilder, TypeArguments, in tokenInfo, ct); @@ -129,34 +139,44 @@ private void AppendNamespace(StringBuilder resultBuilder, in AppendTokenInfo tok ct.ThrowIfCancellationRequested(); if(NamespaceParts.Count <= 0) + { return; + } for(var i = 0; i < NamespaceParts.Count; i++) { ct.ThrowIfCancellationRequested(); if(i != 0) + { _ = resultBuilder.Append(tokenInfo.SeparatorToken); + } _ = resultBuilder.Append((String?)NamespaceParts[i]); } if(tokenInfo.SeparateParts) + { _ = resultBuilder.Append(tokenInfo.SeparatorToken); + } } private void AppendContainingTypes(StringBuilder resultBuilder, in AppendTokenInfo tokenInfo, CancellationToken ct) { ct.ThrowIfCancellationRequested(); if(ContainingTypes.Count == 0) + { return; + } for(var i = 0; i < ContainingTypes.Count; i++) { ct.ThrowIfCancellationRequested(); if(i != 0) + { _ = resultBuilder.Append(tokenInfo.SeparatorToken); + } var containingType = ContainingTypes[i]; @@ -165,7 +185,9 @@ private void AppendContainingTypes(StringBuilder resultBuilder, in AppendTokenIn } if(tokenInfo.SeparateParts) + { _ = resultBuilder.Append(tokenInfo.SeparatorToken); + } } private static void AppendName( StringBuilder resultBuilder, @@ -181,10 +203,14 @@ private static void AppendTypeArguments(StringBuilder resultBuilder, EquatableLi ct.ThrowIfCancellationRequested(); if(typeArguments.Count == 0) + { return; + } if(tokenInfo.SeparateParts) + { _ = resultBuilder.Append(tokenInfo.SeparatorToken); + } _ = resultBuilder.Append(tokenInfo.OpenToken); @@ -193,7 +219,9 @@ private static void AppendTypeArguments(StringBuilder resultBuilder, EquatableLi ct.ThrowIfCancellationRequested(); if(i != 0) + { _ = resultBuilder.Append(tokenInfo.ArgumentSeparatorToken); + } var typeArgument = typeArguments[i]; _ = resultBuilder.Append(typeArgument.Name); @@ -281,7 +309,9 @@ private static void AppendImplementedInterfaces( ct.ThrowIfCancellationRequested(); if(superTypes.Length == 0) + { return; + } using var _ = sourceBuilder.Append(" :").AppendLine().OpenIndentBlockScope(); @@ -290,7 +320,9 @@ private static void AppendImplementedInterfaces( ct.ThrowIfCancellationRequested(); if(i != 0) + { sourceBuilder.Append(',').AppendLineCore(); + } sourceBuilder.AppendCore(superTypes[i]); } @@ -318,7 +350,9 @@ private void AppendNamespace( ct.ThrowIfCancellationRequested(); if(NamespaceParts.Count <= 0) + { return; + } _ = sourceBuilder.Append("namespace "); @@ -348,7 +382,9 @@ private void AppendContainingTypes(StringBuilder hintNameBuilder, StringBuilder ct.ThrowIfCancellationRequested(); if(ContainingTypes.Count == 0) + { return; + } for(var i = 0; i < ContainingTypes.Count; i++) { @@ -381,7 +417,9 @@ private static void AppendTypeArguments(StringBuilder hintNameBuilder, StringBui ct.ThrowIfCancellationRequested(); if(typeArguments.Count == 0) + { return; + } _ = hintNameBuilder.Append("_of"); _ = displayStringBuilder.Append('<'); diff --git a/UtilityGenerators/Library/Models/TypeModel.cs b/UtilityGenerators/Library/Models/TypeModel.cs index 6acf52a..3ebd586 100644 --- a/UtilityGenerators/Library/Models/TypeModel.cs +++ b/UtilityGenerators/Library/Models/TypeModel.cs @@ -18,10 +18,14 @@ public static TypeModel Create(ITypeSymbol type, in ModelCreationContext ctx) ctx.ThrowIfCancellationRequested(); if(type is INamedTypeSymbol named) + { return NamedTypeModel.Create(named, in ctx); + } if(type is IArrayTypeSymbol array) + { return ArrayTypeModel.Create(array, in ctx); + } var namespaceParts = GetNamespaceParts(type, in ctx); var name = type.Name; @@ -47,7 +51,9 @@ private static void AddNamespaceParts(INamespaceSymbol? @namespace, EquatableLis ctx.ThrowIfCancellationRequested(); if(@namespace is null or { IsGlobalNamespace: true }) + { return; + } AddNamespaceParts(@namespace.ContainingNamespace, parts, in ctx); diff --git a/UtilityGenerators/Library/Text/SourceTexts/BlockScopeCollection.cs b/UtilityGenerators/Library/Text/SourceTexts/BlockScopeCollection.cs index 0bfb245..2ec1a11 100644 --- a/UtilityGenerators/Library/Text/SourceTexts/BlockScopeCollection.cs +++ b/UtilityGenerators/Library/Text/SourceTexts/BlockScopeCollection.cs @@ -20,6 +20,8 @@ sealed partial class BlockScopeCollection : IDisposable public void Dispose() { foreach(var scope in _scopes) + { scope.Dispose(); + } } } diff --git a/UtilityGenerators/Library/Text/SourceTexts/CommentBuilder.cs b/UtilityGenerators/Library/Text/SourceTexts/CommentBuilder.cs index 8d03829..ca18aee 100644 --- a/UtilityGenerators/Library/Text/SourceTexts/CommentBuilder.cs +++ b/UtilityGenerators/Library/Text/SourceTexts/CommentBuilder.cs @@ -77,15 +77,21 @@ public IndentedStringBuilder SeeCRefMethod(String name, Int32 highlightIndex, pa for(var i = 0; i < parameterTypes.Length; i++) { if(i > 0) + { Builder.AppendCore(", "); + } if(highlightIndex == i) + { Builder.AppendCore('<'); + } Builder.AppendCore(parameterTypes[i]); if(highlightIndex == i) + { Builder.AppendCore('>'); + } } Builder.AppendCore(")\"/>"); @@ -103,15 +109,21 @@ public IndentedStringBuilder SeeCRefMethod(String name, Int32 highlightIndex, pa foreach(var type in parameterTypes) { if(i > 0) + { Builder.AppendCore(", "); + } if(highlightIndex == i) + { Builder.AppendCore('<'); + } Builder.AppendCore(type); if(highlightIndex == i) + { Builder.AppendCore('>'); + } i++; } @@ -126,24 +138,32 @@ public IndentedStringBuilder Langword(String name) => public IndentedStringBuilder InheritDoc(String name, Boolean topLevel = true) { if(topLevel) + { _ = Builder.Append("/// "); + } _ = Builder.Append(""); if(topLevel) + { _ = Builder.AppendLine(); + } return Builder; } public IndentedStringBuilder InheritDoc(Boolean topLevel = true) { if(topLevel) + { _ = Builder.Append("/// "); + } _ = Builder.Append(""); if(topLevel) + { _ = Builder.AppendLine(); + } return Builder; } diff --git a/UtilityGenerators/Library/Text/SourceTexts/IndentedStringBuilder.cs b/UtilityGenerators/Library/Text/SourceTexts/IndentedStringBuilder.cs index a2b4601..2713dd2 100644 --- a/UtilityGenerators/Library/Text/SourceTexts/IndentedStringBuilder.cs +++ b/UtilityGenerators/Library/Text/SourceTexts/IndentedStringBuilder.cs @@ -33,22 +33,34 @@ private void InitializeBuilder() { if(Options.PrependMarkerComment) + { _ = _builder.Append("// ").Append(Options.NewLine); + } if(!String.IsNullOrWhiteSpace(Options.GeneratorName)) + { _ = _builder.Append("// This file was generated by ").Append(Options.GeneratorName).Append(Options.NewLine); + } if(!String.IsNullOrWhiteSpace(Options.License)) + { _ = _builder.Append(Options.License).Append(Options.NewLine); + } if(Options.PrependMarkerComment) + { _ = _builder.Append("// ").Append(Options.NewLine); + } if(Options.PrependWarningDisablePragma) + { _ = _builder.Append("#pragma warning disable").Append(Options.NewLine); + } if(Options.PrependNullableEnable) + { _ = _builder.Append("#nullable enable").Append(Options.NewLine); + } } public IndentedStringBuilder() : this(IndentedStringBuilderOptions.Default) @@ -67,12 +79,16 @@ private Boolean LastWasNewLine get { if(_builder.Length < Options.NewLine.Length) + { return false; + } for(var i = 1; i <= Options.NewLine.Length; i++) { if(_builder[^i] != Options.NewLine[^i]) + { return false; + } } return true; @@ -85,7 +101,9 @@ private Boolean LastWasNewLine public void OpenBlockCore(Block block) { if(block.PlaceDelimitersOnNewLine && !LastWasNewLine) + { AppendSingleLineCore(); + } AppendCore(block.OpeningDelimiter); @@ -144,14 +162,18 @@ public IndentedStringBuilder OpenRegionBlockScope(String name, BlockScopeCollect public void CloseBlockCore() { if(!LastBlock.HasValue) + { return; + } DetentUnsafeCore(); var block = LastBlock.Value; if(block.PlaceDelimitersOnNewLine) + { AppendSingleLineCore(); + } AppendCore(block.ClosingDelimiter); _ = _blocks.Pop(); @@ -187,7 +209,9 @@ public IndentedStringBuilder Indent() private void ApplyIndentation() { if(!LastWasNewLine) + { return; + } foreach(var indentation in _indentations.Reverse()) { @@ -206,7 +230,9 @@ public void DetentCore() if(_indentations.Count > 0) { if(!_indentations.Peek().IsWhitespace) + { throw new InvalidOperationException("Attempted to detent non-whitespace indentation. Use 'CloseBlock' or 'CloseAllBlocks' instead."); + } DetentUnsafeCore(); } @@ -461,7 +487,9 @@ public IndentedStringBuilder AppendJoin(StringOrChar separator, IEnumerable(StringOrChar separator, IEnumerable(StringOrChar separator, IEnumera var enumerator = values.GetEnumerator(); if(!enumerator.MoveNext()) + { return this; + } AppendCore(enumerator.Current); @@ -587,7 +625,9 @@ public IndentedStringBuilder AppendJoin(StringOrChar separator, IEnumerable characters) { if(characters.Length == 0) + { return; + } var target = Reserve(characters.Length); characters.CopyTo(target); @@ -111,7 +113,9 @@ private void EnsureLength(Int32 requiredLength) public readonly void Dispose() { if(_rented is not null) + { ArrayPool.Shared.Return(_rented); + } } public readonly override String ToString() => Span.ToString(); diff --git a/UtilityGenerators/Library/Text/Templating/TemplateRenderer.Render.cs b/UtilityGenerators/Library/Text/Templating/TemplateRenderer.Render.cs index 07e4979..20e3703 100644 --- a/UtilityGenerators/Library/Text/Templating/TemplateRenderer.Render.cs +++ b/UtilityGenerators/Library/Text/Templating/TemplateRenderer.Render.cs @@ -47,7 +47,9 @@ public void Render(params ReadOnlySpan value) // If the value is empty, we do not unnecessarily prepend indentation, // as no newline could be present. if(value is []) + { return; + } // Otherwise, we split at each newline and append the requested // indentation if the next line is not empty. Since the value will be diff --git a/UtilityGenerators/Library/ThrowHelpers.cs b/UtilityGenerators/Library/ThrowHelpers.cs index a9041c9..ea7511b 100644 --- a/UtilityGenerators/Library/ThrowHelpers.cs +++ b/UtilityGenerators/Library/ThrowHelpers.cs @@ -25,7 +25,9 @@ internal static class ArgumentNullException internal static void ThrowIfNull([NotNull] Object? argument, [CallerArgumentExpression(nameof(argument))] String? paramName = null) { if(argument is null) + { Throw(paramName); + } } [DoesNotReturn] @@ -76,7 +78,9 @@ private static void ThrowNotEqual(T value, T other, String? paramName) => internal static void ThrowIfZero(Single value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value == 0) + { ThrowZero(value, paramName); + } } /// Throws an if is zero. /// The argument to validate as non-zero. @@ -84,7 +88,9 @@ internal static void ThrowIfZero(Single value, [CallerArgumentExpression(nameof( internal static void ThrowIfZero(Double value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value == 0) + { ThrowZero(value, paramName); + } } /// Throws an if is zero. /// The argument to validate as non-zero. @@ -92,7 +98,9 @@ internal static void ThrowIfZero(Double value, [CallerArgumentExpression(nameof( internal static void ThrowIfZero(SByte value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value == 0) + { ThrowZero(value, paramName); + } } /// Throws an if is zero. /// The argument to validate as non-zero. @@ -100,7 +108,9 @@ internal static void ThrowIfZero(SByte value, [CallerArgumentExpression(nameof(v internal static void ThrowIfZero(Int16 value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value == 0) + { ThrowZero(value, paramName); + } } /// Throws an if is zero. /// The argument to validate as non-zero. @@ -108,7 +118,9 @@ internal static void ThrowIfZero(Int16 value, [CallerArgumentExpression(nameof(v internal static void ThrowIfZero(Int32 value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value == 0) + { ThrowZero(value, paramName); + } } /// Throws an if is zero. /// The argument to validate as non-zero. @@ -116,7 +128,9 @@ internal static void ThrowIfZero(Int32 value, [CallerArgumentExpression(nameof(v internal static void ThrowIfZero(Int64 value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value == 0) + { ThrowZero(value, paramName); + } } /// Throws an if is zero. /// The argument to validate as non-zero. @@ -124,7 +138,9 @@ internal static void ThrowIfZero(Int64 value, [CallerArgumentExpression(nameof(v internal static void ThrowIfZero(Byte value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value == 0) + { ThrowZero(value, paramName); + } } /// Throws an if is zero. /// The argument to validate as non-zero. @@ -132,7 +148,9 @@ internal static void ThrowIfZero(Byte value, [CallerArgumentExpression(nameof(va internal static void ThrowIfZero(UInt16 value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value == 0) + { ThrowZero(value, paramName); + } } /// Throws an if is zero. /// The argument to validate as non-zero. @@ -140,7 +158,9 @@ internal static void ThrowIfZero(UInt16 value, [CallerArgumentExpression(nameof( internal static void ThrowIfZero(UInt32 value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value == 0) + { ThrowZero(value, paramName); + } } /// Throws an if is zero. /// The argument to validate as non-zero. @@ -148,7 +168,9 @@ internal static void ThrowIfZero(UInt32 value, [CallerArgumentExpression(nameof( internal static void ThrowIfZero(UInt64 value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value == 0) + { ThrowZero(value, paramName); + } } /// Throws an if is negative. @@ -157,7 +179,9 @@ internal static void ThrowIfZero(UInt64 value, [CallerArgumentExpression(nameof( internal static void ThrowIfNegative(Single value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value < 0) + { ThrowNegative(value, paramName); + } } /// Throws an if is negative. /// The argument to validate as non-negative. @@ -165,7 +189,9 @@ internal static void ThrowIfNegative(Single value, [CallerArgumentExpression(nam internal static void ThrowIfNegative(Double value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value < 0) + { ThrowNegative(value, paramName); + } } /// Throws an if is negative. /// The argument to validate as non-negative. @@ -173,7 +199,9 @@ internal static void ThrowIfNegative(Double value, [CallerArgumentExpression(nam internal static void ThrowIfNegative(SByte value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value < 0) + { ThrowNegative(value, paramName); + } } /// Throws an if is negative. /// The argument to validate as non-negative. @@ -181,7 +209,9 @@ internal static void ThrowIfNegative(SByte value, [CallerArgumentExpression(name internal static void ThrowIfNegative(Int16 value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value < 0) + { ThrowNegative(value, paramName); + } } /// Throws an if is negative. /// The argument to validate as non-negative. @@ -189,7 +219,9 @@ internal static void ThrowIfNegative(Int16 value, [CallerArgumentExpression(name internal static void ThrowIfNegative(Int32 value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value < 0) + { ThrowNegative(value, paramName); + } } /// Throws an if is negative. /// The argument to validate as non-negative. @@ -197,7 +229,9 @@ internal static void ThrowIfNegative(Int32 value, [CallerArgumentExpression(name internal static void ThrowIfNegative(Int64 value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value < 0) + { ThrowNegative(value, paramName); + } } /// Throws an if is negative or zero. @@ -206,7 +240,9 @@ internal static void ThrowIfNegative(Int64 value, [CallerArgumentExpression(name internal static void ThrowIfNegativeOrZero(Single value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value <= 0) + { ThrowNegativeOrZero(value, paramName); + } } /// Throws an if is negative or zero. /// The argument to validate as non-zero or non-negative. @@ -214,7 +250,9 @@ internal static void ThrowIfNegativeOrZero(Single value, [CallerArgumentExpressi internal static void ThrowIfNegativeOrZero(Double value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value <= 0) + { ThrowNegativeOrZero(value, paramName); + } } /// Throws an if is negative or zero. /// The argument to validate as non-zero or non-negative. @@ -222,7 +260,9 @@ internal static void ThrowIfNegativeOrZero(Double value, [CallerArgumentExpressi internal static void ThrowIfNegativeOrZero(SByte value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value <= 0) + { ThrowNegativeOrZero(value, paramName); + } } /// Throws an if is negative or zero. /// The argument to validate as non-zero or non-negative. @@ -230,7 +270,9 @@ internal static void ThrowIfNegativeOrZero(SByte value, [CallerArgumentExpressio internal static void ThrowIfNegativeOrZero(Int16 value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value <= 0) + { ThrowNegativeOrZero(value, paramName); + } } /// Throws an if is negative or zero. /// The argument to validate as non-zero or non-negative. @@ -238,7 +280,9 @@ internal static void ThrowIfNegativeOrZero(Int16 value, [CallerArgumentExpressio internal static void ThrowIfNegativeOrZero(Int32 value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value <= 0) + { ThrowNegativeOrZero(value, paramName); + } } /// Throws an if is negative or zero. /// The argument to validate as non-zero or non-negative. @@ -246,7 +290,9 @@ internal static void ThrowIfNegativeOrZero(Int32 value, [CallerArgumentExpressio internal static void ThrowIfNegativeOrZero(Int64 value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value <= 0) + { ThrowNegativeOrZero(value, paramName); + } } /// Throws an if is equal to . @@ -256,7 +302,9 @@ internal static void ThrowIfNegativeOrZero(Int64 value, [CallerArgumentExpressio internal static void ThrowIfEqual(T value, T other, [CallerArgumentExpression(nameof(value))] String? paramName = null) where T : IEquatable? { if(EqualityComparer.Default.Equals(value, other)) + { ThrowEqual(value, other, paramName); + } } /// Throws an if is not equal to . @@ -266,7 +314,9 @@ internal static void ThrowIfEqual(T value, T other, [CallerArgumentExpression internal static void ThrowIfNotEqual(T value, T other, [CallerArgumentExpression(nameof(value))] String? paramName = null) where T : IEquatable? { if(!EqualityComparer.Default.Equals(value, other)) + { ThrowNotEqual(value, other, paramName); + } } /// Throws an if is greater than . @@ -277,7 +327,9 @@ internal static void ThrowIfGreaterThan(T value, T other, [CallerArgumentExpr where T : IComparable { if(value.CompareTo(other) > 0) + { ThrowGreater(value, other, paramName); + } } /// Throws an if is greater than or equal . @@ -288,7 +340,9 @@ internal static void ThrowIfGreaterThanOrEqual(T value, T other, [CallerArgum where T : IComparable { if(value.CompareTo(other) >= 0) + { ThrowGreaterEqual(value, other, paramName); + } } /// Throws an if is less than . @@ -299,7 +353,9 @@ internal static void ThrowIfLessThan(T value, T other, [CallerArgumentExpress where T : IComparable { if(value.CompareTo(other) < 0) + { ThrowLess(value, other, paramName); + } } /// Throws an if is less than or equal . @@ -310,7 +366,9 @@ internal static void ThrowIfLessThanOrEqual(T value, T other, [CallerArgument where T : IComparable { if(value.CompareTo(other) <= 0) + { ThrowLessEqual(value, other, paramName); + } } } } diff --git a/UtilityGenerators/NonEquatable/NonEquatableGenerator.cs b/UtilityGenerators/NonEquatable/NonEquatableGenerator.cs index e69067a..3ee8947 100644 --- a/UtilityGenerators/NonEquatable/NonEquatableGenerator.cs +++ b/UtilityGenerators/NonEquatable/NonEquatableGenerator.cs @@ -27,7 +27,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) ct.ThrowIfCancellationRequested(); if(ctx.TargetSymbol is not INamedTypeSymbol target) + { return null; + } using var modelCtx = ModelCreationContext.CreateDefault(ct); var model = NamedTypeModel.Create(target, in modelCtx); @@ -52,7 +54,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .AppendCore("public "); if(m.Kind == PartialTypeKindModel.Class || m.Kind == PartialTypeKindModel.Record) + { sourceBuilder.AppendCore("sealed "); + } sourceBuilder.Append("override bool Equals(object obj) => throw new global::System.NotSupportedException(\"") .Append(displayName) @@ -65,7 +69,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .AppendCore("public "); if(m.Kind == PartialTypeKindModel.Class || m.Kind == PartialTypeKindModel.Record) + { sourceBuilder.AppendCore("sealed "); + } sourceBuilder.Append("override int GetHashCode() => throw new global::System.NotSupportedException(\"") .Append(displayName) diff --git a/UtilityGenerators/Templating/Lexer.cs b/UtilityGenerators/Templating/Lexer.cs index 33fb521..4cfd92b 100644 --- a/UtilityGenerators/Templating/Lexer.cs +++ b/UtilityGenerators/Templating/Lexer.cs @@ -278,7 +278,9 @@ private ReadOnlySpan PeekSpan(Int32 lookahead, Int32 maxLength) private Char Peek(Int32 lookahead = 0) { if(IsAtEnd(lookahead)) + { return '\0'; + } var result = _reifiedTemplateString[_index + _length + lookahead]; diff --git a/UtilityGenerators/Templating/Parser.cs b/UtilityGenerators/Templating/Parser.cs index ca78708..a798a04 100644 --- a/UtilityGenerators/Templating/Parser.cs +++ b/UtilityGenerators/Templating/Parser.cs @@ -67,7 +67,9 @@ private TemplateSyntax Template() : new(new EmptyTemplateSyntax()); if(!IsAtEnd()) + { EmitDiagnostic(Ids.UnexpectedToken, DiagnosticSeverity.Error); + } return result; } @@ -720,7 +722,9 @@ private void Return(TSyntax? syntax) where TSyntax : ISyntax { if(syntax is null) + { return; + } _currentIndex -= syntax.CountTokens(_ct); } @@ -737,7 +741,9 @@ private Boolean Peek( Int32 lookahead = 0) { if(Peek(lookahead) == kind) + { return true; + } return false; } @@ -750,7 +756,9 @@ private Boolean Peek( foreach(var kind in kinds) { if(kind == peeked) + { return true; + } } return false; diff --git a/UtilityGenerators/Templating/SourcePosition.cs b/UtilityGenerators/Templating/SourcePosition.cs index d0e8588..e45d251 100644 --- a/UtilityGenerators/Templating/SourcePosition.cs +++ b/UtilityGenerators/Templating/SourcePosition.cs @@ -50,13 +50,24 @@ public SourcePosition(Int32 line, Int32 character) public Int32 CompareTo(SourcePosition other) { if(Line > other.Line) + { return 1; + } + if(Line < other.Line) + { return -1; + } + if(Character > other.Character) + { return 1; + } + if(Character < other.Character) + { return -1; + } return 0; } diff --git a/UtilityGenerators/Templating/Syntax/Visitors/CommentXmlTreeStringBuilder.cs b/UtilityGenerators/Templating/Syntax/Visitors/CommentXmlTreeStringBuilder.cs index 3e7c8df..cc28a27 100644 --- a/UtilityGenerators/Templating/Syntax/Visitors/CommentXmlTreeStringBuilder.cs +++ b/UtilityGenerators/Templating/Syntax/Visitors/CommentXmlTreeStringBuilder.cs @@ -51,7 +51,9 @@ protected override void Append(String production, Action'); if(appendNewLine) + { Builder.AppendLineCore(); + } } protected override void Append(String production, Boolean appendNewLine = true) { @@ -64,6 +66,8 @@ protected override void Append(String production, Boolean appendNewLine = true) .AppendLineCore(); if(appendNewLine) + { Builder.AppendLineCore(); + } } } diff --git a/UtilityGenerators/Templating/Syntax/Visitors/SourceGeneratingTemplateVisitor.CodeBlockVisitor.cs b/UtilityGenerators/Templating/Syntax/Visitors/SourceGeneratingTemplateVisitor.CodeBlockVisitor.cs index 6a74963..0b4631d 100644 --- a/UtilityGenerators/Templating/Syntax/Visitors/SourceGeneratingTemplateVisitor.CodeBlockVisitor.cs +++ b/UtilityGenerators/Templating/Syntax/Visitors/SourceGeneratingTemplateVisitor.CodeBlockVisitor.cs @@ -18,11 +18,15 @@ public override void Visit(WhitespacesSyntax syntax) { } protected override void OnToken(Token token) { if(token.TemplateString.Path is not { Length: > 0 } path) + { return; + } var detentCount = 0; for(; detentCount < parent._builder.OpenBlocks; detentCount++) + { parent._builder.DetentCore(); + } parent._builder .Append("#line (") @@ -44,7 +48,9 @@ protected override void OnToken(Token token) .AppendLineCore(); for(; detentCount > 0; detentCount--) + { parent._builder.IndentCore(); + } } } } diff --git a/UtilityGenerators/Templating/Syntax/Visitors/SourceGeneratingTemplateVisitor.TemplateBlockVisitor.cs b/UtilityGenerators/Templating/Syntax/Visitors/SourceGeneratingTemplateVisitor.TemplateBlockVisitor.cs index f7ddf4b..5c185de 100644 --- a/UtilityGenerators/Templating/Syntax/Visitors/SourceGeneratingTemplateVisitor.TemplateBlockVisitor.cs +++ b/UtilityGenerators/Templating/Syntax/Visitors/SourceGeneratingTemplateVisitor.TemplateBlockVisitor.cs @@ -10,7 +10,7 @@ internal sealed partial class SourceGeneratingTemplateVisitor private sealed class TemplateBlockVisitor(SourceGeneratingTemplateVisitor parent, CancellationToken ct) : TreeWalkingSyntaxVisitor(ct) { private Int32 _index = -1; - private Int32 _length = 0; + private Int32 _length; public override void Visit(RenderBlockSyntax syntax) { @@ -39,7 +39,9 @@ public void Flush() Ct.ThrowIfCancellationRequested(); if(_index == -1) + { return; + } if(_length != 0) { @@ -103,7 +105,9 @@ private void OnSpans(TokenSpans spans) Ct.ThrowIfCancellationRequested(); if(_index == -1) + { _index = spans.NewlineAwareTemplateSpan.Index; + } _length += spans.NewlineAwareTemplateSpan.Length; } diff --git a/UtilityGenerators/Templating/Syntax/Visitors/SourceGeneratingTemplateVisitor.cs b/UtilityGenerators/Templating/Syntax/Visitors/SourceGeneratingTemplateVisitor.cs index c0035d7..950f3ec 100644 --- a/UtilityGenerators/Templating/Syntax/Visitors/SourceGeneratingTemplateVisitor.cs +++ b/UtilityGenerators/Templating/Syntax/Visitors/SourceGeneratingTemplateVisitor.cs @@ -45,7 +45,9 @@ public override void Visit(RenderBlockSyntax syntax) var detentCount = 0; for(; detentCount < _builder.OpenBlocks; detentCount++) + { _builder.DetentCore(); + } _builder .Append("#line (") @@ -59,7 +61,9 @@ public override void Visit(RenderBlockSyntax syntax) .AppendCore(')'); if(_templateString.Path is { Length: > 0 } path) + { _builder.Append(" \"").Append(path).AppendCore('"'); + } _builder .AppendLine() @@ -73,7 +77,9 @@ public override void Visit(RenderBlockSyntax syntax) .AppendLineCore(); for(; detentCount > 0; detentCount--) + { _builder.IndentCore(); + } if(syntax.RenderBlockBody is { }) { diff --git a/UtilityGenerators/Templating/Syntax/Visitors/TemplateStringReconstructionVisitor.cs b/UtilityGenerators/Templating/Syntax/Visitors/TemplateStringReconstructionVisitor.cs index 0fc985b..6743179 100644 --- a/UtilityGenerators/Templating/Syntax/Visitors/TemplateStringReconstructionVisitor.cs +++ b/UtilityGenerators/Templating/Syntax/Visitors/TemplateStringReconstructionVisitor.cs @@ -15,8 +15,12 @@ protected override void OnToken(Token token) Ct.ThrowIfCancellationRequested(); if(token.Kind is TokenKind.Newline) + { builder.AppendCore(newline); + } else + { builder.AppendCore(token.Lexeme.ToString()); + } } } diff --git a/UtilityGenerators/Templating/Syntax/Visitors/TokenValidator.cs b/UtilityGenerators/Templating/Syntax/Visitors/TokenValidator.cs index 48a5e44..e734032 100644 --- a/UtilityGenerators/Templating/Syntax/Visitors/TokenValidator.cs +++ b/UtilityGenerators/Templating/Syntax/Visitors/TokenValidator.cs @@ -15,7 +15,9 @@ private void AssertToken(Token token, TokenKind type, String lexeme, TS AssertToken(token, type, l => { if(l.Equals(lexeme)) + { return (true, null); + } return (false, $"expected:\n\n{lexeme}"); }, syntax); @@ -31,14 +33,18 @@ private void AssertLexeme(Token token, Func Ct.ThrowIfCancellationRequested(); if(lexemePredicate.Invoke(token.Lexeme.ToString()) is (var success, var message) && !success) + { OnError($"lexeme mismatch:\n{message}\n\nlexeme\n{token.Lexeme.ToString()}\ntoken:\n{token}\nsyntax:\n{syntax}"); + } } private void AssertKind(Token token, TokenKind kind, TSyntax syntax) { Ct.ThrowIfCancellationRequested(); if(kind != token.Kind) + { OnError($"token kind mismatch: expected '{kind}', actual was '{token.Kind}'\ntoken:\n{token}\nsyntax:\n{syntax}"); + } } protected virtual void OnError(String message) => throw new InvalidOperationException(message); diff --git a/UtilityGenerators/Templating/Syntax/Visitors/XmlTreeStringBuilder.cs b/UtilityGenerators/Templating/Syntax/Visitors/XmlTreeStringBuilder.cs index 31f966e..b53c254 100644 --- a/UtilityGenerators/Templating/Syntax/Visitors/XmlTreeStringBuilder.cs +++ b/UtilityGenerators/Templating/Syntax/Visitors/XmlTreeStringBuilder.cs @@ -53,7 +53,9 @@ protected override void Append(String production, Action'); if(appendNewLine) + { Builder.AppendLineCore(); + } } protected override void Append(String production, Boolean appendNewLine = true) { @@ -66,6 +68,8 @@ protected override void Append(String production, Boolean appendNewLine = true) .AppendLineCore(); if(appendNewLine) + { Builder.AppendLineCore(); + } } } diff --git a/UtilityGenerators/Templating/TemplateAttribute.Model.cs b/UtilityGenerators/Templating/TemplateAttribute.Model.cs index 36e507b..5bb22c5 100644 --- a/UtilityGenerators/Templating/TemplateAttribute.Model.cs +++ b/UtilityGenerators/Templating/TemplateAttribute.Model.cs @@ -6,7 +6,7 @@ namespace RhoMicro.CodeAnalysis; internal partial class TemplateAttribute { - public partial record Model + public partial record struct Model { public String NewlineValue => Newline switch { diff --git a/UtilityGenerators/Templating/TemplateAttribute.cs b/UtilityGenerators/Templating/TemplateAttribute.cs index f4b721f..89d07aa 100644 --- a/UtilityGenerators/Templating/TemplateAttribute.cs +++ b/UtilityGenerators/Templating/TemplateAttribute.cs @@ -16,7 +16,7 @@ namespace RhoMicro.CodeAnalysis; [NonEquatable] #endif #if RHOMICRO_CODEANALYSIS_UTILITYGENERATORS || RHOMICRO_CODEANALYSIS_UTILITYGENERATORS_DEV -[GenerateFactory] +[GenerateFactory(GenerateModelTypeAsStruct = true)] #endif internal sealed partial class TemplateAttribute : Attribute { @@ -99,14 +99,7 @@ internal sealed partial class TemplateAttribute : Attribute /// static global::RhoMicro.CodeAnalysis.Library.Text.Templating.Indentations /// /// - [DefaultValue( - [ - "static global::RhoMicro.CodeAnalysis.Library.Text.Templating.Indentations", - ])] - public String[] Usings { get; set; } = - [ - "static global::RhoMicro.CodeAnalysis.Library.Text.Templating.Indentations", - ]; + public String[]? Usings { get; set; } /// /// Gets or sets a value indicating whether to generate a structural /// representation and debugging information of the template for debugging diff --git a/UtilityGenerators/Templating/TemplateString.cs b/UtilityGenerators/Templating/TemplateString.cs index 072b3cc..5f8e0af 100644 --- a/UtilityGenerators/Templating/TemplateString.cs +++ b/UtilityGenerators/Templating/TemplateString.cs @@ -108,7 +108,9 @@ private static Int32 GetOpeningQuoteCount(in SyntaxToken token, CancellationToke var i = 0; for(; token.Text[i] == '"'; i++) + { cancellationToken.ThrowIfCancellationRequested(); + } return i; } diff --git a/UtilityGenerators/Templating/TemplatingGenerator.cs b/UtilityGenerators/Templating/TemplatingGenerator.cs index 2cd8ac4..c7d3c14 100644 --- a/UtilityGenerators/Templating/TemplatingGenerator.cs +++ b/UtilityGenerators/Templating/TemplatingGenerator.cs @@ -3,10 +3,8 @@ namespace RhoMicro.CodeAnalysis; using System; - using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; - using RhoMicro.CodeAnalysis.Library.Models; using RhoMicro.CodeAnalysis.Library.Text.SourceTexts; using RhoMicro.CodeAnalysis.Library.Text.Templating; @@ -26,36 +24,37 @@ public class TemplatingGenerator : IIncrementalGenerator /// public void Initialize(IncrementalGeneratorInitializationContext context) { - var provider = context.SyntaxProvider.ForAttributeWithMetadataName<(NamedTypeModel, TemplateString, TemplateAttribute.Model)?>( - _attributeMetadataName, - static (n, _) => n is RecordDeclarationSyntax or ClassDeclarationSyntax or StructDeclarationSyntax, - static (ctx, ct) => - { - ct.ThrowIfCancellationRequested(); - - if(ctx is not + var provider = context.SyntaxProvider + .ForAttributeWithMetadataName<(NamedTypeModel, TemplateString, TemplateAttribute.Model)?>( + _attributeMetadataName, + static (n, _) => n is RecordDeclarationSyntax or ClassDeclarationSyntax or StructDeclarationSyntax, + static (ctx, ct) => + { + ct.ThrowIfCancellationRequested(); + + if (ctx is not + { + TargetSymbol: INamedTypeSymbol target, + Attributes: [{ } attributeData, ..] + } + || !attributeData.TryGetTemplateAttributeModel(out var attribute, cancellationToken: ct) + || attributeData.ApplicationSyntaxReference?.GetSyntax(ct) is not AttributeSyntax + { + ArgumentList.Arguments: [{ Expression: LiteralExpressionSyntax { Token: var token } }, ..] + }) { - TargetSymbol: INamedTypeSymbol target, - Attributes: [{ } attributeData, ..] + return null; } - || !attributeData.TryGetTemplateAttributeModel(out var attribute, cancellationToken: ct) - || attributeData.ApplicationSyntaxReference?.GetSyntax(ct) is not AttributeSyntax - { - ArgumentList.Arguments: [{ Expression: LiteralExpressionSyntax { Token: var token } }, ..] - }) - { - return null; - } - using var modelCtx = ModelCreationContext.CreateDefault(ct); - var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); - cts.CancelAfter(_parserTimeout); - var templateString = TemplateString.Create(token, cts.Token); - var typeModel = NamedTypeModel.Create(target, in modelCtx); - var result = (typeModel, templateString, attribute); + using var modelCtx = ModelCreationContext.CreateDefault(ct); + var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); + cts.CancelAfter(_parserTimeout); + var templateString = TemplateString.Create(token, cts.Token); + var typeModel = NamedTypeModel.Create(target, in modelCtx); + var result = (typeModel, templateString, attribute); - return result; - }) + return result; + }) .Where(static t => t.HasValue) .Select(static (t, ct) => { @@ -85,6 +84,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var isbProvider = provider.Select(IsbImpl); context.RegisterSourceOutput(isbProvider, (ctx, t) => ctx.AddSource(t.hintName, t.source)); } + private static (String hintName, String source) IsbImpl( (NamedTypeModel type, ParseResult parseResult, TemplateAttribute.Model attribute) t, CancellationToken ct) @@ -93,8 +93,7 @@ private static (String hintName, String source) IsbImpl( var sourceBuilder = new IndentedStringBuilder(IndentedStringBuilderOptions.GeneratedFile with { - AmbientCancellationToken = ct, - GeneratorName = typeof(TemplatingGenerator).FullName + AmbientCancellationToken = ct, GeneratorName = typeof(TemplatingGenerator).FullName }); var (type, parseResult, attribute) = t; @@ -103,8 +102,10 @@ private static (String hintName, String source) IsbImpl( var templateString = scanResult.TemplateString; var diagnostics = parseResult.AllDiagnostics; - foreach(var u in attribute.Usings) + foreach (var u in attribute.Usings ?? []) + { sourceBuilder.Append("using ").Append(u).Append(';').AppendLineCore(); + } type.BuildStrings( sourceBuilder, @@ -113,53 +114,52 @@ private static (String hintName, String source) IsbImpl( ["global::RhoMicro.CodeAnalysis.Library.Text.Templating.ITemplate"], ct); - if(attribute.GenerateToString) + if (attribute.GenerateToString) { sourceBuilder .AppendLine("public override string ToString()") .Indent() - .AppendLine("=> global::RhoMicro.CodeAnalysis.Library.Text.Templating.TemplateRenderer.Render(this);") - .AppendLine() + .AppendLine("=> global::RhoMicro.CodeAnalysis.Library.Text.Templating.TemplateRenderer.Render(this);") + .AppendLine() .Detent() - .Append("public string RenderToString<") .Append(attribute.BodyParameterTypeName) .Append(">(in ") .Append(attribute.BodyParameterTypeName).AppendLine(" body)") .Indent() - .Append("where ").Append(attribute.BodyParameterTypeName).AppendLine(" : global::RhoMicro.CodeAnalysis.Library.Text.Templating.ITemplate") - .Append("=> global::RhoMicro.CodeAnalysis.Library.Text.Templating.TemplateRenderer.Render<") - .Append(displayString) - .Append(", ") - .Append(attribute.BodyParameterTypeName) - .AppendLine(">(this, body);") - .AppendLine() + .Append("where ").Append(attribute.BodyParameterTypeName) + .AppendLine(" : global::RhoMicro.CodeAnalysis.Library.Text.Templating.ITemplate") + .Append("=> global::RhoMicro.CodeAnalysis.Library.Text.Templating.TemplateRenderer.Render<") + .Append(displayString) + .Append(", ") + .Append(attribute.BodyParameterTypeName) + .AppendLine(">(this, body);") + .AppendLine() .Detent() - .Append("public string RenderToString<") .Append(attribute.BodyParameterTypeName) .Append(">(in ") .Append(attribute.BodyParameterTypeName) .AppendLine(" body, global::System.Threading.CancellationToken cancellationToken)") .Indent() - .Append("where ").Append(attribute.BodyParameterTypeName).AppendLine(" : global::RhoMicro.CodeAnalysis.Library.Text.Templating.ITemplate") - .Append("=> global::RhoMicro.CodeAnalysis.Library.Text.Templating.TemplateRenderer.Render<") - .Append(displayString) - .Append(", ") - .Append(attribute.BodyParameterTypeName) - .AppendLine(">(this, body, cancellationToken);") - .AppendLine() + .Append("where ").Append(attribute.BodyParameterTypeName) + .AppendLine(" : global::RhoMicro.CodeAnalysis.Library.Text.Templating.ITemplate") + .Append("=> global::RhoMicro.CodeAnalysis.Library.Text.Templating.TemplateRenderer.Render<") + .Append(displayString) + .Append(", ") + .Append(attribute.BodyParameterTypeName) + .AppendLine(">(this, body, cancellationToken);") + .AppendLine() .Detent() - .AppendLine( "public string RenderToString(" + "global::System.Threading.CancellationToken cancellationToken)") .Indent() - .AppendLine("=> " + - "global::RhoMicro.CodeAnalysis.Library.Text.Templating.TemplateRenderer.Render(" + - "this, cancellationToken" + - ");") - .AppendLine() + .AppendLine("=> " + + "global::RhoMicro.CodeAnalysis.Library.Text.Templating.TemplateRenderer.Render(" + + "this, cancellationToken" + + ");") + .AppendLine() .DetentCore(); } @@ -168,25 +168,29 @@ private static (String hintName, String source) IsbImpl( .Append(attribute.BodyParameterTypeName) .AppendLine(">(") .Indent() - .Append("ref global::RhoMicro.CodeAnalysis.Library.Text.Templating.TemplateRenderer ") - .Append(attribute.RendererParameterName).AppendLine(',') - .Append(attribute.BodyParameterTypeName).Append(' ') - .Append(attribute.BodyParameterName).AppendLine(',') - .Append("global::System.Threading.CancellationToken ") - .Append(attribute.CancellationTokenParameterName).AppendLine(')') - .Append("where ").Append(attribute.BodyParameterTypeName).Append(" : global::RhoMicro.CodeAnalysis.Library.Text.Templating.ITemplate") + .Append("ref global::RhoMicro.CodeAnalysis.Library.Text.Templating.TemplateRenderer ") + .Append(attribute.RendererParameterName).AppendLine(',') + .Append(attribute.BodyParameterTypeName).Append(' ') + .Append(attribute.BodyParameterName).AppendLine(',') + .Append("global::System.Threading.CancellationToken ") + .Append(attribute.CancellationTokenParameterName).AppendLine(')') + .Append("where ").Append(attribute.BodyParameterTypeName) + .Append(" : global::RhoMicro.CodeAnalysis.Library.Text.Templating.ITemplate") .Detent() .OpenBracesBlock() .Append(attribute.CancellationTokenParameterName).AppendLine(".ThrowIfCancellationRequested();") .AppendLine() .Append("// This template was taken from: ") .Append(templateString.Path) - .Append('(').Append(templateString.Start.Line.ToString()).Append(',').Append(templateString.Start.Character.ToString()).AppendLine(')') + .Append('(').Append(templateString.Start.Line.ToString()).Append(',') + .Append(templateString.Start.Character.ToString()).AppendLine(')') .Append("const string __template =").AppendLineCore(); var detentCount = 0; - for(; detentCount < sourceBuilder.OpenBlocks; detentCount++) + for (; detentCount < sourceBuilder.OpenBlocks; detentCount++) + { sourceBuilder.DetentCore(); + } var quotes = new String('"', scanResult.RequiredQuotes); sourceBuilder @@ -195,17 +199,19 @@ private static (String hintName, String source) IsbImpl( template.Accept( new TemplateStringReconstructionVisitor( - newline: attribute.NewlineValue, - sourceBuilder, - ct)); + newline: attribute.NewlineValue, + sourceBuilder, + ct)); sourceBuilder .AppendLine() .Append(quotes) .AppendCore(";"); - for(; detentCount > 0; detentCount--) + for (; detentCount > 0; detentCount--) + { sourceBuilder.IndentCore(); + } sourceBuilder.AppendLine().AppendLineCore(); @@ -217,6 +223,7 @@ private static (String hintName, String source) IsbImpl( return (hintName, source); } + private static void AppendTemplate( IndentedStringBuilder sourceBuilder, TemplateSyntax template, @@ -228,11 +235,13 @@ private static void AppendTemplate( template.Accept(new SourceGeneratingTemplateVisitor(sourceBuilder, attribute, templateString, ct)); } - private static void AppendDiagnostics(IndentedStringBuilder sourceBuilder, IEnumerable diagnostics, CancellationToken ct) + + private static void AppendDiagnostics(IndentedStringBuilder sourceBuilder, + IEnumerable diagnostics, CancellationToken ct) { ct.ThrowIfCancellationRequested(); - foreach(var diagnostic in diagnostics) + foreach (var diagnostic in diagnostics) { sourceBuilder .Append("// ") @@ -240,6 +249,7 @@ private static void AppendDiagnostics(IndentedStringBuilder sourceBuilder, IEnum .AppendLineCore(); } } + private static void AppendDebugComment( IndentedStringBuilder sourceBuilder, TemplateAttribute.Model attribute, @@ -248,8 +258,10 @@ private static void AppendDebugComment( { ct.ThrowIfCancellationRequested(); - if(!attribute.GenerateDebugInfo) + if (!attribute.GenerateDebugInfo) + { return; + } var xmlTree = template.ToCommentXmlTreeString(ct); diff --git a/UtilityGenerators/TypeSymbolPattern/TypeSymbolPatternGenerator.cs b/UtilityGenerators/TypeSymbolPattern/TypeSymbolPatternGenerator.cs index 057c072..1f7d8df 100644 --- a/UtilityGenerators/TypeSymbolPattern/TypeSymbolPatternGenerator.cs +++ b/UtilityGenerators/TypeSymbolPattern/TypeSymbolPatternGenerator.cs @@ -5,7 +5,6 @@ namespace RhoMicro.CodeAnalysis; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; - using RhoMicro.CodeAnalysis.Library.Models; using RhoMicro.CodeAnalysis.Library.Text.SourceTexts; @@ -15,37 +14,49 @@ namespace RhoMicro.CodeAnalysis; [Generator(LanguageNames.CSharp)] public partial class TypeSymbolPatternGenerator : IIncrementalGenerator { - [TypeSymbolPattern(typeof(ITypeSymbol))] - private static partial Boolean IsTypeSymbol(ITypeSymbol type); + // [TypeSymbolPattern(typeof(ITypeSymbol))] + // private static partial Boolean IsTypeSymbol(ITypeSymbol type); /// public void Initialize(IncrementalGeneratorInitializationContext context) { var provider = context.SyntaxProvider.ForAttributeWithMetadataName( - "RhoMicro.CodeAnalysis.TypeSymbolPatternAttribute", - static (n, _) => n is MethodDeclarationSyntax - { - Modifiers: [.., { RawKind: (Int32)SyntaxKind.PartialKeyword }] - }, - static (ctx, ct) => - { - ct.ThrowIfCancellationRequested(); + "RhoMicro.CodeAnalysis.TypeSymbolPatternAttribute", + static (n, _) => n is MethodDeclarationSyntax + { + Modifiers: [.., { RawKind: (Int32)SyntaxKind.PartialKeyword }] + }, + static (ctx, ct) => + { + ct.ThrowIfCancellationRequested(); - // The target must be partial, return bool, and have single parameter of type ITypeSymbol. - if(ctx.TargetSymbol is not IMethodSymbol + // The target must be partial, return bool, and have single parameter of type ITypeSymbol. + if (ctx.TargetSymbol is not IMethodSymbol + { + ReturnType.SpecialType: SpecialType.System_Boolean, + IsPartialDefinition: true, + Parameters: [{ Type: { + Name: nameof(ITypeSymbol), + ContainingNamespace: + { + Name: "CodeAnalysis", + ContainingNamespace: + { + Name: "Microsoft", + ContainingNamespace.IsGlobalNamespace: true + } + } + } } + ] + } target) { - ReturnType.SpecialType: SpecialType.System_Boolean, - IsPartialDefinition: true, - Parameters: [{ } singleParameter] - } target || !IsTypeSymbol(singleParameter.Type)) - { - return null; - } + return null; + } - using var modelCtx = ModelCreationContext.CreateDefault(ct); - var result = TypeSymbolPatternMethodsPartialModel.Create(target, ctx.Attributes[0], in modelCtx); + using var modelCtx = ModelCreationContext.CreateDefault(ct); + var result = TypeSymbolPatternMethodsPartialModel.Create(target, ctx.Attributes[0], in modelCtx); - return result; - }).Where(static m => m is not null) + return result; + }).Where(static m => m is not null) .Collect() .SelectMany(static (m, ct) => { @@ -62,14 +73,15 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var sourceBuilder = new IndentedStringBuilder(IndentedStringBuilderOptions.GeneratedFile with { - GeneratorName = typeof(TypeSymbolPatternGenerator).FullName, - AmbientCancellationToken = ct + GeneratorName = typeof(TypeSymbolPatternGenerator).FullName, AmbientCancellationToken = ct }); m.ContainingType.BuildStrings(sourceBuilder, out var hintName, out _, ct); - foreach(var method in m.Methods) + foreach (var method in m.Methods) + { AppendPatternMethods(sourceBuilder, method, ct); + } var source = sourceBuilder .CloseAllBlocks() @@ -81,24 +93,30 @@ public void Initialize(IncrementalGeneratorInitializationContext context) context.RegisterSourceOutput(provider, (ctx, t) => ctx.AddSource(t.hintName, t.source)); } - private static void AppendPatternMethods(IndentedStringBuilder sourceBuilder, TypeSymbolPatternMethodModel method, CancellationToken ct) + private static void AppendPatternMethods(IndentedStringBuilder sourceBuilder, TypeSymbolPatternMethodModel method, + CancellationToken ct) { ct.ThrowIfCancellationRequested(); AppendPatternMethod(sourceBuilder, method, ct); AppendPatternOutMethod(sourceBuilder, method, ct); } - private static void AppendPatternOutMethod(IndentedStringBuilder sourceBuilder, TypeSymbolPatternMethodModel method, CancellationToken ct) + + private static void AppendPatternOutMethod(IndentedStringBuilder sourceBuilder, TypeSymbolPatternMethodModel method, + CancellationToken ct) { ct.ThrowIfCancellationRequested(); sourceBuilder.AppendCore(SyntaxFacts.GetText(method.Accessibility)); - if(method.IsStatic) + if (method.IsStatic) + { sourceBuilder.AppendCore(" static"); + } sourceBuilder - .Append(" bool ").Append(method.MethodName).AppendCore("(global::Microsoft.CodeAnalysis.ITypeSymbol? type, [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out "); + .Append(" bool ").Append(method.MethodName).AppendCore( + "(global::Microsoft.CodeAnalysis.ITypeSymbol? type, [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out "); AppendPatternTypeType(sourceBuilder, method, ct); @@ -115,17 +133,22 @@ private static void AppendPatternOutMethod(IndentedStringBuilder sourceBuilder, .Append("return result;") .CloseBlockCore(); } - private static void AppendPatternMethod(IndentedStringBuilder sourceBuilder, TypeSymbolPatternMethodModel method, CancellationToken ct) + + private static void AppendPatternMethod(IndentedStringBuilder sourceBuilder, TypeSymbolPatternMethodModel method, + CancellationToken ct) { ct.ThrowIfCancellationRequested(); sourceBuilder.AppendCore(SyntaxFacts.GetText(method.Accessibility)); - if(method.IsStatic) + if (method.IsStatic) + { sourceBuilder.AppendCore(" static"); + } sourceBuilder - .Append(" partial bool ").Append(method.MethodName).AppendCore("(global::Microsoft.CodeAnalysis.ITypeSymbol? type"); + .Append(" partial bool ").Append(method.MethodName) + .AppendCore("(global::Microsoft.CodeAnalysis.ITypeSymbol? type"); sourceBuilder .Append(')') @@ -144,29 +167,32 @@ private static void AppendPatternMethod(IndentedStringBuilder sourceBuilder, Typ .CloseBlockCore(); } - private static void AppendTypePattern(IndentedStringBuilder sourceBuilder, TypeModel type, Boolean checkTypeArguments, ref CancellationToken ct) + private static void AppendTypePattern(IndentedStringBuilder sourceBuilder, TypeModel type, + Boolean checkTypeArguments, ref CancellationToken ct) { ct.ThrowIfCancellationRequested(); - if(type is NamedTypeModel named) + if (type is NamedTypeModel named) { sourceBuilder .Append("global::Microsoft.CodeAnalysis.INamedTypeSymbol") .OpenBracesBlock() - .Append("Name: \"").Append(type.Name).Append("\",").AppendLineCore(); + .Append("Name: \"").Append(type.Name).Append("\",").AppendLineCore(); - if(checkTypeArguments) + if (checkTypeArguments) { _ = sourceBuilder .Append("TypeArguments:") .OpenBracketsBlock(); - for(var i = 0; i < named.TypeArguments.Count; i++) + for (var i = 0; i < named.TypeArguments.Count; i++) { ct.ThrowIfCancellationRequested(); - if(i != 0) + if (i != 0) + { sourceBuilder.AppendCore(", "); + } var arg = named.TypeArguments[i]; AppendTypePattern(sourceBuilder, arg, checkTypeArguments, ref ct); @@ -179,35 +205,38 @@ private static void AppendTypePattern(IndentedStringBuilder sourceBuilder, TypeM } AppendNamespacePatternPart(sourceBuilder, type, ct); - } else if(type is ArrayTypeModel array) + } + else if (type is ArrayTypeModel array) { sourceBuilder .Append("global::Microsoft.CodeAnalysis.IArrayTypeSymbol") .OpenBracesBlock() - .AppendCore("ElementType: "); + .AppendCore("ElementType: "); AppendTypePattern(sourceBuilder, array.ElementType, checkTypeArguments, ref ct); sourceBuilder.AppendLineCore(); - } else + } + else { sourceBuilder .OpenBracesBlock() - .Append("Name: \"").Append(type.Name).Append("\",").AppendLineCore(); + .Append("Name: \"").Append(type.Name).Append("\",").AppendLineCore(); AppendNamespacePatternPart(sourceBuilder, type, ct); } sourceBuilder.CloseBlockCore(); } - private static void AppendNamespacePatternPart(IndentedStringBuilder sourceBuilder, TypeModel type, CancellationToken ct) + private static void AppendNamespacePatternPart(IndentedStringBuilder sourceBuilder, TypeModel type, + CancellationToken ct) { ct.ThrowIfCancellationRequested(); sourceBuilder .AppendCore("ContainingNamespace: "); - for(var i = type.NamespaceParts.Count - 1; i > -1; i--) + for (var i = type.NamespaceParts.Count - 1; i > -1; i--) { ct.ThrowIfCancellationRequested(); @@ -223,17 +252,24 @@ private static void AppendNamespacePatternPart(IndentedStringBuilder sourceBuild .OpenBracesBlock() .AppendCore("IsGlobalNamespace: true"); - for(var i = -1; i < type.NamespaceParts.Count; i++) + for (var i = -1; i < type.NamespaceParts.Count; i++) + { sourceBuilder.CloseBlockCore(); + } } - private static void AppendPatternTypeType(IndentedStringBuilder sourceBuilder, TypeSymbolPatternMethodModel method, CancellationToken ct) + private static void AppendPatternTypeType(IndentedStringBuilder sourceBuilder, TypeSymbolPatternMethodModel method, + CancellationToken ct) { ct.ThrowIfCancellationRequested(); - if(method.Type.Type is ArrayTypeModel) + if (method.Type.Type is ArrayTypeModel) + { sourceBuilder.AppendCore("global::Microsoft.CodeAnalysis.IArrayTypeSymbol"); - else if(method.Type.Type is NamedTypeModel) + } + else if (method.Type.Type is NamedTypeModel) + { sourceBuilder.AppendCore("global::Microsoft.CodeAnalysis.INamedTypeSymbol"); + } } } diff --git a/UtilityGenerators/bootstrap.sh b/UtilityGenerators/bootstrap.sh new file mode 100644 index 0000000..c416f16 --- /dev/null +++ b/UtilityGenerators/bootstrap.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# Default configuration +CONFIGURATION="Debug" + +# Get the directory of the script +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +PROPS_PATH="$SCRIPT_DIR/../Directory.Build.props" + +# Increment assembly revision +REVISION=$(grep -oP '(?<=)\d+' "$PROPS_PATH") +NEW_REVISION=$((REVISION + 1)) +sed -i "s/$REVISION$NEW_REVISION /dev/null 2>&1 +if [ $? -ne 0 ]; then + echo -e "\033[31mError while building UtilityGenerators.csproj\033[0m" + exit 1 +else + echo -e "\033[32mBuilt UtilityGenerators.csproj\033[0m" +fi + +# Build UtilityGenerators.Dev.csproj +echo -e "\033[33mBuilding UtilityGenerators.Dev.csproj\033[0m" +dotnet build "$SCRIPT_DIR/../UtilityGenerators.Dev/UtilityGenerators.Dev.csproj" -c "$CONFIGURATION" -v q -p:SolutionName="RhoMicro.CodeAnalysis" > /dev/null 2>&1 +if [ $? -ne 0 ]; then + echo -e "\033[31mError while building UtilityGenerators.Dev.csproj\033[0m" + exit 1 +else + echo -e "\033[32mBuilt UtilityGenerators.Dev.csproj\033[0m" +fi + +# Restore UtilityGenerators.csproj +echo -e "\033[33mRestoring UtilityGenerators.csproj\033[0m" +dotnet restore --force > /dev/null 2>&1 +if [ $? -ne 0 ]; then + echo -e "\033[31mError while restoring UtilityGenerators.csproj\033[0m" + exit 1 +else + echo -e "\033[32mRestored UtilityGenerators.csproj\033[0m" +fi diff --git a/VisitorGenerator/Generators/Models/NodeModel.cs b/VisitorGenerator/Generators/Models/NodeModel.cs index ad33675..306f2f4 100644 --- a/VisitorGenerator/Generators/Models/NodeModel.cs +++ b/VisitorGenerator/Generators/Models/NodeModel.cs @@ -26,22 +26,34 @@ public static void AddModels( ctx.ThrowIfCancellationRequested(); if (SymbolEqualityComparer.Default.Equals(nodeTypeCandidate, baseNodeType)) + { return; + } if (!handledTypes.Add(nodeTypeCandidate)) + { return; + } if (nodeTypeCandidate is not INamedTypeSymbol namedNodeTypeCandidate) + { return; + } if (namedNodeTypeCandidate.ContainingType is not null) + { return; + } if (!namedNodeTypeCandidate.Inherits(baseNodeType)) + { return; + } if (!NodeSignatureModel.TryCreate(namedNodeTypeCandidate, out var signature, in ctx)) + { return; + } var properties = ctx.CollectionFactory.CreateList(); foreach (var member in namedNodeTypeCandidate.GetMembers()) @@ -49,14 +61,18 @@ public static void AddModels( ctx.ThrowIfCancellationRequested(); if (PropertyModel.TryCreate(member, baseNodeType, out var property, in ctx)) + { properties.Add(property); + } } var model = new NodeModel(properties, signature, baseSignature); models.Add(model); if (nodeTypeCandidate.BaseType is { } baseNodeTypeCandidate) + { AddModels(baseNodeTypeCandidate, baseNodeType, baseSignature, models, handledTypes, in ctx); + } } public Boolean IsLeaf() => Signature.Flags.HasFlag(NodeSignatureFlags.IsSealed); diff --git a/VisitorGenerator/Generators/Models/NodeSignatureModel.cs b/VisitorGenerator/Generators/Models/NodeSignatureModel.cs index e76c5b0..cacf4bc 100644 --- a/VisitorGenerator/Generators/Models/NodeSignatureModel.cs +++ b/VisitorGenerator/Generators/Models/NodeSignatureModel.cs @@ -31,13 +31,19 @@ public static Boolean TryCreate(ITypeSymbol type, [NotNullWhen(true)] out NodeSi var flags = NodeSignatureFlags.None; if (type.IsRecord) + { flags |= NodeSignatureFlags.IsRecord; + } if (type.DeclaredAccessibility is Accessibility.Public) + { flags |= NodeSignatureFlags.IsPublic; + } if (type.IsSealed) + { flags |= NodeSignatureFlags.IsSealed; + } var typeParameters = ctx.CollectionFactory.CreateList(); diff --git a/VisitorGenerator/Generators/Models/PropertyModel.cs b/VisitorGenerator/Generators/Models/PropertyModel.cs index ecd0ec7..3cc9738 100644 --- a/VisitorGenerator/Generators/Models/PropertyModel.cs +++ b/VisitorGenerator/Generators/Models/PropertyModel.cs @@ -24,7 +24,9 @@ public static Boolean TryCreate( result = null; if(propertyCandidate is not IPropertySymbol property) + { return false; + } if(property is not { @@ -72,7 +74,9 @@ @interface.TypeArguments is [INamedTypeSymbol arg] && } if(propertyType is null) + { return false; + } var type = propertyType.ToDisplayString(); diff --git a/VisitorGenerator/Generators/VisitorGenerator.cs b/VisitorGenerator/Generators/VisitorGenerator.cs index 57c4cb2..d26db24 100644 --- a/VisitorGenerator/Generators/VisitorGenerator.cs +++ b/VisitorGenerator/Generators/VisitorGenerator.cs @@ -119,7 +119,9 @@ private static void AddModels( ctx.ThrowIfCancellationRequested(); if (model is null) + { continue; + } var signature = model.Signature; @@ -136,7 +138,9 @@ private static void AddModels( ctx.ThrowIfCancellationRequested(); if (handledNodes.Add(node.Signature)) + { newDuplicate.Nodes.Add(node); + } } duplicate = newDuplicate; @@ -150,7 +154,9 @@ private static void AddModels( ctx.ThrowIfCancellationRequested(); if (handledNodes.Add(node.Signature)) + { duplicate.Nodes.Add(node); + } } } else @@ -166,16 +172,22 @@ private static void AddModels( ct.ThrowIfCancellationRequested(); if (ctx.TargetSymbol is not INamedTypeSymbol baseNodeType) + { return null; + } if (baseNodeType.ContainingType is not null) + { return null; + } using var modelCtx = ModelCreationContext.CreateDefault(ct); var nodes = modelCtx.CollectionFactory.CreateList(); if (!NodeSignatureModel.TryCreate(baseNodeType, out var baseSignature, in modelCtx)) + { return null; + } var handledTypes = new HashSet(SymbolEqualityComparer.Default); var result = new BaseNodeModel(nodes, baseSignature); @@ -189,12 +201,16 @@ private static void AddModels( ct.ThrowIfCancellationRequested(); if (arg is not { Kind: TypedConstantKind.Array, Values: { } @params }) + { continue; + } foreach (var param in @params) { if (param is not { Kind: TypedConstantKind.Type, Value: ITypeSymbol typeArg }) + { continue; + } NodeModel.AddModels( typeArg, @@ -207,7 +223,9 @@ private static void AddModels( } if (attribute.AttributeClass?.TypeArguments is not [_, ..] args) + { continue; + } foreach (var arg in args) { @@ -247,14 +265,18 @@ private static Boolean IsGenerateVisitorAttributeTarget(SyntaxNode n, Cancellati if (modifier.IsKind(SyntaxKind.AbstractKeyword)) { if (isAbstract) + { return false; + } isAbstract = true; } } if (!isAbstract) + { return false; + } return true; } diff --git a/VisitorGenerator/VisitorGenerator.csproj b/VisitorGenerator/VisitorGenerator.csproj index 6b81fbc..3e69b3c 100644 --- a/VisitorGenerator/VisitorGenerator.csproj +++ b/VisitorGenerator/VisitorGenerator.csproj @@ -9,7 +9,7 @@ enable enable preview - $(NoWarn);CS1591 + $(NoWarn);CS1591;CS9113 diff --git a/WorkerService1/Program.cs b/WorkerService1/Program.cs deleted file mode 100644 index 7d5eb5b..0000000 --- a/WorkerService1/Program.cs +++ /dev/null @@ -1,89 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -using Microsoft.Extensions.Options; - -using RhoMicro.CodeAnalysis.WorkerService1; - -using System.ComponentModel.DataAnnotations; -using System.Text.Json; - -var builder = Host.CreateApplicationBuilder(args); - -_ = builder.Services - .AddFoo(c => c.UseMonitorOptions()) - .AddHostedService(); - -var host = builder.Build(); -host.Run(); - -namespace RhoMicro.CodeAnalysis.WorkerService1 -{ - partial class BarRegistrationStrategy - { - partial class Pattern - { - static partial void ConfigureOptionsBuilder( - OptionsBuilder builder, - BarConfiguration configuration) - => builder.ValidateDataAnnotations().ValidateOnStart(); - } - } - - public class Worker(ILogger logger, IFoo options) : BackgroundService - { - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - var jsonOptions = new JsonSerializerOptions() { WriteIndented = true }; - - while(!stoppingToken.IsCancellationRequested) - { - logger.LogInformation("{Data}", JsonSerializer.Serialize(options, jsonOptions)); - await Task.Delay(500, stoppingToken).ConfigureAwait(false); - } - } - } - - // Process: - // An interface is consumed by the library: public interface IFooOptions - // An implementation is provided for in-process configuration: public sealed class ImmutableFooOptions - // An implementation is provided for out-of-process configuration: public sealed class FooOptions - - // By default, FooOptions will be registered via the options pattern - // In addition to the options pattern, we map the IOptions onto IFooOptions via some form of '.AddTransient(sp => sp.GetRequiredService>().Value)' - // The registration may be overridden by IFooOptions, e.g. via ImmutableFooOptions - // Since FooOptions is not explicitly depended upon from anywhere, this causes the options pattern to become inert, unless validation is performed at startup. - - // Related: validate required properties automatically - - // The options model needs to contain (we do not support nested interfaces for now): - // - Properties - // - name - // - namespace - // - normalized name - // - because we're including arbitrary expressions (attributes & dve), we also need to clone using statements - // The property model needs to contain: - // - Type display string - // - readonly? init? set? - // - default value expression - // - list of attribute expressions - // - location of the name for diagnostics like non-nullable prop being unassigned - // The default value expression needs to contain: - // - expression text - // - location - [Options] - public partial interface IFoo - { - [DefaultValueExpression("String.Empty")] - [AllowedValues(123, 456L)] - String Prop { get; } - - Int32 IntProp { get; } - IBar Bar { get; } - } - - [Options] - public partial interface IBar - { - String StringProp { get; } - } -} diff --git a/WorkerService1/Properties/launchSettings.json b/WorkerService1/Properties/launchSettings.json deleted file mode 100644 index e5d9dbd..0000000 --- a/WorkerService1/Properties/launchSettings.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/launchsettings.json", - "profiles": { - "WorkerService1": { - "commandName": "Project", - "dotnetRunMessages": true, - "environmentVariables": { - "DOTNET_ENVIRONMENT": "Development" - } - } - } -} diff --git a/WorkerService1/WorkerService1.csproj b/WorkerService1/WorkerService1.csproj deleted file mode 100644 index 0245c41..0000000 --- a/WorkerService1/WorkerService1.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - net8.0 - enable - enable - dotnet-WorkerService1-e2bcb5cd-902f-4d76-828b-e06aff08fdb1 - $(NoWarn);CS1591;CS1716;CA1716,CA1848;IDE0040 - - - - - - - - - - - diff --git a/WorkerService1/appsettings.Development.json b/WorkerService1/appsettings.Development.json deleted file mode 100644 index b2dcdb6..0000000 --- a/WorkerService1/appsettings.Development.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.Hosting.Lifetime": "Information" - } - } -} diff --git a/WorkerService1/appsettings.json b/WorkerService1/appsettings.json deleted file mode 100644 index 7b281b0..0000000 --- a/WorkerService1/appsettings.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.Hosting.Lifetime": "Information" - } - }, - "Foo": { - "Prop": "Hello, World!", - "Bar": { - "StringProp": "Goodbye, World!" - } - } -} \ No newline at end of file From 3f33de34230b3a20bc2ab0019f2a9ed41ffd15af Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Sat, 20 Dec 2025 21:14:58 +0100 Subject: [PATCH 25/36] Revert "fix all warnings" This reverts commit d085ba03e7ceb03ef502ca97feeeb23067cb8400. --- ...CSharpSourceBuilder.TestApplication.csproj | 15 + .../Program.cs | 8 + .../MacroExpansions/TailExpansion.cs | 2 - .../Comments/CommentContents.cs | 16 + .../Comments/DocumentationComment.cs | 11 + DocReflect.Library/Comments/Empty.cs | 16 + DocReflect.Library/Comments/Example.cs | 17 + DocReflect.Library/Comments/Param.cs | 23 + DocReflect.Library/Comments/Remarks.cs | 17 + DocReflect.Library/Comments/Returns.cs | 17 + DocReflect.Library/Comments/Summary.cs | 17 + DocReflect.Library/Comments/Typeparam.cs | 22 + DocReflect.Library/DocReflect.Library.csproj | 47 ++ DocReflect.Library/Documentation.cs | 66 ++ .../EnumerableExtensions.Internal.cs | 35 + .../DocumentationProviderAttribute.cs | 12 + .../Infrastructure/IDocumentationProvider.cs | 30 + DocReflect.Library/MethodDocumentation.cs | 51 ++ DocReflect.Library/PropertyDocumentation.cs | 29 + DocReflect.Library/README.md | 3 + DocReflect.Library/TypeDocumentation.cs | 40 + .../TypeExtensions.KeyValuePairComparer.cs | 18 + DocReflect.Library/TypeExtensions.cs | 116 +++ DocReflect/DocReflect.csproj | 74 ++ DocReflect/Generators/DocReflect.cs | 91 +++ .../Generators/ExtractCommentStepResult.cs | 28 + DocReflect/Generators/FinalStepResult.cs | 5 + DocReflect/Generators/ParseStepResult.cs | 27 + .../Comments/CommentContents.Internal.cs | 13 + .../Comments/DocumentationComment.Internal.cs | 11 + .../Library/TypeDocumentation.Internal.cs | 17 + DocReflect/Properties/launchSettings.json | 8 + DocReflect/README.md | 3 + .../DslGenerator.TestApp.csproj | 32 + DslGenerator.TestApp/Program.cs | 11 + DslGenerator.TestApp/README.md | 3 + DslGenerator.TestApp/Rmbnf.rmbnf | 32 + DslGenerator.Tests/DslGenerator.Tests.csproj | 29 + DslGenerator.Tests/GlobalUsings.cs | 4 + DslGenerator.Tests/ParserTests.cs | 271 +++++++ DslGenerator.Tests/TokenizerTests.cs | 124 +++ DslGenerator/Analysis/Diagnostic.cs | 23 + DslGenerator/Analysis/DiagnosticDescriptor.cs | 23 + .../Analysis/DiagnosticDescriptors.cs | 26 + .../Analysis/DiagnosticsCollection.cs | 39 + DslGenerator/DslGenerator.csproj | 56 ++ .../Attributes/GeneratedGrammarAttribute.cs | 11 + .../Attributes/GeneratedRuleListAttribute.cs | 14 + DslGenerator/Generators/DslGenerator.cs | 102 +++ .../Generators/GeneratedRuleListGenerator.cs | 271 +++++++ DslGenerator/Grammar/DisplayStringVisitor.cs | 25 + DslGenerator/Grammar/IndentedStringBuilder.cs | 37 + DslGenerator/Grammar/ListExtensions.cs | 50 ++ DslGenerator/Grammar/Name.cs | 48 ++ DslGenerator/Grammar/NamedRuleList.cs | 36 + DslGenerator/Grammar/Rule.Alternative.cs | 34 + DslGenerator/Grammar/Rule.Any.cs | 29 + DslGenerator/Grammar/Rule.Concatenation.cs | 32 + DslGenerator/Grammar/Rule.Grouping.cs | 32 + DslGenerator/Grammar/Rule.OptionalGrouping.cs | 31 + DslGenerator/Grammar/Rule.Range.cs | 31 + DslGenerator/Grammar/Rule.Reference.cs | 32 + .../Grammar/Rule.SpecificRepetition.cs | 33 + DslGenerator/Grammar/Rule.Terminal.cs | 41 + .../Grammar/Rule.VariableRepetition.cs | 31 + DslGenerator/Grammar/Rule.cs | 14 + DslGenerator/Grammar/RuleBuilder.cs | 144 ++++ .../Grammar/RuleDefinition.Incremental.cs | 28 + DslGenerator/Grammar/RuleDefinition.New.cs | 29 + DslGenerator/Grammar/RuleDefinition.cs | 20 + DslGenerator/Grammar/RuleList.cs | 36 + DslGenerator/Grammar/RuleListBuilder.cs | 50 ++ DslGenerator/Grammar/SyntaxNode.cs | 117 +++ DslGenerator/Grammar/SyntaxNodeVisitor.cs | 22 + DslGenerator/Grammar/Utils.cs | 16 + DslGenerator/Lexing/Lexeme.cs | 43 + DslGenerator/Lexing/Lexemes.cs | 30 + DslGenerator/Lexing/Location.cs | 65 ++ DslGenerator/Lexing/SourceText.cs | 40 + DslGenerator/Lexing/StringSlice.cs | 50 ++ DslGenerator/Lexing/Token.cs | 29 + DslGenerator/Lexing/TokenType.cs | 30 + DslGenerator/Lexing/TokenizeResult.cs | 15 + DslGenerator/Lexing/Tokenizer.cs | 302 +++++++ DslGenerator/Lexing/Tokens.cs | 24 + DslGenerator/Parsing/ParseResult.cs | 16 + DslGenerator/Parsing/Parser.cs | 373 +++++++++ DslGenerator/Properties/launchSettings.json | 8 + DslGenerator/README.md | 3 + DslGenerator/Usings.cs | 4 + .../Generators/EnumConstStringGenerator.cs | 4 - EnumStringTestApp/EnumStringTestApp.csproj | 14 + EnumStringTestApp/GlobalSuppressions.cs | 10 + EnumStringTestApp/Program.cs | 18 + .../IndentedStringBuilderTestApp.csproj | 21 + IndentedStringBuilderTestApp/Program.cs | 73 ++ Janus.Analyzers/AnalyzerReleases.Shipped.md | 25 + Janus.Analyzers/AnalyzerReleases.Unshipped.md | 0 .../Components/ConstructorComponent.cs | 2 +- .../Components/FactoriesComponent.cs | 72 +- .../Components/FactoryComponent.cs | 24 +- .../Components/OperatorsComponent.cs | 2 +- .../Components/ValidationComponent.cs | 2 +- Janus.Analyzers/DiagnosticDescriptors.cs | 8 - Janus.Analyzers/Janus.Analyzers.csproj | 5 + Janus.Analyzers/JanusAnalyzer.cs | 87 -- .../{Attributes => }/UnionTypeAttribute.cs | 0 .../UnionTypeSettingsAttribute.cs | 10 +- Janus.Benchmarks/Directory.build.props | 5 - Janus.Benchmarks/Janus.Benchmarks.csproj | 23 - Janus.Benchmarks/Program.cs | 6 - Janus.Benchmarks/SwitchStateBenchmark.cs | 38 - .../UnmanagedOverlayingBenchmark.cs | 26 - Janus.TestApplication/Program.cs | 8 +- Janus.Tests.EndToEnd/IsPropertyTests.cs | 1 - Janus.Tests.EndToEnd/ReadMeAssertions.cs | 2 - Janus.Tests.EndToEnd/TryAsFunctionsTests.cs | 1 - Janus.Tests/Janus.Tests.csproj | 10 +- Janus.Tests/JanusAnalyzerTests.cs | 34 +- Janus.Tests/JanusTest.cs | 4 +- ...oadResolutionPriorityAttributeGenerator.cs | 4 +- Janus/Janus.csproj | 1 - Janus/README.md | 755 ++++++++++++++---- JsonSchemaGenerator.Cli/MainService.cs | 35 +- JsonSchemaGenerator.Cli/Settings.cs | 2 - JsonSchemaGenerator.Tests/AnnotationsTests.cs | 16 +- JsonSchemaGenerator.Tests/CliTests.cs | 12 +- JsonSchemaGenerator.Tests/EnumTests.cs | 14 +- JsonSchemaGenerator.Tests/Extensions.cs | 16 - .../JsonSchemaGenerator.Tests.csproj | 65 +- JsonSchemaGenerator.Tests/ListLikeTests.cs | 122 +-- JsonSchemaGenerator.Tests/MapLikeTests.cs | 12 +- .../NullableReferenceTypeTests.cs | 4 +- .../NullableValueTypesTests.cs | 2 +- JsonSchemaGenerator.Tests/PrimitivesTest.cs | 16 +- JsonSchemaGenerator.Tests/RefTests.cs | 10 +- JsonSchemaGenerator.Tests/TestBase.cs | 10 +- .../JsonSchemaGenerator.csproj | 130 +-- .../Models/Core/JsonListLikeModel.cs | 2 - .../Models/Core/JsonNumberModel.cs | 2 +- .../Models/Core/JsonObjectModel.cs | 2 - .../Models/Core/JsonSchemaModel.cs | 8 - .../Wrappers/AdditionalPropertiesModel.cs | 2 - .../Models/Wrappers/AnnotationsBuilder.cs | 5 - JsonSchemaGenerator/Models/Wrappers/Id.cs | 2 - .../Models/Wrappers/SubSchemaModelBuilder.cs | 32 - Lyra/CSharpSourceBuilder.cs | 4 +- OptionsGenerator.Tests/AnalyzerTests.cs | 14 +- .../OptionsGenerator.Tests.csproj | 68 +- OptionsGenerator/AnalyzerReleases.Shipped.md | 9 + .../AnalyzerReleases.Unshipped.md | 0 OptionsGenerator/Analyzers/OptionsAnalyzer.cs | 4 - .../Generators/OptionsGenerator.cs | 2 - OptionsGenerator/Generators/OptionsModel.cs | 2 - OptionsGenerator/Generators/PropertyModel.cs | 6 - ...ConfigurationFullyQualifiedNameTemplate.cs | 2 - .../ConfigurationNameTemplate.cs | 2 - .../Configuration/ConfigurationTemplate.cs | 2 - ...utableOptionsFullyQualifiedNameTemplate.cs | 2 - .../Immutable/ImmutableOptionsNameTemplate.cs | 2 - .../Immutable/ImmutableOptionsTemplate.cs | 2 - .../Immutable/ImmutablePropertyTemplate.cs | 2 - ...erfaceOptionsFullyQualifiedNameTemplate.cs | 2 - .../Interface/InterfaceOptionsNameTemplate.cs | 2 - .../Interface/InterfaceOptionsTemplate.cs | 2 - .../Templates/MonitorPropertyTemplate.cs | 2 - .../MutableDefaultExpressionTemplate.cs | 2 - ...utableOptionsFullyQualifiedNameTemplate.cs | 2 - .../Mutable/MutableOptionsNameTemplate.cs | 2 - .../Mutable/MutableOptionsTemplate.cs | 2 - .../Mutable/MutablePropertyTemplate.cs | 2 - ...efaultOptionsFullyQualifiedNameTemplate.cs | 2 - .../Passthrough/DefaultOptionsNameTemplate.cs | 2 - ...onitorOptionsFullyQualifiedNameTemplate.cs | 2 - .../Passthrough/MonitorOptionsNameTemplate.cs | 2 - ...assthroughOptionsImplementationTemplate.cs | 2 - .../PassthroughPropertyTemplate.cs | 2 - ...apshotOptionsFullyQualifiedNameTemplate.cs | 2 - .../SnapshotOptionsNameTemplate.cs | 2 - .../Templates/PropertyAnnotationsTemplate.cs | 2 - ...ationStrategyFullyQualifiedNameTemplate.cs | 2 - .../RegistrationStrategyNameTemplate.cs | 2 - .../RegistrationStrategyTemplate.cs | 2 - .../Templates/RootNamespaceTemplate.cs | 2 - .../Generators/Templates/RootTemplate.cs | 2 - .../ServiceCollectionExtensionsTemplate.cs | 2 - OptionsGenerator/OptionsGenerator.csproj | 5 - Project1/Program.cs | 49 ++ Project1/Project1.csproj | 21 + Project1/README.md | 3 + RhoMicro.CodeAnalysis.slnx | 66 +- TestApp/Program.cs | 216 +++++ TestApp/TestApp.csproj | 38 + TestApp/appsettings.json | 14 + TestApp/appsettings.schema.json | 9 + .../BlockTerminatorsBenchmark.cs | 56 -- .../ParserMatchBenchmark.cs | 44 +- .../TemplateBenchmark.cs | 2 - .../UtilityGenerators.Benchmarks.csproj | 6 - .../UtilityGenerators.Dev.csproj | 9 +- UtilityGenerators.Tests.E2E/TestAttribute.cs | 2 +- .../UtilityGenerators.Tests.E2E.csproj | 6 - .../Collections/EquatableDictionaryTests.cs | 16 +- .../Library/Collections/EquatableListTests.cs | 30 +- .../Library/Collections/EquatableSetTests.cs | 28 +- .../LazyEquatableDictionaryTests.cs | 16 +- .../Collections/LazyEquatableListTests.cs | 30 +- .../Text/Templating/TemplateRendererTests.cs | 6 - .../Templating/LexerTests.cs | 2 - .../Templating/ParserTests.cs | 14 +- .../Templating/TemplateStringTests.cs | 4 +- .../Templating/TestHelpers.cs | 2 - UtilityGenerators.Tests/TestBase.cs | 8 +- .../UtilityGenerators.Tests.csproj | 63 +- .../AttributeFactoryGenerator.cs | 58 -- .../AttributeFactory/AttributeFactoryModel.cs | 2 - .../AttributeFactory/ConstructorModel.cs | 18 - .../InitializationMethodModel.cs | 2 - .../AttributeFactory/PropertyModel.cs | 20 +- UtilityGenerators/GlobalUsings.cs | 1 - ...dentedStringBuilderAppendablesGenerator.cs | 22 - .../System/Buffers/ArrayBufferWriter.cs | 8 - .../Library/External/System/HashCode.cs | 10 - .../System/LocalAppContextSwitches.cs | 5 - .../Models/AttributeParameterTypeKind.cs | 2 - .../Collections/DictionaryEqualityComparer.cs | 4 - .../Collections/EnumerableEqualityComparer.cs | 4 - .../Models/Collections/EquatableList.cs | 2 - .../Models/Collections/EquatableSet.cs | 2 - .../Models/Collections/LazyEquatableList.cs | 4 - .../Models/Collections/MutabilityContext.cs | 2 - .../Models/Collections/SetEqualityComparer.cs | 4 - .../Library/Models/NamedTypeModel.cs | 38 - UtilityGenerators/Library/Models/TypeModel.cs | 6 - .../Text/SourceTexts/BlockScopeCollection.cs | 2 - .../Text/SourceTexts/CommentBuilder.cs | 20 - .../Text/SourceTexts/IndentedStringBuilder.cs | 44 - .../IndentedStringBuilderOptions.cs | 6 +- .../DynamicallyAllocatedCharBuffer.cs | 4 - .../Templating/TemplateRenderer.Render.cs | 2 - UtilityGenerators/Library/ThrowHelpers.cs | 58 -- .../NonEquatable/NonEquatableGenerator.cs | 6 - UtilityGenerators/Templating/Lexer.cs | 2 - UtilityGenerators/Templating/Parser.cs | 8 - .../Templating/SourcePosition.cs | 11 - .../Visitors/CommentXmlTreeStringBuilder.cs | 4 - ...eratingTemplateVisitor.CodeBlockVisitor.cs | 6 - ...ingTemplateVisitor.TemplateBlockVisitor.cs | 6 +- .../SourceGeneratingTemplateVisitor.cs | 6 - .../TemplateStringReconstructionVisitor.cs | 4 - .../Syntax/Visitors/TokenValidator.cs | 6 - .../Syntax/Visitors/XmlTreeStringBuilder.cs | 4 - .../Templating/TemplateAttribute.Model.cs | 2 +- .../Templating/TemplateAttribute.cs | 11 +- .../Templating/TemplateString.cs | 2 - .../Templating/TemplatingGenerator.cs | 156 ++-- .../TypeSymbolPatternGenerator.cs | 136 ++-- UtilityGenerators/bootstrap.sh | 45 -- .../Generators/Models/NodeModel.cs | 16 - .../Generators/Models/NodeSignatureModel.cs | 6 - .../Generators/Models/PropertyModel.cs | 4 - .../Generators/VisitorGenerator.cs | 22 - VisitorGenerator/VisitorGenerator.csproj | 2 +- WorkerService1/Program.cs | 89 +++ WorkerService1/Properties/launchSettings.json | 12 + WorkerService1/WorkerService1.csproj | 21 + WorkerService1/appsettings.Development.json | 8 + WorkerService1/appsettings.json | 14 + 268 files changed, 5846 insertions(+), 1871 deletions(-) create mode 100644 CSharpSourceBuilder.TestApplication/CSharpSourceBuilder.TestApplication.csproj create mode 100644 CSharpSourceBuilder.TestApplication/Program.cs create mode 100644 DocReflect.Library/Comments/CommentContents.cs create mode 100644 DocReflect.Library/Comments/DocumentationComment.cs create mode 100644 DocReflect.Library/Comments/Empty.cs create mode 100644 DocReflect.Library/Comments/Example.cs create mode 100644 DocReflect.Library/Comments/Param.cs create mode 100644 DocReflect.Library/Comments/Remarks.cs create mode 100644 DocReflect.Library/Comments/Returns.cs create mode 100644 DocReflect.Library/Comments/Summary.cs create mode 100644 DocReflect.Library/Comments/Typeparam.cs create mode 100644 DocReflect.Library/DocReflect.Library.csproj create mode 100644 DocReflect.Library/Documentation.cs create mode 100644 DocReflect.Library/EnumerableExtensions.Internal.cs create mode 100644 DocReflect.Library/Infrastructure/DocumentationProviderAttribute.cs create mode 100644 DocReflect.Library/Infrastructure/IDocumentationProvider.cs create mode 100644 DocReflect.Library/MethodDocumentation.cs create mode 100644 DocReflect.Library/PropertyDocumentation.cs create mode 100644 DocReflect.Library/README.md create mode 100644 DocReflect.Library/TypeDocumentation.cs create mode 100644 DocReflect.Library/TypeExtensions.KeyValuePairComparer.cs create mode 100644 DocReflect.Library/TypeExtensions.cs create mode 100644 DocReflect/DocReflect.csproj create mode 100644 DocReflect/Generators/DocReflect.cs create mode 100644 DocReflect/Generators/ExtractCommentStepResult.cs create mode 100644 DocReflect/Generators/FinalStepResult.cs create mode 100644 DocReflect/Generators/ParseStepResult.cs create mode 100644 DocReflect/Library/Comments/CommentContents.Internal.cs create mode 100644 DocReflect/Library/Comments/DocumentationComment.Internal.cs create mode 100644 DocReflect/Library/TypeDocumentation.Internal.cs create mode 100644 DocReflect/Properties/launchSettings.json create mode 100644 DocReflect/README.md create mode 100644 DslGenerator.TestApp/DslGenerator.TestApp.csproj create mode 100644 DslGenerator.TestApp/Program.cs create mode 100644 DslGenerator.TestApp/README.md create mode 100644 DslGenerator.TestApp/Rmbnf.rmbnf create mode 100644 DslGenerator.Tests/DslGenerator.Tests.csproj create mode 100644 DslGenerator.Tests/GlobalUsings.cs create mode 100644 DslGenerator.Tests/ParserTests.cs create mode 100644 DslGenerator.Tests/TokenizerTests.cs create mode 100644 DslGenerator/Analysis/Diagnostic.cs create mode 100644 DslGenerator/Analysis/DiagnosticDescriptor.cs create mode 100644 DslGenerator/Analysis/DiagnosticDescriptors.cs create mode 100644 DslGenerator/Analysis/DiagnosticsCollection.cs create mode 100644 DslGenerator/DslGenerator.csproj create mode 100644 DslGenerator/Generators/Attributes/GeneratedGrammarAttribute.cs create mode 100644 DslGenerator/Generators/Attributes/GeneratedRuleListAttribute.cs create mode 100644 DslGenerator/Generators/DslGenerator.cs create mode 100644 DslGenerator/Generators/GeneratedRuleListGenerator.cs create mode 100644 DslGenerator/Grammar/DisplayStringVisitor.cs create mode 100644 DslGenerator/Grammar/IndentedStringBuilder.cs create mode 100644 DslGenerator/Grammar/ListExtensions.cs create mode 100644 DslGenerator/Grammar/Name.cs create mode 100644 DslGenerator/Grammar/NamedRuleList.cs create mode 100644 DslGenerator/Grammar/Rule.Alternative.cs create mode 100644 DslGenerator/Grammar/Rule.Any.cs create mode 100644 DslGenerator/Grammar/Rule.Concatenation.cs create mode 100644 DslGenerator/Grammar/Rule.Grouping.cs create mode 100644 DslGenerator/Grammar/Rule.OptionalGrouping.cs create mode 100644 DslGenerator/Grammar/Rule.Range.cs create mode 100644 DslGenerator/Grammar/Rule.Reference.cs create mode 100644 DslGenerator/Grammar/Rule.SpecificRepetition.cs create mode 100644 DslGenerator/Grammar/Rule.Terminal.cs create mode 100644 DslGenerator/Grammar/Rule.VariableRepetition.cs create mode 100644 DslGenerator/Grammar/Rule.cs create mode 100644 DslGenerator/Grammar/RuleBuilder.cs create mode 100644 DslGenerator/Grammar/RuleDefinition.Incremental.cs create mode 100644 DslGenerator/Grammar/RuleDefinition.New.cs create mode 100644 DslGenerator/Grammar/RuleDefinition.cs create mode 100644 DslGenerator/Grammar/RuleList.cs create mode 100644 DslGenerator/Grammar/RuleListBuilder.cs create mode 100644 DslGenerator/Grammar/SyntaxNode.cs create mode 100644 DslGenerator/Grammar/SyntaxNodeVisitor.cs create mode 100644 DslGenerator/Grammar/Utils.cs create mode 100644 DslGenerator/Lexing/Lexeme.cs create mode 100644 DslGenerator/Lexing/Lexemes.cs create mode 100644 DslGenerator/Lexing/Location.cs create mode 100644 DslGenerator/Lexing/SourceText.cs create mode 100644 DslGenerator/Lexing/StringSlice.cs create mode 100644 DslGenerator/Lexing/Token.cs create mode 100644 DslGenerator/Lexing/TokenType.cs create mode 100644 DslGenerator/Lexing/TokenizeResult.cs create mode 100644 DslGenerator/Lexing/Tokenizer.cs create mode 100644 DslGenerator/Lexing/Tokens.cs create mode 100644 DslGenerator/Parsing/ParseResult.cs create mode 100644 DslGenerator/Parsing/Parser.cs create mode 100644 DslGenerator/Properties/launchSettings.json create mode 100644 DslGenerator/README.md create mode 100644 DslGenerator/Usings.cs create mode 100644 EnumStringTestApp/EnumStringTestApp.csproj create mode 100644 EnumStringTestApp/GlobalSuppressions.cs create mode 100644 EnumStringTestApp/Program.cs create mode 100644 IndentedStringBuilderTestApp/IndentedStringBuilderTestApp.csproj create mode 100644 IndentedStringBuilderTestApp/Program.cs create mode 100644 Janus.Analyzers/AnalyzerReleases.Shipped.md create mode 100644 Janus.Analyzers/AnalyzerReleases.Unshipped.md rename Janus.Analyzers/{Attributes => }/UnionTypeAttribute.cs (100%) rename Janus.Analyzers/{Attributes => }/UnionTypeSettingsAttribute.cs (98%) delete mode 100644 Janus.Benchmarks/Directory.build.props delete mode 100644 Janus.Benchmarks/Janus.Benchmarks.csproj delete mode 100644 Janus.Benchmarks/Program.cs delete mode 100644 Janus.Benchmarks/SwitchStateBenchmark.cs delete mode 100644 Janus.Benchmarks/UnmanagedOverlayingBenchmark.cs create mode 100644 OptionsGenerator/AnalyzerReleases.Shipped.md create mode 100644 OptionsGenerator/AnalyzerReleases.Unshipped.md create mode 100644 Project1/Program.cs create mode 100644 Project1/Project1.csproj create mode 100644 Project1/README.md create mode 100644 TestApp/Program.cs create mode 100644 TestApp/TestApp.csproj create mode 100644 TestApp/appsettings.json create mode 100644 TestApp/appsettings.schema.json delete mode 100644 UtilityGenerators/bootstrap.sh create mode 100644 WorkerService1/Program.cs create mode 100644 WorkerService1/Properties/launchSettings.json create mode 100644 WorkerService1/WorkerService1.csproj create mode 100644 WorkerService1/appsettings.Development.json create mode 100644 WorkerService1/appsettings.json diff --git a/CSharpSourceBuilder.TestApplication/CSharpSourceBuilder.TestApplication.csproj b/CSharpSourceBuilder.TestApplication/CSharpSourceBuilder.TestApplication.csproj new file mode 100644 index 0000000..2fc683e --- /dev/null +++ b/CSharpSourceBuilder.TestApplication/CSharpSourceBuilder.TestApplication.csproj @@ -0,0 +1,15 @@ + + + + Exe + net9.0 + preview + enable + enable + + + + + + + diff --git a/CSharpSourceBuilder.TestApplication/Program.cs b/CSharpSourceBuilder.TestApplication/Program.cs new file mode 100644 index 0000000..551df15 --- /dev/null +++ b/CSharpSourceBuilder.TestApplication/Program.cs @@ -0,0 +1,8 @@ +// See https://aka.ms/new-console-template for more information + +using RhoMicro.CodeAnalysis; +using RhoMicro.CodeAnalysis.Lyra; + +using var builder = new CSharpSourceBuilder(); + + diff --git a/CopyToGenerator/MacroExpansions/TailExpansion.cs b/CopyToGenerator/MacroExpansions/TailExpansion.cs index f43c764..7cd16c5 100644 --- a/CopyToGenerator/MacroExpansions/TailExpansion.cs +++ b/CopyToGenerator/MacroExpansions/TailExpansion.cs @@ -15,9 +15,7 @@ internal sealed class TailExpansion(Model model) : MacroExpansionBase(model, Mac public override void Expand(IExpandingMacroStringBuilder builder, CancellationToken cancellationToken) { if(!Model.IsInGlobalNamespace) - { _ = builder.Append('}'); - } _ = builder.Append('}'); } diff --git a/DocReflect.Library/Comments/CommentContents.cs b/DocReflect.Library/Comments/CommentContents.cs new file mode 100644 index 0000000..ad0d517 --- /dev/null +++ b/DocReflect.Library/Comments/CommentContents.cs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DocReflect.Comments; +using System; + +/// +/// Represents a comments contents. +/// +/// The textual contents of the comment. +public readonly partial record struct CommentContents(String Text) +{ + /// + /// Gets empty comment contents. + /// + public static CommentContents Empty { get; } = new(String.Empty); +} diff --git a/DocReflect.Library/Comments/DocumentationComment.cs b/DocReflect.Library/Comments/DocumentationComment.cs new file mode 100644 index 0000000..d9b98a8 --- /dev/null +++ b/DocReflect.Library/Comments/DocumentationComment.cs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DocReflect.Comments; + +/// +/// Represents a top level documentation comment attached to some member. +/// +/// +/// The contents of this comment. +/// +public abstract partial record DocumentationComment(CommentContents Contents); diff --git a/DocReflect.Library/Comments/Empty.cs b/DocReflect.Library/Comments/Empty.cs new file mode 100644 index 0000000..bdef616 --- /dev/null +++ b/DocReflect.Library/Comments/Empty.cs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DocReflect.Comments; +using System; + +/// +/// Represents an empty or non-existent top-level documentation comment. +/// +public sealed partial record Empty : DocumentationComment +{ + /// + /// Initializes a new instance. + /// + public Empty() : base(CommentContents.Empty) + { } +} diff --git a/DocReflect.Library/Comments/Example.cs b/DocReflect.Library/Comments/Example.cs new file mode 100644 index 0000000..f499c81 --- /dev/null +++ b/DocReflect.Library/Comments/Example.cs @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DocReflect.Comments; + +/// +/// Represents a example comment. +/// +public sealed partial record Example : DocumentationComment +{ + /// + /// Initializes a new instance. + /// + /// + public Example(CommentContents content) : base(content) + { + } +} diff --git a/DocReflect.Library/Comments/Param.cs b/DocReflect.Library/Comments/Param.cs new file mode 100644 index 0000000..45a4514 --- /dev/null +++ b/DocReflect.Library/Comments/Param.cs @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DocReflect.Comments; +using System; + +/// +/// Represents a param comment. +/// +public sealed partial record Param : DocumentationComment +{ + /// + /// Initializes a new instance. + /// + /// + /// The name of the parameter referenced. + public Param(CommentContents contents, String name) + : base(contents) + => Name = name; + /// + /// Gets the name of the parameter referenced. + /// + public String Name { get; } +} diff --git a/DocReflect.Library/Comments/Remarks.cs b/DocReflect.Library/Comments/Remarks.cs new file mode 100644 index 0000000..1014a06 --- /dev/null +++ b/DocReflect.Library/Comments/Remarks.cs @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DocReflect.Comments; + +/// +/// Represents a remarks comment. +/// +public sealed partial record Remarks : DocumentationComment +{ + /// + /// Initializes a new instance. + /// + /// + public Remarks(CommentContents content) : base(content) + { + } +} diff --git a/DocReflect.Library/Comments/Returns.cs b/DocReflect.Library/Comments/Returns.cs new file mode 100644 index 0000000..641f17f --- /dev/null +++ b/DocReflect.Library/Comments/Returns.cs @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DocReflect.Comments; + +/// +/// Represents a returns comment. +/// +public sealed partial record Returns : DocumentationComment +{ + /// + /// Initializes a new instance. + /// + /// + public Returns(CommentContents content) : base(content) + { + } +} diff --git a/DocReflect.Library/Comments/Summary.cs b/DocReflect.Library/Comments/Summary.cs new file mode 100644 index 0000000..7958952 --- /dev/null +++ b/DocReflect.Library/Comments/Summary.cs @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DocReflect.Comments; + +/// +/// Represents a summary comment. +/// +public sealed partial record Summary : DocumentationComment +{ + /// + /// Initializes a new instance. + /// + /// + public Summary(CommentContents content) : base(content) + { + } +} diff --git a/DocReflect.Library/Comments/Typeparam.cs b/DocReflect.Library/Comments/Typeparam.cs new file mode 100644 index 0000000..cecc988 --- /dev/null +++ b/DocReflect.Library/Comments/Typeparam.cs @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DocReflect.Comments; + +/// +/// Represents a typeparam comment. +/// +public sealed partial record Typeparam : DocumentationComment +{ + /// + /// Initializes a new instance. + /// + /// + /// The name of the type parameter referenced. + public Typeparam(CommentContents content, String name) + : base(content) + => Name = name; + /// + /// Gets the name of the type parameter referenced. + /// + public String Name { get; } +} diff --git a/DocReflect.Library/DocReflect.Library.csproj b/DocReflect.Library/DocReflect.Library.csproj new file mode 100644 index 0000000..7c4da3c --- /dev/null +++ b/DocReflect.Library/DocReflect.Library.csproj @@ -0,0 +1,47 @@ + + + + netstandard2.0 + RhoMicro.CodeAnalysis.DocReflect + + + + true + true + Provides the types required to inspect type documentation using a reflection-like api. + Source Generator; Documentation; Reflection + + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + \ No newline at end of file diff --git a/DocReflect.Library/Documentation.cs b/DocReflect.Library/Documentation.cs new file mode 100644 index 0000000..ec90673 --- /dev/null +++ b/DocReflect.Library/Documentation.cs @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DocReflect; + +using RhoMicro.CodeAnalysis.DocReflect.Comments; +using RhoMicro.CodeAnalysis.Library.Models.Collections; + +using System; + +/// +/// Represents a members documentation. +/// +public partial class Documentation : IEquatable +{ + /// + /// Initializes a new instance. + /// + /// All top level documentation comments; in order of declaration. + public Documentation(IEnumerable topLevelComments) + { + _ = topLevelComments ?? throw new ArgumentNullException(nameof(topLevelComments)); + + using var factory = EquatableCollectionFactory.CreateDefault(); + + var tlcList = factory.CreateList(); + + foreach(var tlc in topLevelComments) + { + if(tlc is Summary summary) + Summary ??= summary; + else if(tlc is Remarks remarks) + Remarks ??= remarks; + else if(tlc is Example example) + Example ??= example; + + tlcList.Add(tlc); + } + + TopLevelComments = tlcList; + } + + /// + /// Gets all top level documentation comments; in order of declaration. + /// + public IReadOnlyList TopLevelComments { get; } + /// + /// Gets the summary comment if one exists; otherwise, . + /// + public Summary? Summary { get; } + /// + /// Gets the remarks comment if one exists; otherwise, . + /// + public Remarks? Remarks { get; } + /// + /// Gets the example comment if one exists; otherwise, . + /// + public Example? Example { get; } + /// + public override Boolean Equals(Object? obj) => Equals(obj as Documentation); + /// + public Boolean Equals(Documentation? other) => other is not null && + TopLevelComments.Count == other.TopLevelComments.Count && + TopLevelComments.Equals(other.TopLevelComments); + /// + public override Int32 GetHashCode() => TopLevelComments.GetHashCode(); +} diff --git a/DocReflect.Library/EnumerableExtensions.Internal.cs b/DocReflect.Library/EnumerableExtensions.Internal.cs new file mode 100644 index 0000000..401f956 --- /dev/null +++ b/DocReflect.Library/EnumerableExtensions.Internal.cs @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Library; + +using RhoMicro.CodeAnalysis.Library.Models.Collections; + +internal static partial class EnumerableExtensions +{ + public static IReadOnlyDictionary ToEquatableNameMap( + this IEnumerable elements, + Func nameSelector, + String elementsName) + { + _ = elements ?? throw new ArgumentNullException(elementsName); + + using var factory = EquatableCollectionFactory.CreateDefault(); + + var map = factory.CreateDictionary(); + + foreach(var element in elements) + { + var name = nameSelector.Invoke(element); + if(map.ContainsKey(name)) + { + throw new ArgumentException( + $"{elementsName} contains duplicate name: {name}", + elementsName); + } + + map.Add(name, element); + } + + return map; + } +} diff --git a/DocReflect.Library/Infrastructure/DocumentationProviderAttribute.cs b/DocReflect.Library/Infrastructure/DocumentationProviderAttribute.cs new file mode 100644 index 0000000..153468c --- /dev/null +++ b/DocReflect.Library/Infrastructure/DocumentationProviderAttribute.cs @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DocReflect.Infrastructure; + +/// +/// Marks a class as a provider of documentation. +/// In order to be used by the infrastructure, classes +/// marked by this attribute must implement +/// and have a parameterless constructor. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +public sealed class DocumentationProviderAttribute : Attribute; diff --git a/DocReflect.Library/Infrastructure/IDocumentationProvider.cs b/DocReflect.Library/Infrastructure/IDocumentationProvider.cs new file mode 100644 index 0000000..b31f471 --- /dev/null +++ b/DocReflect.Library/Infrastructure/IDocumentationProvider.cs @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DocReflect.Infrastructure; + +using System.Reflection; + +/// +/// Provides documentation for types, methods and properties. +/// In order to be considered by the infrastructure, +/// provider types must be marked using +/// and have a parameterless constructor. +/// +public interface IDocumentationProvider +{ + /// + /// Provides a map of documentations onto their types. + /// + /// Documentations associated with their respective type. + IEnumerable> GetTypeDocumentations(); + /// + /// Provides a map of documentations onto their methods. + /// + /// Documentations associated with their respective method. + IEnumerable> GetMethodDocumentations(); + /// + /// Provides a map of documentations onto their properties. + /// + /// Documentations associated with their respective property. + IEnumerable> GetPropertyDocumentations(); +} diff --git a/DocReflect.Library/MethodDocumentation.cs b/DocReflect.Library/MethodDocumentation.cs new file mode 100644 index 0000000..b42b092 --- /dev/null +++ b/DocReflect.Library/MethodDocumentation.cs @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DocReflect; + +using RhoMicro.CodeAnalysis.DocReflect.Comments; +using RhoMicro.CodeAnalysis.Library; + +using System.Collections.Generic; + +/// +/// Represents a methods documentation. +/// +public partial class MethodDocumentation : Documentation +{ + /// + /// Initializes a new instance. + /// + /// + /// + /// + /// + /// The methods type parameters. + /// + /// + /// The methods parameters. + /// + public MethodDocumentation( + IEnumerable topLevelComments, + IEnumerable typeParameters, + IEnumerable parameters) + : base(topLevelComments) + { + TypeParameters = typeParameters.ToEquatableNameMap(p => p.Name, nameof(typeParameters)); + Parameters = parameters.ToEquatableNameMap(p => p.Name, nameof(parameters)); + } + + /// + /// Gets an empty type documentation comment. + /// + public static MethodDocumentation Empty { get; } = + new(Array.Empty(), Array.Empty(), Array.Empty()); + + /// + /// Gets the type parameter comments; in order of declaration. + /// + public IReadOnlyDictionary TypeParameters { get; } + /// + /// Gets the parameter comments; in order of declaration. + /// + public IReadOnlyDictionary Parameters { get; } +} diff --git a/DocReflect.Library/PropertyDocumentation.cs b/DocReflect.Library/PropertyDocumentation.cs new file mode 100644 index 0000000..943be0b --- /dev/null +++ b/DocReflect.Library/PropertyDocumentation.cs @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DocReflect; + +using RhoMicro.CodeAnalysis.DocReflect.Comments; + +using System.Collections.Generic; + +/// +/// Represents a properties documentation. +/// +public partial class PropertyDocumentation : Documentation +{ + /// + /// Initializes a new instance. + /// + /// + /// + /// + public PropertyDocumentation(IEnumerable topLevelComments) + : base(topLevelComments) + { } + + /// + /// Gets an empty type documentation comment. + /// + public static PropertyDocumentation Empty { get; } = + new(Array.Empty()); +} diff --git a/DocReflect.Library/README.md b/DocReflect.Library/README.md new file mode 100644 index 0000000..ffca571 --- /dev/null +++ b/DocReflect.Library/README.md @@ -0,0 +1,3 @@ +# DocReflect.Library + +Provides the types required to inspect type documentation using a reflection-like api. \ No newline at end of file diff --git a/DocReflect.Library/TypeDocumentation.cs b/DocReflect.Library/TypeDocumentation.cs new file mode 100644 index 0000000..ae6b863 --- /dev/null +++ b/DocReflect.Library/TypeDocumentation.cs @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DocReflect; + +using RhoMicro.CodeAnalysis.DocReflect.Comments; +using RhoMicro.CodeAnalysis.Library; + +using System.Collections.Generic; + +/// +/// Represents a types documentation. +/// +public partial class TypeDocumentation : Documentation +{ + /// + /// Initializes a new instance. + /// + /// + /// + /// + /// + /// The types type parameters. + /// + public TypeDocumentation( + IEnumerable topLevelComments, + IEnumerable typeParameters) + : base(topLevelComments) + => TypeParameters = typeParameters.ToEquatableNameMap(p => p.Name, nameof(typeParameters)); + + /// + /// Gets an empty type documentation comment. + /// + public static TypeDocumentation Empty { get; } = + new(Array.Empty(), Array.Empty()); + + /// + /// Gets the type parameter comments; in order of declaration. + /// + public IReadOnlyDictionary TypeParameters { get; } +} diff --git a/DocReflect.Library/TypeExtensions.KeyValuePairComparer.cs b/DocReflect.Library/TypeExtensions.KeyValuePairComparer.cs new file mode 100644 index 0000000..a5e3bff --- /dev/null +++ b/DocReflect.Library/TypeExtensions.KeyValuePairComparer.cs @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DocReflect; +public static partial class Extensions +{ + private sealed class KeyValuePairComparer : IEqualityComparer> + { + public KeyValuePairComparer() { } + + private static readonly EqualityComparer _keyComparer = EqualityComparer.Default; + + public Boolean Equals(KeyValuePair x, KeyValuePair y) => + _keyComparer.Equals(x.Key, y.Key); + + public Int32 GetHashCode(KeyValuePair obj) => + _keyComparer.GetHashCode(obj.Key); + } +} diff --git a/DocReflect.Library/TypeExtensions.cs b/DocReflect.Library/TypeExtensions.cs new file mode 100644 index 0000000..fb34838 --- /dev/null +++ b/DocReflect.Library/TypeExtensions.cs @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DocReflect; + +using DocReflect.Infrastructure; + +using System.Collections.Concurrent; +using System.ComponentModel; +using System.Reflection; + +/// +/// Contains reflection extensions required to access type, method and property documentation. +/// +public static partial class Extensions +{ + private static readonly Lazy> _providers = + new(() => AppDomain.CurrentDomain + .GetAssemblies() + .SelectMany(a => a.GetTypes()) + .Where(t => + t.CustomAttributes.Any(a => a.AttributeType == typeof(DocumentationProviderAttribute)) && + typeof(IDocumentationProvider).IsAssignableFrom(t) && + t.GetConstructors().Any(c => c.IsPublic && c.GetParameters().Length == 0)) + .Distinct() + .Select(Activator.CreateInstance) + .OfType() + .ToList()); + private static readonly Lazy> _typeDocs = + new(() => _providers.Value + .SelectMany(p => + { + try + { + return p.GetTypeDocumentations(); + } catch + { + return Array.Empty>(); + } + }) + .Distinct(new KeyValuePairComparer()) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value)); + private static readonly Lazy> _methodDocs = + new(() => _providers.Value + .SelectMany(p => + { + try + { + return p.GetMethodDocumentations(); + } catch + { + return Array.Empty>(); + } + }) + .Distinct(new KeyValuePairComparer()) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value)); + private static readonly Lazy> _propertyDocs = + new(() => _providers.Value + .SelectMany(p => + { + try + { + return p.GetPropertyDocumentations(); + } catch + { + return Array.Empty>(); + } + }) + .Distinct(new KeyValuePairComparer()) + .ToDictionary(kvp => kvp.Key, kvp => kvp.Value)); + + /// + /// Gets the documentation available for this type. + /// + /// The type whose documentation to locate. + /// + /// The documentation located for , + /// if one could be located; otherwise, . + public static TypeDocumentation GetDocumentation(this Type type) + { + var result = _typeDocs.Value.TryGetValue(type, out var r) ? + r : + TypeDocumentation.Empty; + + return result; + } + /// + /// Gets the documentation available for this method. + /// + /// The method whose documentation to locate. + /// + /// The documentation located for , + /// if one could be located; otherwise, . + public static MethodDocumentation GetDocumentation(this MethodInfo method) + { + var result = _methodDocs.Value.TryGetValue(method, out var r) ? + r : + MethodDocumentation.Empty; + + return result; + } + /// + /// Gets the documentation available for this property. + /// + /// The property whose documentation to locate. + /// + /// The documentation located for , + /// if one could be located; otherwise, . + public static PropertyDocumentation GetDocumentation(this PropertyInfo property) + { + var result = _propertyDocs.Value.TryGetValue(property, out var r) ? + r : + PropertyDocumentation.Empty; + + return result; + } +} diff --git a/DocReflect/DocReflect.csproj b/DocReflect/DocReflect.csproj new file mode 100644 index 0000000..a8f8c3c --- /dev/null +++ b/DocReflect/DocReflect.csproj @@ -0,0 +1,74 @@ + + + + + netstandard2.0 + false + true + true + true + enable + enable + + + + true + true + Generates reflection-like API to examine member documentation. + Source Generator; Documentation; Reflection + + + + $(DefineConstants);GENERATOR + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + diff --git a/DocReflect/Generators/DocReflect.cs b/DocReflect/Generators/DocReflect.cs new file mode 100644 index 0000000..894a93c --- /dev/null +++ b/DocReflect/Generators/DocReflect.cs @@ -0,0 +1,91 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DocReflect.Generators; + +using RhoMicro.CodeAnalysis.Library.Text.SourceTexts; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +using System.Collections.Immutable; +using System.Xml; + +using static RhoMicro.CodeAnalysis.Library.Text.SourceTexts.IndentedStringBuilder.Appendables; + +/// +/// Generates documentation providers for DocReflect. +/// +[Generator(LanguageNames.CSharp)] +public sealed class DocReflect : IIncrementalGenerator +{ + /// + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var provider = context.SyntaxProvider.CreateSyntaxProvider( + (node, ct) => node is TypeDeclarationSyntax, + ExtractCommentStep) + .Select(ParseStep) + .Collect() + .Select(FinalStep); + } + + private static ParseStepResult ParseStep(ExtractCommentStepResult result, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + private static FinalStepResult FinalStep(ImmutableArray results, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var options = IndentedStringBuilderOptions.GeneratedFile with + { + AmbientCancellationToken = cancellationToken, + GeneratorName = "RhoMicro.CodeAnalysis.DocReflect" + }; + + var builder = new IndentedStringBuilder(options) + .Append("namespace RhoMicro.CodeAnalysis.Generated.DocReflect") + .OpenBracesBlock() + .Append("internal sealed class TypeDocumentationProvider : RhoMicro.CodeAnalysis.DocReflect.Infrastructure.IDocumentationProvider") + .OpenBracesBlock() + .AppendLine("/// ") + .AppendLine("public IEnumerable> GetMethodDocumentations() => Array.Empty>();") + .AppendLine("/// ") + .AppendLine("public IEnumerable> GetMethodDocumentations() => Array.Empty>();") + .AppendLine("/// ") + .AppendLine("public IEnumerable> GetTypeDocumentations() =>") + .OpenBlock(Blocks.Brackets with + { + PlaceDelimitersOnNewLine = true, + Indentation = options.DefaultIndentation + }); + + var sourceText = builder.CloseAllBlocks().ToString(); + const String hintName = "TypeDocumentationProvider.g.cs"; + var result = new FinalStepResult(hintName, sourceText); + + return result; + } + private static ExtractCommentStepResult ExtractCommentStep(GeneratorSyntaxContext context, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + var targetSymbol = context.SemanticModel.GetSymbolInfo(context.Node, cancellationToken).Symbol; + if(targetSymbol == null) + return default; + + cancellationToken.ThrowIfCancellationRequested(); + var rawComment = targetSymbol.GetDocumentationCommentXml(expandIncludes: true, cancellationToken: cancellationToken); + if(rawComment == null) + return default; + + cancellationToken.ThrowIfCancellationRequested(); + + throw new NotImplementedException("Comment parsing is not implemented yet."); + var parsedComment = new XmlDocument(); + + cancellationToken.ThrowIfCancellationRequested(); + var typeMetadataName = targetSymbol.MetadataName; + var result = new ExtractCommentStepResult(typeMetadataName, rawComment, parsedComment); + + return result; + } +} diff --git a/DocReflect/Generators/ExtractCommentStepResult.cs b/DocReflect/Generators/ExtractCommentStepResult.cs new file mode 100644 index 0000000..505554d --- /dev/null +++ b/DocReflect/Generators/ExtractCommentStepResult.cs @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DocReflect.Generators; + +using System.Collections.Generic; +using System.Xml; + +internal readonly struct ExtractCommentStepResult(String typeMetadataName, String rawComment, XmlDocument parsedComment) + : IEquatable +{ + public readonly String TypeMetadataName { get; } = typeMetadataName; + private readonly String _rawComment = rawComment; + public readonly XmlDocument ParsedComment { get; } = parsedComment; + + public override Boolean Equals(Object? obj) => obj is ExtractCommentStepResult result && Equals(result); + public Boolean Equals(ExtractCommentStepResult other) => TypeMetadataName == other.TypeMetadataName && _rawComment == other._rawComment; + + public override Int32 GetHashCode() + { + var hashCode = -15450755; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(TypeMetadataName); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_rawComment); + return hashCode; + } + + public static Boolean operator ==(ExtractCommentStepResult left, ExtractCommentStepResult right) => left.Equals(right); + public static Boolean operator !=(ExtractCommentStepResult left, ExtractCommentStepResult right) => !(left == right); +} diff --git a/DocReflect/Generators/FinalStepResult.cs b/DocReflect/Generators/FinalStepResult.cs new file mode 100644 index 0000000..76739aa --- /dev/null +++ b/DocReflect/Generators/FinalStepResult.cs @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DocReflect.Generators; + +internal readonly record struct FinalStepResult(String HintName, String SourceText); diff --git a/DocReflect/Generators/ParseStepResult.cs b/DocReflect/Generators/ParseStepResult.cs new file mode 100644 index 0000000..5374248 --- /dev/null +++ b/DocReflect/Generators/ParseStepResult.cs @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DocReflect.Generators; + +using System.Collections.Generic; + +internal readonly struct ParseStepResult(String typeMetadataName, String rawComment, TypeDocumentation documentation) : IEquatable +{ + public readonly String TypeMetadataName { get; } = typeMetadataName; + private readonly String _rawComment = rawComment; + + public readonly TypeDocumentation Documentation { get; } = documentation; + + public override Boolean Equals(Object? obj) => obj is ParseStepResult result && Equals(result); + public Boolean Equals(ParseStepResult other) => TypeMetadataName == other.TypeMetadataName && _rawComment == other._rawComment; + + public override Int32 GetHashCode() + { + var hashCode = 462352912; + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(TypeMetadataName); + hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(_rawComment); + return hashCode; + } + + public static Boolean operator ==(ParseStepResult left, ParseStepResult right) => left.Equals(right); + public static Boolean operator !=(ParseStepResult left, ParseStepResult right) => !(left == right); +} diff --git a/DocReflect/Library/Comments/CommentContents.Internal.cs b/DocReflect/Library/Comments/CommentContents.Internal.cs new file mode 100644 index 0000000..e05e74e --- /dev/null +++ b/DocReflect/Library/Comments/CommentContents.Internal.cs @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DocReflect.Comments; + +using RhoMicro.CodeAnalysis.Library.Text.SourceTexts; + +using static RhoMicro.CodeAnalysis.Library.Text.SourceTexts.IndentedStringBuilder.Appendables; + +public partial record struct CommentContents : IIndentedStringBuilderAppendable +{ + void IIndentedStringBuilderAppendable.AppendTo(IndentedStringBuilder builder) => _ = builder.Operators + + "new " + typeof(CommentContents).FullName + '(' + NewLine + "\"\"\"" + NewLine + Text + NewLine + "\"\"\"" + ')'; +} diff --git a/DocReflect/Library/Comments/DocumentationComment.Internal.cs b/DocReflect/Library/Comments/DocumentationComment.Internal.cs new file mode 100644 index 0000000..858102f --- /dev/null +++ b/DocReflect/Library/Comments/DocumentationComment.Internal.cs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DocReflect.Comments; + +using RhoMicro.CodeAnalysis.Library.Text.SourceTexts; + +public partial record DocumentationComment : IIndentedStringBuilderAppendable +{ + void IIndentedStringBuilderAppendable.AppendTo(IndentedStringBuilder builder) => _ = builder.Operators + + "new " + typeof(DocumentationComment).FullName + '(' + Contents + ')'; +} diff --git a/DocReflect/Library/TypeDocumentation.Internal.cs b/DocReflect/Library/TypeDocumentation.Internal.cs new file mode 100644 index 0000000..ae1abbb --- /dev/null +++ b/DocReflect/Library/TypeDocumentation.Internal.cs @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DocReflect; + +using RhoMicro.CodeAnalysis.Library.Text.SourceTexts; + +using static RhoMicro.CodeAnalysis.Library.Text.SourceTexts.IndentedStringBuilder.Appendables; + +public partial class Documentation : IIndentedStringBuilderAppendable +{ + void IIndentedStringBuilderAppendable.AppendTo(IndentedStringBuilder builder) => _ = builder.Operators + + "new " + GetType().FullName + OpenBlock(Blocks.Parentheses with + { + Indentation = builder.Options.DefaultIndentation, + PlaceDelimitersOnNewLine = true + }) + AppendJoin(TopLevelComments) + CloseBlock(); +} diff --git a/DocReflect/Properties/launchSettings.json b/DocReflect/Properties/launchSettings.json new file mode 100644 index 0000000..456d652 --- /dev/null +++ b/DocReflect/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Profile 1": { + "commandName": "DebugRoslynComponent", + "targetProject": "..\\TestApp\\TestApp.csproj" + } + } +} \ No newline at end of file diff --git a/DocReflect/README.md b/DocReflect/README.md new file mode 100644 index 0000000..1837558 --- /dev/null +++ b/DocReflect/README.md @@ -0,0 +1,3 @@ +# DocReflect + +Generates reflection-like API to examine member documentation. \ No newline at end of file diff --git a/DslGenerator.TestApp/DslGenerator.TestApp.csproj b/DslGenerator.TestApp/DslGenerator.TestApp.csproj new file mode 100644 index 0000000..c3ded4c --- /dev/null +++ b/DslGenerator.TestApp/DslGenerator.TestApp.csproj @@ -0,0 +1,32 @@ + + + + net9.0 + exe + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + diff --git a/DslGenerator.TestApp/Program.cs b/DslGenerator.TestApp/Program.cs new file mode 100644 index 0000000..95102e6 --- /dev/null +++ b/DslGenerator.TestApp/Program.cs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace DslGenerator.TestApp; + +internal partial class Program +{ + private static void Main(String[] _) + { + + } +} diff --git a/DslGenerator.TestApp/README.md b/DslGenerator.TestApp/README.md new file mode 100644 index 0000000..9fd5017 --- /dev/null +++ b/DslGenerator.TestApp/README.md @@ -0,0 +1,3 @@ +# DslGenerator.TestApp + +Test application for the dsl generator \ No newline at end of file diff --git a/DslGenerator.TestApp/Rmbnf.rmbnf b/DslGenerator.TestApp/Rmbnf.rmbnf new file mode 100644 index 0000000..c38f4ec --- /dev/null +++ b/DslGenerator.TestApp/Rmbnf.rmbnf @@ -0,0 +1,32 @@ +RhoMicroBackusNaurForm; + +RuleList = [Name ";"] *RuleDefinition; +RuleDefinition = Name "=" Rule ";"; + +Rule = Binary / Unary / Primary; + +Binary = Range / Concatenation / Alternative; +Concatenation = Unary / (Rule Whitespace Rule); +Alternative = Unary / (Rule "/" Rule); +Range = Unary / (SingleAlpha "-" SingleAlpha); + +Unary = VariableRepetition / SpecificRepetition; +VariableRepetition = Primary / ("*" Rule); +SpecificRepetition = Primary / (Digit Rule); + +Primary = Grouping / OptionalGrouping / TerminalOrName; +Grouping = TerminalOrName / ("(" Rule ")"); +OptionalGrouping = TerminalOrName / ("[" Rule "]"); +TerminalOrName = Terminal / Any / Name; +Name = Alpha; +Terminal = "\"" . "\""; +Any = "."; + +Trivia = Whitespace / NewLine; +Whitespace = Space / Tab *Whitespace; +Space = " "; +Tab = " "; +NewLine = "\n" / "\r\n" / "\r"; +SingleAlpha = "a"-"z" / "A"-"Z" / "_"; +Alpha = SingleAlpha *SingleAlpha; +Digit = "0"-"9" *Digit; diff --git a/DslGenerator.Tests/DslGenerator.Tests.csproj b/DslGenerator.Tests/DslGenerator.Tests.csproj new file mode 100644 index 0000000..f542baa --- /dev/null +++ b/DslGenerator.Tests/DslGenerator.Tests.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + RhoMicro.CodeAnalysis.DslGenerator.Tests + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/DslGenerator.Tests/GlobalUsings.cs b/DslGenerator.Tests/GlobalUsings.cs new file mode 100644 index 0000000..cb9744e --- /dev/null +++ b/DslGenerator.Tests/GlobalUsings.cs @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 + +global using Xunit; +global using RhoMicro.CodeAnalysis.DslGenerator; diff --git a/DslGenerator.Tests/ParserTests.cs b/DslGenerator.Tests/ParserTests.cs new file mode 100644 index 0000000..2f4e3d7 --- /dev/null +++ b/DslGenerator.Tests/ParserTests.cs @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: MPL-2.0 + +#pragma warning disable CA1819 // Properties should not return arrays +namespace RhoMicro.CodeAnalysis.DslGenerator.Tests; + +using RhoMicro.CodeAnalysis.DslGenerator.Analysis; +using RhoMicro.CodeAnalysis.DslGenerator.Grammar; +using RhoMicro.CodeAnalysis.DslGenerator.Lexing; +using RhoMicro.CodeAnalysis.DslGenerator.Parsing; + +using System; +using System.Diagnostics; +using System.Linq; + +public class ParserTests +{ + public static Object[][] Data => + [ + [ + "rule=rule;", + new RuleListBuilder().New("rule", b => b.Reference("rule")), + Array.Empty() + ], + [ + """rule="a"-"z";""", + new RuleListBuilder().New("rule", b => b.Range('a', 'z')), + Array.Empty() + ], + [ + """rule= other / "a"-"z";""", + new RuleListBuilder().New("rule", b => b.Alternative(b => b.Reference("other").Range('a', 'z'))), + Array.Empty() + ], + [ + "rule = rule; ", + new RuleListBuilder().New("rule", b => b.Reference("rule")), + Array.Empty() + ], + [ + """ + + rule = # some commented stuff + rule; + """, + new RuleListBuilder().New("rule", b => b.Reference("rule")), + Array.Empty() + ], + [ + """ + rule = [optionalRule]; + """, + new RuleListBuilder().New("rule", b => b.OptionalGrouping(b => b.Reference("optionalRule"))), + Array.Empty() + ], + [ + """ + rule = (groupedRule); + """, + new RuleListBuilder().New("rule", b => b.Grouping(b => b.Reference("groupedRule"))), + Array.Empty() + ], + [ + """ + testRuleList; + rule = (groupedRule); + """, + new RuleListBuilder("testRuleList").New("rule", b => b.Grouping(b => b.Reference("groupedRule"))), + Array.Empty() + ], + [ + "rule = (\"groupedTerminal\");", + new RuleListBuilder().New("rule", b => b.Grouping(b => b.Terminal("groupedTerminal"))), + Array.Empty() + ], + [ + "rule = [\"groupedTerminal\"];", + new RuleListBuilder().New("rule", b => b.OptionalGrouping(b => b.Terminal("groupedTerminal"))), + Array.Empty() + ], + [ + """ + testname = ["is this the only legal value?"] / # here is a comment + *"no, it's not :)"; # and another comment + """, + new RuleListBuilder().New("testname", b => + b.Alternative(b => + b.OptionalGrouping(b => + b.Terminal("is this the only legal value?")) + .VariableRepetition(b => + b.Terminal("no, it's not :)")) + )), + Array.Empty() + ], + [ + """ + testname /= ["is this the only legal value?"] / # here is a comment + *"no, it's not :)"; # and another comment + """, + new RuleListBuilder().Incremental("testname", b => + b.Alternative(b => + b.OptionalGrouping(b => + b.Terminal("is this the only legal value?")) + .VariableRepetition(b => + b.Terminal("no, it's not :)")) + )), + Array.Empty() + ], + [ + + "rule=\"\";", + new RuleListBuilder().New("rule", b => + b.Terminal(String.Empty) + ), + Array.Empty() + ], + [ + "Range = SingleAlpha \"-\" SingleAlpha;", + new RuleListBuilder().New("Range", b => + b.Reference("SingleAlpha").Terminal("-").Reference("SingleAlpha")), + Array.Empty() + ], + [ + """ + TestGrammarName; + testname = ["is this the only legal value?"] / # here is a comment + *"no, it's not :)"; # and another comment + testname /= 77here (is *some more) "ruley" [goodness]; # :) + more = defs; + testname /= more; + more /= no; + """, + new RuleListBuilder("TestGrammarName") + .New("testname", b => + b.Alternative(b => + b.OptionalGrouping(b => b.Terminal("is this the only legal value?")) + .VariableRepetition(b => b.Terminal("no, it's not :)")))) + .Incremental("testname", b => + b.SpecificRepetition(77, b => b.Reference("here")) + .Grouping(b => b.Reference("is").VariableRepetition(b => b.Reference("some")).Reference("more")) + .Terminal("ruley") + .OptionalGrouping(b => b.Reference("goodness"))) + .New("more", b => b.Reference("defs")) + .Incremental("testname", b => b.Reference("more")) + .Incremental("more", b => b.Reference("no")), + Array.Empty() + ], + [ + "TerminalValue = \"\\\"\" . \"\\\"\";", + new RuleListBuilder().New("TerminalValue", b => + b.Terminal("\\\"").Any().Terminal("\\\"")), + Array.Empty() + ], + [ + """ + RhoMicroBackusNaurForm; + + RuleList = [Name ";"] *RuleDefinition; + RuleDefinition = Name "=" Rule ";"; + + Rule = Binary / Unary / Primary; + + Binary = Unary / Range / Concatenation / Alternative; + Concatenation = Rule Whitespace Rule; + Alternative = Rule "/" Rule; + Range = SingleAlpha "-" SingleAlpha; + + Unary = Primary / VariableRepetition / SpecificRepetition; + VariableRepetition = "*" Rule; + SpecificRepetition = Digit Rule; + + Primary = Grouping / OptionalGrouping / TerminalOrNameOrAny; + Grouping = "(" Rule ")"; + OptionalGrouping = "[" Rule "]"; + TerminalOrNameOrAny = Terminal / Name / Any ; + Terminal = "\"" . "\""; + Name = Alpha; + Any = "."; + + Trivia = Whitespace / NewLine; + Whitespace = Space / Tab *Whitespace; + Space = " "; + Tab = " "; + NewLine = "\n" / "\r\n" / "\r"; + SingleAlpha = "a"-"z" / "A"-"Z" / "_"; + Alpha = SingleAlpha *SingleAlpha; + Digit = "0"-"9" *Digit; + + """, + new RuleListBuilder("RhoMicroBackusNaurForm") + + .New("RuleList", b => b.OptionalGrouping(b => b.Reference("Name").Terminal(";")).VariableRepetition("RuleDefinition")) + .New("RuleDefinition", b => b.Reference("Name").Terminal("=").Reference("Rule").Terminal(";")) + + .New("Rule", b => b.Alternative("Binary", "Unary", "Primary")) + + .New("Binary", b => b.Alternative("Unary", "Range", "Concatenation", "Alternative")) + .New("Concatenation", b => b.Concatenation("Rule", "Whitespace", "Rule")) + .New("Alternative", b => b.Reference("Rule").Terminal("/").Reference("Rule")) + .New("Range", b => b.Reference("SingleAlpha").Terminal("-").Reference("SingleAlpha")) + + .New("Unary", b => b.Alternative("Primary", "VariableRepetition", "SpecificRepetition")) + .New("VariableRepetition", b => b.Terminal("*").Reference("Rule")) + .New("SpecificRepetition", b => b.Concatenation("Digit", "Rule")) + + .New("Primary", b => b.Alternative("Grouping", "OptionalGrouping", "TerminalOrNameOrAny")) + .New("Grouping", b => b.Terminal("(").Reference("Rule").Terminal(")")) + .New("OptionalGrouping", b => b.Terminal("[").Reference("Rule").Terminal("]")) + .New("TerminalOrNameOrAny", b => b.Alternative("Terminal", "Name", "Any")) + .New("Terminal", b => b.Terminal("\\\"").Any().Terminal("\\\"")) + .New("Name", b => b.Reference("Alpha")) + .New("Any", b => b.Terminal(".")) + + .New("Trivia", b => b.Alternative("Whitespace", "NewLine")) + .New("Whitespace", b => b.Alternative("Space", "Tab").VariableRepetition("Whitespace")) + .New("Space", b => b.Terminal(" ")) + .New("Tab", b => b.Terminal("\t")) + .New("NewLine", b => b.Alternative(b => b.Terminal("\\n").Terminal("\\r\\n").Terminal("\\r"))) + .New("SingleAlpha", b => b.Alternative(b => b.Range('a', 'z').Range('A', 'Z').Terminal("_"))) + .New("Alpha", b => b.Reference("SingleAlpha").VariableRepetition("SingleAlpha")) + .New("Digit", b => b.Range('0', '9').VariableRepetition("Digit")), + + Array.Empty() + ] + ]; + + [Theory] + [MemberData(nameof(Data))] + public void ParsesCorrectly(String source, Object expectedWeak, Int32[] expectedDiagnosticIds) + { + //Arrange +#pragma warning disable CA1062 // Validate arguments of public methods + var expectedRuleList = ((RuleListBuilder)expectedWeak).Build(); +#pragma warning restore CA1062 // Validate arguments of public methods + var tokenizeResult = new Tokenizer().Tokenize(source, default, String.Empty); + var parser = new Parser(); + + //Act + var (actualRuleList, actualDiagnostics) = parser.Parse(tokenizeResult, default); + + //Assert + try + { + Assert.Equal(expectedRuleList, actualRuleList); + VerifyDiagnostics(expectedDiagnosticIds, actualDiagnostics); + } catch + { + var expected = expectedRuleList.ToDisplayString(default).Split('\n'); + var actual = actualRuleList.ToDisplayString(default).Split('\n'); + var comparisonStringParts = expected + .Select((e, i) => (e, i)) + .Where((t) => actual.ElementAtOrDefault(t.i) != t.e) + .Select((t, i) => $"{t.e} # EXPECTED\n{actual.ElementAtOrDefault(t.i) ?? "# MISSING"} # ACTUAL"); + var comparisonString = String.Join("\n\n", comparisonStringParts); + + Debugger.Break(); + _ = parser.Parse(tokenizeResult, default); + throw; + } + } + private static void VerifyDiagnostics(Int32[] expectedDiagnosticIds, DiagnosticsCollection actualDiagnostics) + { + var actualDiagnosticIdCounts = actualDiagnostics.GroupBy(d => d.Descriptor.Id) + .Select(g => (id: g.Key, count: g.Count())) + .ToDictionary(t => t.id, t => t.count); + foreach(var (id, expectedCount) in expectedDiagnosticIds.GroupBy(id => id).Select(g => (id: g.Key, count: g.Count()))) + { + var actualCount = actualDiagnosticIdCounts.TryGetValue(id, out var c) ? c : -1; + Assert.Equal(expectedCount, actualCount); + } + } +} diff --git a/DslGenerator.Tests/TokenizerTests.cs b/DslGenerator.Tests/TokenizerTests.cs new file mode 100644 index 0000000..6796bf6 --- /dev/null +++ b/DslGenerator.Tests/TokenizerTests.cs @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: MPL-2.0 + +#pragma warning disable CA1861 // Avoid constant arrays as arguments +namespace DslGenerator.Tests; + +using RhoMicro.CodeAnalysis.DslGenerator.Analysis; +using RhoMicro.CodeAnalysis.DslGenerator.Lexing; + +using System.Collections.Immutable; +using System.Diagnostics; + +public class TokenizerTests +{ +#pragma warning disable RS1035 // Do not use APIs banned for analyzers + public static Object[][] TokenizerData => [ + ["name=name", new[] { "Name:name", "Equal:=", "Name:name" }, Array.Empty()], + ["name=\"\";", new[] { "Name:name", "Equal:=", "Terminal:", "Semicolon:;" }, Array.Empty()], + ["name=\"a\"-\"z\";", new[] { "Name:name", "Equal:=", "Terminal:a", "Dash:-", "Terminal:z", "Semicolon:;" }, Array.Empty()], + ["name", new[] { "Name:name" }, Array.Empty()], + ["=", new[] { "Equal:=" }, Array.Empty()], + [".", new[] { "Period:." }, Array.Empty()], + ["TerminalValue = \"\\\"\" . \"\\\"\";", + new[] { "Name:TerminalValue", "Whitespace: ", "Equal:=", "Whitespace: ", "Terminal:\\\"", "Whitespace: ", "Period:.", "Whitespace: ", "Terminal:\\\"", "Semicolon:;" }, + Array.Empty()], + ["-", new[] { "Dash:-" }, Array.Empty()], + ["\"a\"-\"z\"", new[] { "Terminal:a", "Dash:-", "Terminal:z" }, Array.Empty()], + ["\"ab\"-\"z\"", new[] { "Terminal:ab", "Dash:-", "Terminal:z" }, Array.Empty()], + [" ", new[] { "Whitespace: " }, Array.Empty()], + [" ", new[] { "Whitespace: " }, Array.Empty()], + ["\t", new[] { "Whitespace:\t" }, Array.Empty()], + ["\t\t\t", new[] { "Whitespace:\t\t\t" }, Array.Empty()], + ["\n", new[] { "NewLine:\n" }, Array.Empty()], + ["\n\n\n", new[] { "NewLine:\n", "NewLine:\n", "NewLine:\n" }, Array.Empty()], + ["\r", new[] { "NewLine:\r" }, Array.Empty()], + ["\r\r\r", new[] { "NewLine:\r", "NewLine:\r", "NewLine:\r" }, Array.Empty()], + ["\r\n", new[] { "NewLine:\r\n" }, Array.Empty()], + ["\r\n\r\n\r\n", new[] { "NewLine:\r\n", "NewLine:\r\n", "NewLine:\r\n" }, Array.Empty()], + ["\r\n\n\r\n", new[] { "NewLine:\r\n", "NewLine:\n", "NewLine:\r\n" }, Array.Empty()], + [";", new[] { "Semicolon:;" }, Array.Empty()], + ["\"", Array.Empty(), Array.Empty()], + ["/", new[] { "Slash:/" }, Array.Empty()], + ["#", Array.Empty(), Array.Empty()], + ["# this is a comment; 24224 ����", new[] { "Comment: this is a comment; 24224 ����" }, Array.Empty()], + ["/=", new[] { "SlashEqual:/=" }, Array.Empty()], + ["(", new[] { "ParenLeft:(" }, Array.Empty()], + [")", new[] { "ParenRight:)" }, Array.Empty()], + ["[", new[] { "BracketLeft:[" }, Array.Empty()], + ["]", new[] { "BracketRight:]" }, Array.Empty()], + ["32rule", new[] { "Number:32", "Name:rule" }, Array.Empty()], + ["&33+", new[] { "Unknown:&", "Number:33", "Unknown:+" }, Array.Empty()], + ["%%%", new[] { "Unknown:%%%" }, new[] { 1 }], + ["\"TerminalValue\"", new[] { "Terminal:TerminalValue" }, Array.Empty()], + ["\"TerminalValue", new[] { "Terminal:TerminalValue" }, new[] { 2 }], + ["\"TerminalValue;rule=somename;", new[] { "Terminal:TerminalValue;rule=somename;" }, new[] { 2 }], + ["\"TerminalValue;ru\\\"le=somename;\"", new[] { "Terminal:TerminalValue;ru\\\"le=somename;" }, Array.Empty()], + [ """ + testname = ["is this the 1 legal value?"] / # here is a comment + *"no, it's not :)"; # and another comment + + """, + new[] { "Name:testname", "Whitespace: ", "Equal:=", "Whitespace: ", + "BracketLeft:[", "Terminal:is this the 1 legal value?", "BracketRight:]", + "Whitespace: ","Slash:/","Whitespace: ", "Comment: here is a comment", $"NewLine:\n", + "Whitespace: ","Star:*","Terminal:no, it's not :)" , + "Semicolon:;", "Whitespace: ", "Comment: and another comment", $"NewLine:\n", + }, + Array.Empty()], + ["a=b;a/=c;", new[] { "Name:a", "Equal:=", "Name:b", "Semicolon:;", "Name:a", "SlashEqual:/=", "Name:c", "Semicolon:;" }, Array.Empty()] + ]; +#pragma warning restore RS1035 // Do not use APIs banned for analyzers + + [Theory(Timeout = 5000)] + [MemberData(nameof(TokenizerData))] + public async Task TokenizesCorrectly(String source, String[] rawTokens, Int32[] expectedDiagnosticIds) + { + //Arrange + var expectedTokens = rawTokens.Select(t => t.Split(':', 2)) + .Select(t => (Type: Enum.Parse(t[0]), Lexeme: (Lexeme)String.Concat(t[1..]))) + .Select(t => new Token(t.Type, t.Lexeme)) + .Append(new Token(TokenType.Eof, Lexeme.Empty)) + .ToImmutableArray(); + var tokenizer = new Tokenizer(); + + //Act + var (actualTokens, actualDiagnostics) = tokenizer.Tokenize(source, default, String.Empty); + + //Assert + try + { + VerifyTokens(expectedTokens, actualTokens); + VerifyDiagnostics(expectedDiagnosticIds, actualDiagnostics); + } catch + { + Debugger.Break(); + _ = Tokenizer.Instance.Tokenize(source, default); + throw; + } + } + + private static void VerifyDiagnostics(Int32[] expectedDiagnosticIds, DiagnosticsCollection actualDiagnostics) + { + var actualDiagnosticIdCounts = actualDiagnostics.GroupBy(d => d.Descriptor.Id) + .Select(g => (id: g.Key, count: g.Count())) + .ToDictionary(t => t.id, t => t.count); + foreach(var (id, expectedCount) in expectedDiagnosticIds.GroupBy(id => id).Select(g => (id: g.Key, count: g.Count()))) + { + var actualCount = actualDiagnosticIdCounts.TryGetValue(id, out var c) ? c : -1; + Assert.Equal(expectedCount, actualCount); + } + } + + private static void VerifyTokens(IReadOnlyList expectedTokens, IReadOnlyList actualTokens) + { + for(var i = 0; i < expectedTokens.Count; i++) + { + var expectedToken = expectedTokens[i]; + if(actualTokens.Count <= i) + Assert.Fail($"Not enough tokens produced; missing {expectedToken}"); + + var actualToken = actualTokens[i]; + Assert.Equal(expectedToken, actualToken); + } + } +} diff --git a/DslGenerator/Analysis/Diagnostic.cs b/DslGenerator/Analysis/Diagnostic.cs new file mode 100644 index 0000000..69662ce --- /dev/null +++ b/DslGenerator/Analysis/Diagnostic.cs @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Analysis; +using System; + +using RhoMicro.CodeAnalysis.DslGenerator.Lexing; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +sealed record Diagnostic(DiagnosticDescriptor Descriptor, Location Location, IReadOnlyCollection MessageArgs) +{ + public static Diagnostic Create(DiagnosticDescriptor descriptor, Location location, params Object[] messageArgs) => + new(descriptor, location, messageArgs); +#if DSL_GENERATOR + public Microsoft.CodeAnalysis.Diagnostic ToMsDiagnostic() => + Microsoft.CodeAnalysis.Diagnostic.Create( + Descriptor.ToMsDescriptor(), + Location.ToMsLocation(), + MessageArgs.ToArray()); +#endif +} diff --git a/DslGenerator/Analysis/DiagnosticDescriptor.cs b/DslGenerator/Analysis/DiagnosticDescriptor.cs new file mode 100644 index 0000000..576d07a --- /dev/null +++ b/DslGenerator/Analysis/DiagnosticDescriptor.cs @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Analysis; +using System; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +readonly record struct DiagnosticDescriptor(Int32 Id, String Title, String Message) +{ +#if DSL_GENERATOR + private const String _diagnosticsCategory = "RhoMicro.CodeAnalysis.DslGenerator"; + public Microsoft.CodeAnalysis.DiagnosticDescriptor ToMsDescriptor() => + new( + $"DSLG{Id:0000}", + Title, + Message, + _diagnosticsCategory, + Microsoft.CodeAnalysis.DiagnosticSeverity.Error, + isEnabledByDefault: true); +#endif +} diff --git a/DslGenerator/Analysis/DiagnosticDescriptors.cs b/DslGenerator/Analysis/DiagnosticDescriptors.cs new file mode 100644 index 0000000..dc653e9 --- /dev/null +++ b/DslGenerator/Analysis/DiagnosticDescriptors.cs @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Analysis; +using System; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +static class DiagnosticDescriptors +{ + private static DiagnosticDescriptor Create( + Int32 id, + String title, + String message) => + new(id, title, message); + + public static DiagnosticDescriptor UnexpectedCharacter { get; } = + Create(1, "Unexpected Character", "Encountered unexpected character."); + public static DiagnosticDescriptor UnterminatedTerminal { get; } = + Create(2, "Unterminated Terminal", "Encountered an unterminated terminal (missing closing quote)."); + public static DiagnosticDescriptor UnexpectedToken { get; } = + Create(3, "Unexpected Token", "Encountered an unexpected '{0}' token. {1}"); + public static DiagnosticDescriptor InvalidRangeToken { get; } = + Create(4, "Invalid Range", "Encountered a range token. Ranges must be surrounded by terminals of one character."); +} diff --git a/DslGenerator/Analysis/DiagnosticsCollection.cs b/DslGenerator/Analysis/DiagnosticsCollection.cs new file mode 100644 index 0000000..af2caec --- /dev/null +++ b/DslGenerator/Analysis/DiagnosticsCollection.cs @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Analysis; + +using RhoMicro.CodeAnalysis.DslGenerator.Lexing; + +using System; +using System.Collections; +using System.Collections.Generic; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +sealed class DiagnosticsCollection : IReadOnlyList +{ + private readonly List _diagnostics = []; + +#if DSL_GENERATOR + public void ReportToContext(Microsoft.CodeAnalysis.SourceProductionContext context) + { + foreach(var diagnostic in _diagnostics) + { + var msDiagnostic = diagnostic.ToMsDiagnostic(); + context.ReportDiagnostic(msDiagnostic); + } + } +#endif + public void Add(DiagnosticDescriptor descriptor, Location location, params Object[] messageArgs) => + _diagnostics.Add(new Diagnostic(descriptor, location, messageArgs)); + public void Add(DiagnosticsCollection diagnostics) => _diagnostics.AddRange(diagnostics); + + public Diagnostic this[Int32 index] => ((IReadOnlyList)_diagnostics)[index]; + + public Int32 Count => ((IReadOnlyCollection)_diagnostics).Count; + + public IEnumerator GetEnumerator() => ((IEnumerable)_diagnostics).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)_diagnostics).GetEnumerator(); +} diff --git a/DslGenerator/DslGenerator.csproj b/DslGenerator/DslGenerator.csproj new file mode 100644 index 0000000..b109d5c --- /dev/null +++ b/DslGenerator/DslGenerator.csproj @@ -0,0 +1,56 @@ + + + + + netstandard2.0 + false + true + true + true + enable + enable + + + + + + + + true + true + Generates utilities for lexing and parsing domain specific languages, little languages etc. + Source Generator + + + + $(DefineConstants);DSL_GENERATOR + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + diff --git a/DslGenerator/Generators/Attributes/GeneratedGrammarAttribute.cs b/DslGenerator/Generators/Attributes/GeneratedGrammarAttribute.cs new file mode 100644 index 0000000..c6fd5b3 --- /dev/null +++ b/DslGenerator/Generators/Attributes/GeneratedGrammarAttribute.cs @@ -0,0 +1,11 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis; + +using System; + +#if DSL_GENERATOR +[GenerateFactory] +#endif +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] +internal sealed partial class GeneratedGrammarAttribute : Attribute; diff --git a/DslGenerator/Generators/Attributes/GeneratedRuleListAttribute.cs b/DslGenerator/Generators/Attributes/GeneratedRuleListAttribute.cs new file mode 100644 index 0000000..74661dc --- /dev/null +++ b/DslGenerator/Generators/Attributes/GeneratedRuleListAttribute.cs @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis; + +using System; + +#if DSL_GENERATOR +[GenerateFactory] +#endif +[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] +internal sealed partial class GeneratedRuleListAttribute(String source) : Attribute +{ + public String Source { get; } = source; +} diff --git a/DslGenerator/Generators/DslGenerator.cs b/DslGenerator/Generators/DslGenerator.cs new file mode 100644 index 0000000..1571198 --- /dev/null +++ b/DslGenerator/Generators/DslGenerator.cs @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Generators; + +using Microsoft.CodeAnalysis; +using RhoMicro.CodeAnalysis.Generated; +using RhoMicro.CodeAnalysis.Library; +using RhoMicro.CodeAnalysis.DslGenerator.Lexing; +using RhoMicro.CodeAnalysis.DslGenerator.Parsing; +using RhoMicro.CodeAnalysis.DslGenerator.Grammar; +using RhoMicro.CodeAnalysis.DslGenerator.Analysis; +using System.Collections.Immutable; +using RhoMicro.CodeAnalysis.Library.Text; + +/// +/// Generates utilities for domain specific languages. +/// +[Generator(LanguageNames.CSharp)] +public sealed class DslGenerator : IIncrementalGenerator +{ + private const String _grammarFileExtension = ".rmbnf"; + private static readonly IEqualityComparer<(String source, DiagnosticsCollection diagnostics)> _sourceDiagnosticsTupleComparer = + new EqualityComparerStrategy<(String source, DiagnosticsCollection diagnostics)>( + (x, y) => x.source == y.source, + obj => obj.source.GetHashCode()); + private static readonly IEqualityComparer> _sourceDiagnosticsTupleArrayComparer = + new ImmutableArrayCollectionEqualityComparer<(String source, DiagnosticsCollection diagnostics)>(_sourceDiagnosticsTupleComparer); + + /// + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var provider = context.AdditionalTextsProvider + .Where(text => Path.GetExtension(text.Path) == _grammarFileExtension) + .Select((text, ct) => (tokenizeResult: new Tokenizer().Tokenize(text.GetText(ct)?.ToString() ?? String.Empty, ct, text.Path), fileName: Path.GetFileNameWithoutExtension(text.Path))) + .Select((t, ct) => (parseResult: new Parser().Parse(t.tokenizeResult, ct), t.fileName)) + .Select((t, ct) => (t.parseResult, isNamed: t.parseResult.RuleList is NamedRuleList, t.fileName)) + .Select((t, ct) => ( + t.parseResult, + name: t.isNamed ? ((NamedRuleList)t.parseResult.RuleList).Name.ToDisplayString(ct) : t.fileName, + type: t.isNamed ? nameof(NamedRuleList) : nameof(RuleList))) + .Where(t => Microsoft.CodeAnalysis.CSharp.SyntaxFacts.IsValidIdentifier(t.name)) + .Select((t, ct) => + { + ct.ThrowIfCancellationRequested(); + + var (parseResult, name, type) = t; + var (ruleList, diagnostics) = parseResult; + var source = new IndentedStringBuilder() + .Indent() + .Comment.OpenSummary() + .Comment.OpenParagraph() + .Append("Matches the following grammar:") + .CloseBlock() // + .AppendCommentParagraphs(ruleList, ct) + .CloseBlock() // + .Append("public static ").Append(type).Append(' ').Append(name).Append(' ').AppendLine("{ get; } = ") + .OpenIndentBlock() + .AppendMetaString(ruleList, ct).AppendLine(";") + .CloseBlock() + .ToString(); + + return (source, diagnostics); + }) + .WithComparer(_sourceDiagnosticsTupleComparer) + .Collect() + .WithComparer(_sourceDiagnosticsTupleArrayComparer) + .Select((sourceTuples, ct) => + { + var diagnosticsAggregator = new DiagnosticsCollection(); + var sourceBuilder = new IndentedStringBuilder( + IndentedStringBuilderOptions.GeneratedFile with + { + GeneratorName = "RhoMicro.CodeAnalysis.DslGenerator" + }) + .AppendLine("namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar.Generated;") + .Append("static class GeneratedRuleLists") + .OpenBracesBlock(); + + for(var i = 0; i < sourceTuples.Length; i++) + { + var (source, diagnostics) = sourceTuples[i]; + _ = sourceBuilder.AppendLine(source); + diagnosticsAggregator.Add(diagnostics); + } + + var ruleListsSource = sourceBuilder.CloseAllBlocks().ToString(); + + return (source: ruleListsSource, diagnostics: diagnosticsAggregator); + }) + .WithComparer(new EqualityComparerStrategy<(String source, DiagnosticsCollection diagnostics)>( + (x, y) => x.source == y.source, + obj => obj.source.GetHashCode())); + + context.RegisterSourceOutput(provider, (ctx, data) => + { + var (source, diagnostics) = data; + ctx.AddSource("GeneratedRuleLists.g.cs", source); + diagnostics.ReportToContext(ctx); + }); + IncludedFileSources.RegisterToContext(context); + } +} diff --git a/DslGenerator/Generators/GeneratedRuleListGenerator.cs b/DslGenerator/Generators/GeneratedRuleListGenerator.cs new file mode 100644 index 0000000..6c4f357 --- /dev/null +++ b/DslGenerator/Generators/GeneratedRuleListGenerator.cs @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Generators; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +using RhoMicro.CodeAnalysis.DslGenerator.Lexing; +using RhoMicro.CodeAnalysis.DslGenerator.Parsing; + +using System.Collections.Immutable; +using System.Text; + +using InitialStepResult = (String unparsedList, String implTypeName, String partialTypeSourceText); +using TokenizeStepResult = (Lexing.TokenizeResult tokenizeResult, String implTypeName, String partialTypeSourceText); +using ParseStepResult = (Parsing.ParseResult parseResult, String implTypeName, String partialTypeSourceText); +using ImplTypeStepResult = (String implTypeSourceText, String partialTypeSourceText, RhoMicro.CodeAnalysis.DslGenerator.Analysis.DiagnosticsCollection diagnostics); +using FinalStepResult = (String source, RhoMicro.CodeAnalysis.DslGenerator.Analysis.DiagnosticsCollection diagnostics); +using RhoMicro.CodeAnalysis.DslGenerator.Analysis; + +[Generator(LanguageNames.CSharp)] +internal class GeneratedRuleListGenerator : IIncrementalGenerator +{ + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var provider = context.SyntaxProvider.ForGeneratedRuleListAttribute( + IsGeneratorTarget, + InitialStep) + .Where(t => t != default) + .Select(TokenizeStep) + .Select(ParseStep) + .Select(ImplTypeStep) + .WithComparer(_implTypeStepResultComparer) + .Collect() + .WithComparer(_implTypeStepResultArrayComparer) + .Select(FinalStep) + .WithComparer(_finalStepResultComparer); + + context.RegisterSourceOutput(provider, (ctx, result) => + { + var (source, diagnostics) = result; + ctx.AddSource("GeneratedRuleLists.g.cs", source); + diagnostics.ReportToContext(ctx); //TODO: map to attribute span + }); + context.RegisterPostInitializationOutput(ctx => + ctx.AddSource($"{nameof(GeneratedRuleListAttribute)}.g.cs", GeneratedRuleListAttribute.SourceText)); + } + + private static readonly EqualityComparerStrategy _implTypeStepResultComparer = new( + (x, y) => x.implTypeSourceText == y.implTypeSourceText && + x.partialTypeSourceText == y.partialTypeSourceText, + obj => (obj.implTypeSourceText, obj.partialTypeSourceText).GetHashCode()); + private static readonly ImmutableArrayCollectionEqualityComparer _implTypeStepResultArrayComparer = + new(_implTypeStepResultComparer); + private static readonly EqualityComparerStrategy _finalStepResultComparer = new( + (x, y) => x.source == y.source, + obj => obj.source.GetHashCode()); + private static readonly SymbolDisplayFormat _typeSignatureNameFormat = + SymbolDisplayFormat.MinimallyQualifiedFormat.WithGenericsOptions(SymbolDisplayGenericsOptions.IncludeTypeParameters); + private static readonly SymbolDisplayFormat _namespaceFormat = + SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted); + + private static Boolean IsGeneratorTarget(Microsoft.CodeAnalysis.SyntaxNode node, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + if(node is not MethodDeclarationSyntax mds || mds.ParameterList.Parameters.Count != 0) + return false; + + cancellationToken.ThrowIfCancellationRequested(); + + var isStatic = false; + var isPartial = false; + for(var i = 0; i < mds.Modifiers.Count; i++) + { + cancellationToken.ThrowIfCancellationRequested(); + + if(mds.Modifiers[i].IsKind(SyntaxKind.StaticKeyword)) + { + isStatic = true; + if(isPartial) + break; + } else if(mds.Modifiers[i].IsKind(SyntaxKind.PartialKeyword)) + { + isPartial = true; + if(isStatic) + break; + } + } + + if(!(isStatic || isPartial)) + return false; + + cancellationToken.ThrowIfCancellationRequested(); + + var result = mds.Body == null; + + return result; + } + + private static InitialStepResult InitialStep(GeneratorAttributeSyntaxContext context, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var target = (IMethodSymbol)context.TargetSymbol; + + if(target.ReturnType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) != + "global::RhoMicro.CodeAnalysis.DslGenerator.Grammar.RuleList") + { + return default; + } + + if(context.Attributes[0].ConstructorArguments[0].Value is not String unparsedList) + return default; + + var implTypeName = GetImplTypeName(cancellationToken); + var partialTypeSourceText = GetPartialTypeSourceText(target, implTypeName, cancellationToken); + + var result = (unparsedList, implTypeName, partialTypeSourceText); + + return result; + } + + private static TokenizeStepResult TokenizeStep(InitialStepResult previous, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var tokenizeResult = Tokenizer.Instance.Tokenize(previous.unparsedList, cancellationToken); + + return (tokenizeResult, previous.implTypeName, previous.partialTypeSourceText); + } + + private static ParseStepResult ParseStep(TokenizeStepResult previous, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var parseResult = Parser.Instance.Parse(previous.tokenizeResult, cancellationToken); + + return (parseResult, previous.implTypeName, previous.partialTypeSourceText); + } + + private static ImplTypeStepResult ImplTypeStep(ParseStepResult previous, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var implTypeSourceText = new IndentedStringBuilder() + .Append("file static class ").AppendLine(previous.implTypeName) + .OpenBracesBlock() + .AppendLine("public static global::RhoMicro.CodeAnalysis.DslGenerator.Grammar.RuleList Instance { get; } =") + .OpenIndentBlock() + .AppendMetaString(previous.parseResult.RuleList, cancellationToken).AppendLine(';') + .CloseAllBlocks() + .ToString(); + + return (implTypeSourceText, previous.partialTypeSourceText, previous.parseResult.Diagnostics); + } + + private static FinalStepResult FinalStep(ImmutableArray results, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var resultBuilder = new StringBuilder(); + var diagnosticsAccumulator = new DiagnosticsCollection(); + _ = resultBuilder.AppendLine("// ") + .Append("// This file was last generated by the RhoMicro.CodeAnalysis.DslGenerator on ").AppendLine(DateTimeOffset.Now.ToString()) + .AppendLine("// ") + .AppendLine("#pragma warning disable"); + + for(var i = 0; i < results.Length; i++) + { + var (implTypeSourceText, partialTypeSourceText, diagnostics) = results[i]; + _ = resultBuilder.AppendLine(implTypeSourceText).AppendLine(partialTypeSourceText); + diagnosticsAccumulator.Add(diagnostics); + } + + var sourceText = resultBuilder.ToString(); + + return (sourceText, diagnosticsAccumulator); + } + + private static String GetPartialTypeSourceText(IMethodSymbol target, String implTypeName, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var builder = new StringBuilder(); + var containingType = target.ContainingType; + + AppendHead(containingType, builder, cancellationToken); + AppendMethodImplementation(target, implTypeName, builder, cancellationToken); + AppendTail(builder, containingType, cancellationToken); + + //result + cancellationToken.ThrowIfCancellationRequested(); + + var result = builder.ToString(); + return result; + } + + private static void AppendMethodImplementation(IMethodSymbol target, String implTypeName, StringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = builder.Append(" ") + .Append(SyntaxFacts.GetText(target.DeclaredAccessibility)) + .Append(" static partial global::RhoMicro.CodeAnalysis.DslGenerator.Grammar.RuleList ") + .Append(target.Name) + .Append("() => ") + .Append(implTypeName) + .AppendLine(".Instance;"); + } + + private static void AppendTail(StringBuilder builder, INamedTypeSymbol containingType, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var parent = containingType; + while(parent != null) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = builder.AppendLine("}"); + parent = parent.ContainingType; + } + + if(!containingType.ContainingNamespace.IsGlobalNamespace) + { + _ = builder.AppendLine("}"); + } + } + + private static void AppendHead(INamedTypeSymbol parent, StringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + if(parent.ContainingType != null) + { + AppendHead(parent.ContainingType, builder, cancellationToken); + } else if(!parent.ContainingNamespace.IsGlobalNamespace) + { + _ = builder + .Append("namespace ") + .AppendLine(parent.ContainingNamespace.ToDisplayString(_namespaceFormat)) + .AppendLine("{"); + } + + var typeModifier = parent.IsRecord ? + parent.IsReferenceType ? + "record" : + "record struct " : + parent.IsReferenceType ? + "class " : + "struct "; + + _ = builder + .Append("partial ") + .Append(typeModifier) + .AppendLine(parent.ToDisplayString(_typeSignatureNameFormat)) + .AppendLine("{"); + } + + private static String GetImplTypeName(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var id = Guid.NewGuid().ToString().Replace('-', '_'); + var result = $"GeneratedRueList_{id}"; + + return result; + } +} diff --git a/DslGenerator/Grammar/DisplayStringVisitor.cs b/DslGenerator/Grammar/DisplayStringVisitor.cs new file mode 100644 index 0000000..2b74ebf --- /dev/null +++ b/DslGenerator/Grammar/DisplayStringVisitor.cs @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; +using System; + +internal sealed class DisplayStringVisitor(IndentedStringBuilder builder) : SyntaxNodeVisitor +{ + public DisplayStringVisitor() : this(new()) { } + private readonly IndentedStringBuilder _builder = builder; + public override void Visit(Name name) => _builder.Append(name.Value); + public override void Visit(NamedRuleList namedRuleList) => throw new NotImplementedException(); + public override void Visit(Rule.Alternative alternative) => throw new NotImplementedException(); + public override void Visit(Rule.Any any) => throw new NotImplementedException(); + public override void Visit(Rule.Concatenation concatenation) => throw new NotImplementedException(); + public override void Visit(Rule.Grouping grouping) => throw new NotImplementedException(); + public override void Visit(Rule.OptionalGrouping optionalGrouping) => throw new NotImplementedException(); + public override void Visit(Rule.Range range) => throw new NotImplementedException(); + public override void Visit(Rule.Reference reference) => throw new NotImplementedException(); + public override void Visit(Rule.SpecificRepetition specificRepetition) => throw new NotImplementedException(); + public override void Visit(Rule.Terminal terminal) => throw new NotImplementedException(); + public override void Visit(Rule.VariableRepetition variableRepetition) => throw new NotImplementedException(); + public override void Visit(RuleDefinition.New @new) => throw new NotImplementedException(); + public override void Visit(RuleDefinition.Incremental incremental) => throw new NotImplementedException(); + public override void Visit(RuleList ruleList) => throw new NotImplementedException(); +} diff --git a/DslGenerator/Grammar/IndentedStringBuilder.cs b/DslGenerator/Grammar/IndentedStringBuilder.cs new file mode 100644 index 0000000..d38f9e7 --- /dev/null +++ b/DslGenerator/Grammar/IndentedStringBuilder.cs @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.Library.Text; + +using RhoMicro.CodeAnalysis.DslGenerator.Grammar; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +partial class IndentedStringBuilder +{ + public IndentedStringBuilder AppendCommentParagraphs(RuleList ruleList, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + ruleList.AppendCommentParagraphsTo(this, cancellationToken); + + return this; + } + public IndentedStringBuilder AppendDisplayString(SyntaxNode node, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + node.AppendDisplayStringTo(this, cancellationToken); + + return this; + } + public IndentedStringBuilder AppendMetaString(SyntaxNode node, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + node.AppendMetaStringTo(this, cancellationToken); + + return this; + } +} diff --git a/DslGenerator/Grammar/ListExtensions.cs b/DslGenerator/Grammar/ListExtensions.cs new file mode 100644 index 0000000..856efa6 --- /dev/null +++ b/DslGenerator/Grammar/ListExtensions.cs @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; + +using System.Linq; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +static class ListExtensions +{ + public static IReadOnlyList Unify(this IReadOnlyList definitions) => + definitions.Aggregate(new Dictionary(), (defs, def) => + { + var (name, rule) = def; + //previous definition? + if(!defs.TryGetValue(name, out var previousRule)) + { + //new definition + defs[name] = rule; + return defs; + } + + //redefinition? + if(def is RuleDefinition.New) + { + //TODO: make illegal? + defs[name] = rule; + return defs; + } + + //incremental? + if(def is RuleDefinition.Incremental) + { + //make sure left is grouped to maintain semantics + // rule /= a / b / c + // rule = (rule) or a or b or c + var groupedPreviousRule = previousRule is Rule.Grouping or Rule.OptionalGrouping ? + previousRule : + new Rule.Grouping(previousRule); + + var alternative = new Rule.Alternative(groupedPreviousRule, rule); + defs[name] = alternative; + return defs; + } + + throw new InvalidOperationException($"Encountered unknown rule definition type: {def.GetType()}"); + }).Select(kvp => new RuleDefinition.New(kvp.Key, kvp.Value)).ToList(); +} diff --git a/DslGenerator/Grammar/Name.cs b/DslGenerator/Grammar/Name.cs new file mode 100644 index 0000000..488f21f --- /dev/null +++ b/DslGenerator/Grammar/Name.cs @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; + +using RhoMicro.CodeAnalysis.DslGenerator.Lexing; + +using System.Diagnostics; + +#if DSL_GENERATOR +[IncludeFile] +#endif +[DebuggerDisplay("{ToDisplayString()}")] +internal sealed partial record Name : SyntaxNode +{ + public Name(Token token) : this(token.Lexeme) { } + public Name(Lexeme lexeme) : this(lexeme.ToString() ?? String.Empty) { } +#pragma warning disable IDE1006 // Naming Styles + public Name(String Value) +#pragma warning restore IDE1006 // Naming Styles + { + for(var i = 0; i < Value.Length; i++) + { + if(!Utils.IsValidNameChar(Value[i])) + { + throw new ArgumentException($"Invalid character at index {i}: '{Value[i]}'. Rule names must contain letters only.)", nameof(Value)); + } + } + + this.Value = Value; + } + + public override String ToString() => base.ToString(); + public String Value { get; } + public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = builder.Append(Value); + } + + protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = AppendCtorArg(builder, nameof(Value), Value, quoteValue: true, cancellationToken); + } + public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); +} diff --git a/DslGenerator/Grammar/NamedRuleList.cs b/DslGenerator/Grammar/NamedRuleList.cs new file mode 100644 index 0000000..3fa4be2 --- /dev/null +++ b/DslGenerator/Grammar/NamedRuleList.cs @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; + +using System; +using System.Collections.Generic; +using System.Diagnostics; + +#if DSL_GENERATOR +[IncludeFile] +#endif +[DebuggerDisplay("{ToDisplayString()}")] +internal record NamedRuleList(Name Name, IReadOnlyList Definitions) : RuleList(Definitions) +{ + public override String ToString() => base.ToString(); + public virtual Boolean Equals(NamedRuleList other) => Name.Equals(other.Name) && base.Equals(other); + public override Int32 GetHashCode() => base.GetHashCode() * -1521134295 + Name.GetHashCode(); + public override void AppendCommentParagraphsTo(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = builder.Comment.OpenParagraph() + .Append("Name: ") + .Comment.OpenCode() + .AppendDisplayString(Name, cancellationToken) + .CloseBlock() + .CloseBlock(); + + base.AppendCommentParagraphsTo(builder, cancellationToken); + } + public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) => + base.AppendDisplayStringTo(builder.AppendDisplayString(Name, cancellationToken).AppendLine(";"), cancellationToken); + protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) => + AppendCtorArg(AppendCtorArg(builder, nameof(Name), Name, cancellationToken).Append(", "), nameof(Definitions), Definitions, cancellationToken); + public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); +} diff --git a/DslGenerator/Grammar/Rule.Alternative.cs b/DslGenerator/Grammar/Rule.Alternative.cs new file mode 100644 index 0000000..6008463 --- /dev/null +++ b/DslGenerator/Grammar/Rule.Alternative.cs @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; + +using System.Diagnostics; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +abstract partial record Rule +{ + [DebuggerDisplay("{ToDisplayString()}")] + public sealed record Alternative(Rule Left, Rule Right) : Rule + { + public override String ToString() => base.ToString(); + public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = builder.AppendDisplayString(Left, cancellationToken).Append(" / ").AppendDisplayString(Right, cancellationToken); + } + + protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = AppendCtorArg( + AppendCtorArg(builder, nameof(Left), Left, cancellationToken) + .Append(", "), nameof(Right), Right, cancellationToken); + } + public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); + } +} diff --git a/DslGenerator/Grammar/Rule.Any.cs b/DslGenerator/Grammar/Rule.Any.cs new file mode 100644 index 0000000..b055c34 --- /dev/null +++ b/DslGenerator/Grammar/Rule.Any.cs @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; + +using System.Diagnostics; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +abstract partial record Rule +{ + [DebuggerDisplay("{ToDisplayString()}")] + public sealed partial record Any : Rule + { + public static Any Instance { get; } = new(); + public override String ToString() => base.ToString(); + public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = builder.Append('.'); + } + + protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) => + cancellationToken.ThrowIfCancellationRequested(); + public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); + } +} diff --git a/DslGenerator/Grammar/Rule.Concatenation.cs b/DslGenerator/Grammar/Rule.Concatenation.cs new file mode 100644 index 0000000..03859ef --- /dev/null +++ b/DslGenerator/Grammar/Rule.Concatenation.cs @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; + +using System.Diagnostics; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +abstract partial record Rule +{ + [DebuggerDisplay("{ToDisplayString()}")] + public sealed record Concatenation(Rule Left, Rule Right) : Rule + { + public override String ToString() => base.ToString(); + public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = builder.AppendDisplayString(Left, cancellationToken).Append(' ').AppendDisplayString(Right, cancellationToken); + } + + protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = AppendCtorArg(AppendCtorArg(builder, nameof(Left), Left, cancellationToken).Append(", "), nameof(Right), Right, cancellationToken); + } + public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); + } +} diff --git a/DslGenerator/Grammar/Rule.Grouping.cs b/DslGenerator/Grammar/Rule.Grouping.cs new file mode 100644 index 0000000..97fb6d4 --- /dev/null +++ b/DslGenerator/Grammar/Rule.Grouping.cs @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; + +using System.Diagnostics; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +abstract partial record Rule +{ + [DebuggerDisplay("{ToDisplayString()}")] + public sealed record Grouping(Rule Rule) : Rule + { + public override String ToString() => base.ToString(); + public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = builder.Append('(').AppendDisplayString(Rule, cancellationToken).Append(')'); + } + + protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = AppendCtorArg(builder, nameof(Rule), Rule, cancellationToken); + } + public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); + } +} diff --git a/DslGenerator/Grammar/Rule.OptionalGrouping.cs b/DslGenerator/Grammar/Rule.OptionalGrouping.cs new file mode 100644 index 0000000..5a34206 --- /dev/null +++ b/DslGenerator/Grammar/Rule.OptionalGrouping.cs @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; + +using System.Diagnostics; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +abstract partial record Rule +{ + [DebuggerDisplay("{ToDisplayString()}")] + public sealed record OptionalGrouping(Rule Rule) : Rule + { + public override String ToString() => base.ToString(); + public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = builder.Append('[').AppendDisplayString(Rule, cancellationToken).Append(']'); + } + protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = AppendCtorArg(builder, nameof(Rule), Rule, cancellationToken); + } + public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); + } +} diff --git a/DslGenerator/Grammar/Rule.Range.cs b/DslGenerator/Grammar/Rule.Range.cs new file mode 100644 index 0000000..193a03d --- /dev/null +++ b/DslGenerator/Grammar/Rule.Range.cs @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; + +using System.Diagnostics; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +abstract partial record Rule +{ + [DebuggerDisplay("{ToDisplayString()}")] + public sealed partial record Range(Terminal Start, Terminal End) : Rule + { + public override String ToString() => base.ToString(); + public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = builder.AppendDisplayString(Start, cancellationToken).Append('-').AppendDisplayString(End, cancellationToken); + } + protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = AppendCtorArg(AppendCtorArg(builder, nameof(Start), Start, cancellationToken).Append(", "), nameof(End), End, cancellationToken); + } + public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); + } +} diff --git a/DslGenerator/Grammar/Rule.Reference.cs b/DslGenerator/Grammar/Rule.Reference.cs new file mode 100644 index 0000000..be169b7 --- /dev/null +++ b/DslGenerator/Grammar/Rule.Reference.cs @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; + +using System.Diagnostics; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +abstract partial record Rule +{ + [DebuggerDisplay("{ToDisplayString()}")] + public sealed partial record Reference(Name Name) : Rule + { + public override String ToString() => base.ToString(); + public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = builder.AppendDisplayString(Name, cancellationToken); + } + + protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = AppendCtorArg(builder, nameof(Name), Name, cancellationToken); + } + public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); + } +} diff --git a/DslGenerator/Grammar/Rule.SpecificRepetition.cs b/DslGenerator/Grammar/Rule.SpecificRepetition.cs new file mode 100644 index 0000000..7d1673d --- /dev/null +++ b/DslGenerator/Grammar/Rule.SpecificRepetition.cs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; + +using System.Diagnostics; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +abstract partial record Rule +{ + [DebuggerDisplay("{ToDisplayString()}")] + public sealed record SpecificRepetition(Int32 Count, Rule Rule) : Rule + { + public override String ToString() => base.ToString(); + public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = builder.Append(Count.ToString()).AppendDisplayString(Rule, cancellationToken); + } + protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = AppendCtorArg( + AppendCtorArg(builder, nameof(Count), Count.ToString(), quoteValue: false, cancellationToken) + .Append(", "), nameof(Rule), Rule, cancellationToken); + } + public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); + } +} diff --git a/DslGenerator/Grammar/Rule.Terminal.cs b/DslGenerator/Grammar/Rule.Terminal.cs new file mode 100644 index 0000000..e2f9bb7 --- /dev/null +++ b/DslGenerator/Grammar/Rule.Terminal.cs @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; + +using RhoMicro.CodeAnalysis.DslGenerator.Lexing; + +using System.Diagnostics; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +abstract partial record Rule +{ + [DebuggerDisplay("{ToDisplayString()}")] + public sealed partial record Terminal(String Value) : Rule + { + public override String ToString() => base.ToString(); + public Terminal(Token token) + : this(token.Type == TokenType.Terminal ? + token.Lexeme.ToString() ?? String.Empty : + throw new ArgumentOutOfRangeException(nameof(token), token.Type, "Token must be of terminal type.")) + { } + public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = builder.Append('"').Append(Value).Append('"'); + } + + protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = AppendCtorArg(builder, nameof(Value), Value, quoteValue: true, cancellationToken); + } + public Boolean Equals(Terminal other) => other is not null && Value == other.Value; + public override Int32 GetHashCode() => Value.GetHashCode(); + public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); + } +} diff --git a/DslGenerator/Grammar/Rule.VariableRepetition.cs b/DslGenerator/Grammar/Rule.VariableRepetition.cs new file mode 100644 index 0000000..673c609 --- /dev/null +++ b/DslGenerator/Grammar/Rule.VariableRepetition.cs @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; + +using System.Diagnostics; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +abstract partial record Rule +{ + [DebuggerDisplay("{ToDisplayString()}")] + public sealed record VariableRepetition(Rule Rule) : Rule + { + public override String ToString() => base.ToString(); + public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = builder.Append('*').AppendDisplayString(Rule, cancellationToken); + } + protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = AppendCtorArg(builder, nameof(Rule), Rule, cancellationToken); + } + public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); + } +} diff --git a/DslGenerator/Grammar/Rule.cs b/DslGenerator/Grammar/Rule.cs new file mode 100644 index 0000000..26f260e --- /dev/null +++ b/DslGenerator/Grammar/Rule.cs @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; + +using System.Diagnostics; + +#if DSL_GENERATOR +[IncludeFile] +#endif +[DebuggerDisplay("{ToDisplayString()}")] +internal abstract partial record Rule : SyntaxNode +{ + public override String ToString() => base.ToString(); +} diff --git a/DslGenerator/Grammar/RuleBuilder.cs b/DslGenerator/Grammar/RuleBuilder.cs new file mode 100644 index 0000000..431022f --- /dev/null +++ b/DslGenerator/Grammar/RuleBuilder.cs @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; + +using System; +using System.Collections.Generic; +using System.Threading; + +using static Rule; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +readonly struct RuleBuilder +{ + public RuleBuilder() + : this(isAlternative: false) + { } + private RuleBuilder(Boolean isAlternative) => _isAlternative = isAlternative; + private readonly List _rules = []; + private readonly Boolean _isAlternative; + + public override String ToString() => _rules.Count == 0 ? GetType().ToString() : Build().ToString(); + public RuleBuilder Reference(String value) + { + _rules.Add(new Reference(new(value))); + return this; + } + public RuleBuilder Terminal(String value) + { + _rules.Add(new Terminal(value)); + return this; + } + public RuleBuilder Any() + { + _rules.Add(Rule.Any.Instance); + return this; + } + public RuleBuilder Range(Char start, Char end) + { + _rules.Add(new Rule.Range(new(start.ToString()), new(end.ToString()))); + return this; + } + public RuleBuilder Concatenation(params String[] referenceNames) => + Concatenation(b => _ = referenceNames.Aggregate(b, (b, n) => b.Reference(n))); + public RuleBuilder Concatenation(Action buildRule) => AppendBinary(buildAlternative: false, buildRule); + public RuleBuilder Alternative(params String[] referenceNames) => + Alternative(b => _ = referenceNames.Aggregate(b, (b, n) => b.Reference(n))); + public RuleBuilder Alternative(Action buildRule) => AppendBinary(buildAlternative: true, buildRule); + private RuleBuilder AppendBinary(Boolean buildAlternative, Action buildRule) + { + if(buildAlternative == _isAlternative) + { + buildRule.Invoke(this); + return this; + } + + var builder = new RuleBuilder(buildAlternative); + buildRule.Invoke(builder); + if(builder._rules.Count == 0) + { + return this; + } + + var rule = + builder._rules.Count > 1 ? + builder._rules.Aggregate((left, right) => new Alternative(left, right)) : + builder.Build(); + _rules.Add(rule); + + return this; + } + public RuleBuilder VariableRepetition(String referenceName) + { + var rule = new Reference(new(referenceName)); + var repeatedRule = new VariableRepetition(rule); + _rules.Add(repeatedRule); + + return this; + } + public RuleBuilder VariableRepetition(Action buildRule) + { + var rule = GetRule(buildRule); + var repeatedRule = new VariableRepetition(rule); + _rules.Add(repeatedRule); + + return this; + } + public RuleBuilder SpecificRepetition(Int32 count, String referenceName) + { + var rule = new Reference(new(referenceName)); + var repeatedRule = new SpecificRepetition(count, rule); + _rules.Add(repeatedRule); + + return this; + } + public RuleBuilder SpecificRepetition(Int32 count, Action buildRule) + { + var rule = GetRule(buildRule); + var repetition = new SpecificRepetition(count, rule); + _rules.Add(repetition); + + return this; + } + public RuleBuilder Grouping(Action buildRule) + { + var rule = GetRule(buildRule); + var grouping = new Grouping(rule); + _rules.Add(grouping); + + return this; + } + public RuleBuilder OptionalGrouping(String referenceName) + { + var rule = new Reference(new(referenceName)); + var grouping = new OptionalGrouping(rule); + _rules.Add(grouping); + + return this; + } + public RuleBuilder OptionalGrouping(Action buildRule) + { + var rule = GetRule(buildRule); + var grouping = new OptionalGrouping(rule); + _rules.Add(grouping); + + return this; + } + + private static Rule GetRule(Action buildRule) + { + var builder = new RuleBuilder(); + buildRule.Invoke(builder); + var rule = builder.Build(); + return rule; + } + + public Rule Build() => _rules.Count == 1 ? + _rules[0] : + _rules.Count > 1 ? + _rules.Aggregate((left, right) => new Concatenation(left, right)) : + throw new InvalidOperationException("Unable to build rule as no builder instruction has been called."); +} diff --git a/DslGenerator/Grammar/RuleDefinition.Incremental.cs b/DslGenerator/Grammar/RuleDefinition.Incremental.cs new file mode 100644 index 0000000..8c014fb --- /dev/null +++ b/DslGenerator/Grammar/RuleDefinition.Incremental.cs @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; + +using System.Diagnostics; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +abstract partial record RuleDefinition +{ + [DebuggerDisplay("{ToDisplayString()}")] + public sealed record Incremental : RuleDefinition + { +#pragma warning disable IDE1006 // Naming Styles + public Incremental(Name Name, Rule Rule) : base(Name, Rule) { } +#pragma warning restore IDE1006 // Naming Styles + public override String ToString() => base.ToString(); + public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = builder.AppendDisplayString(Name, cancellationToken).Append(" /= ").AppendDisplayString(Rule, cancellationToken).Append(';'); + } + public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); + } +} diff --git a/DslGenerator/Grammar/RuleDefinition.New.cs b/DslGenerator/Grammar/RuleDefinition.New.cs new file mode 100644 index 0000000..8e7d863 --- /dev/null +++ b/DslGenerator/Grammar/RuleDefinition.New.cs @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; + +using System.Diagnostics; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +abstract partial record RuleDefinition +{ + [DebuggerDisplay("{ToDisplayString()}")] + public sealed record New : RuleDefinition + { +#pragma warning disable IDE1006 // Naming Styles + public New(Name Name, Rule Rule) : base(Name, Rule) { } +#pragma warning restore IDE1006 // Naming Styles + public override String ToString() => base.ToString(); + public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = builder.AppendDisplayString(Name, cancellationToken).Append(" = ") + .AppendDisplayString(Rule, cancellationToken).Append(';'); + } + public override void Receive(SyntaxNodeVisitor visitor) => visitor.Visit(this); + } +} diff --git a/DslGenerator/Grammar/RuleDefinition.cs b/DslGenerator/Grammar/RuleDefinition.cs new file mode 100644 index 0000000..d9e6843 --- /dev/null +++ b/DslGenerator/Grammar/RuleDefinition.cs @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; + +using System.Diagnostics; + +#if DSL_GENERATOR +[IncludeFile] +#endif +[DebuggerDisplay("{ToDisplayString()}")] +internal abstract partial record RuleDefinition(Name Name, Rule Rule) : SyntaxNode +{ + public override String ToString() => base.ToString(); + protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = AppendCtorArg(AppendCtorArg(builder, nameof(Name), Name, cancellationToken).Append(", "), nameof(Rule), Rule, cancellationToken); + } +} diff --git a/DslGenerator/Grammar/RuleList.cs b/DslGenerator/Grammar/RuleList.cs new file mode 100644 index 0000000..e7af8e1 --- /dev/null +++ b/DslGenerator/Grammar/RuleList.cs @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; + +using System.Diagnostics; +using System.Linq; + +#if DSL_GENERATOR +[IncludeFile] +#endif +[DebuggerDisplay("{ToDisplayString()}")] +internal record RuleList(IReadOnlyList Definitions) : SyntaxNode +{ + public override String ToString() => base.ToString(); + public virtual Boolean Equals(RuleList other) => + other.Definitions.SequenceEqual(Definitions); + public override Int32 GetHashCode() => + Definitions.Aggregate(997021164, (hc, def) => hc * -1521134295 + def.GetHashCode()); + public virtual void AppendCommentParagraphsTo(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = Definitions.Aggregate(builder, (b, d) => + b.Comment.OpenParagraph() + .Comment.OpenCode() + .AppendDisplayString(d, cancellationToken) + .CloseBlock() + .CloseBlock()); + } + public override void Receive(SyntaxNodeVisitor visitor) => + visitor.Visit(this); + public override void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) => + _ = Definitions.Aggregate(builder, (sb, r) => sb.AppendDisplayString(r, cancellationToken).Append('\n')); + protected override void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken) => + AppendCtorArg(builder, nameof(Definitions), Definitions, cancellationToken); +} diff --git a/DslGenerator/Grammar/RuleListBuilder.cs b/DslGenerator/Grammar/RuleListBuilder.cs new file mode 100644 index 0000000..c4c932b --- /dev/null +++ b/DslGenerator/Grammar/RuleListBuilder.cs @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; + +using System; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +readonly struct RuleListBuilder +{ + public RuleListBuilder(String nameValue) => + _name = new(nameValue); + public RuleListBuilder() => + _name = null; + + private readonly List _definitions = []; + private readonly Name? _name; + + public override String ToString() => Build().ToString(); + public RuleListBuilder New(String nameValue, Action buildRule) + { + var rule = GetRule(buildRule); + var ruleDefinition = new RuleDefinition.New(new(nameValue), rule); + _definitions.Add(ruleDefinition); + + return this; + } + public RuleListBuilder Incremental(String nameValue, Action buildRule) + { + var rule = GetRule(buildRule); + var ruleDefinition = new RuleDefinition.Incremental(new(nameValue), rule); + _definitions.Add(ruleDefinition); + + return this; + } + private static Rule GetRule(Action buildRule) + { + var ruleBuilder = new RuleBuilder(); + buildRule.Invoke(ruleBuilder); + var rule = ruleBuilder.Build(); + + return rule; + } + public RuleList Build() => + _name != null ? + new NamedRuleList(_name, _definitions) : + new RuleList(_definitions); +} diff --git a/DslGenerator/Grammar/SyntaxNode.cs b/DslGenerator/Grammar/SyntaxNode.cs new file mode 100644 index 0000000..a65735a --- /dev/null +++ b/DslGenerator/Grammar/SyntaxNode.cs @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; + +using RhoMicro.CodeAnalysis.Library.Text; + +using System.Text.RegularExpressions; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +abstract record SyntaxNode +{ + private static readonly Regex _quotePattern = new("(\"*)", RegexOptions.Compiled); + protected static IndentedStringBuilder AppendCtorArg( + IndentedStringBuilder builder, + String argName, + String argValue, + Boolean quoteValue, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = builder.Append(argName).Append(": "); + + var rawQuotes = quoteValue ? + String.Concat(Enumerable.Repeat('"', _quotePattern.Matches(argValue) + .Cast() + .Select(m => m.Length) + .Append(2) + .Max() + 1)) : + null; + + if(quoteValue) + { + _ = builder.Append('\n').Append(rawQuotes).Append('\n'); + } + + _ = builder.Append(argValue); + + if(quoteValue) + { + _ = builder.Append('\n').Append(rawQuotes).Append('\n'); + } + + return builder; + } + protected static IndentedStringBuilder AppendCtorArg( + IndentedStringBuilder builder, + String argName, + SyntaxNode argValue, + CancellationToken cancellationToken) => + builder.Append(argName).Append(": ").AppendMetaString(argValue, cancellationToken); + protected static IndentedStringBuilder AppendCtorArg( + IndentedStringBuilder builder, + String argName, + IEnumerable argValue, + CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + _ = builder.Append(argName).Append(": ["); + var enumerator = argValue.GetEnumerator(); + if(enumerator.MoveNext()) + { + var first = enumerator.Current; + _ = builder.AppendMetaString(first, cancellationToken); + while(enumerator.MoveNext()) + { + cancellationToken.ThrowIfCancellationRequested(); + + var next = enumerator.Current; + _ = builder.Append(", ").AppendMetaString(next, cancellationToken); + } + } else + { + _ = builder.Append(' '); + } + + _ = builder.Append(']'); + + return builder; + } + + public String ToDisplayString(CancellationToken cancellationToken) => + new IndentedStringBuilder().AppendDisplayString(this, cancellationToken).ToString(); + public String ToMetaString(CancellationToken cancellationToken) => + new IndentedStringBuilder().AppendMetaString(this, cancellationToken).ToString(); + public void AppendMetaStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var type = GetType(); + _ = builder.Append("new ").Append(type.Namespace); + append(type); + _ = builder.Append('('); + AppendCtorArgs(builder, cancellationToken); + _ = builder.Append(')'); + + void append(Type type) + { + cancellationToken.ThrowIfCancellationRequested(); + + if(type.DeclaringType != null) + { + append(type.DeclaringType); + } + + _ = builder.Append('.').Append(type.Name); + } + } + protected abstract void AppendCtorArgs(IndentedStringBuilder builder, CancellationToken cancellationToken); + public abstract void AppendDisplayStringTo(IndentedStringBuilder builder, CancellationToken cancellationToken); + public override String ToString() => ToDisplayString(default); + public abstract void Receive(SyntaxNodeVisitor visitor); +} diff --git a/DslGenerator/Grammar/SyntaxNodeVisitor.cs b/DslGenerator/Grammar/SyntaxNodeVisitor.cs new file mode 100644 index 0000000..706b99c --- /dev/null +++ b/DslGenerator/Grammar/SyntaxNodeVisitor.cs @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; + +internal abstract class SyntaxNodeVisitor +{ + public abstract void Visit(Name name); + public abstract void Visit(NamedRuleList namedRuleList); + public abstract void Visit(Rule.Alternative alternative); + public abstract void Visit(Rule.Any any); + public abstract void Visit(Rule.Concatenation concatenation); + public abstract void Visit(Rule.Grouping grouping); + public abstract void Visit(Rule.OptionalGrouping optionalGrouping); + public abstract void Visit(Rule.Range range); + public abstract void Visit(Rule.Reference reference); + public abstract void Visit(Rule.SpecificRepetition specificRepetition); + public abstract void Visit(Rule.Terminal terminal); + public abstract void Visit(Rule.VariableRepetition variableRepetition); + public abstract void Visit(RuleDefinition.New @new); + public abstract void Visit(RuleDefinition.Incremental incremental); + public abstract void Visit(RuleList ruleList); +} diff --git a/DslGenerator/Grammar/Utils.cs b/DslGenerator/Grammar/Utils.cs new file mode 100644 index 0000000..7bfde28 --- /dev/null +++ b/DslGenerator/Grammar/Utils.cs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Grammar; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +static class Utils +{ + [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] + public static Boolean IsValidNameChar(Char c) => + c is >= 'a' and <= 'z' or + >= 'A' and <= 'Z' or + '_'; +} diff --git a/DslGenerator/Lexing/Lexeme.cs b/DslGenerator/Lexing/Lexeme.cs new file mode 100644 index 0000000..dede6dc --- /dev/null +++ b/DslGenerator/Lexing/Lexeme.cs @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Lexing; + +using System.Text.RegularExpressions; + +#if DSL_GENERATOR +[IncludeFile] +#endif +[UnionType] +[UnionTypeSettings( + ToStringSetting = ToStringSetting.Simple, + Miscellaneous = MiscellaneousSettings.Default | MiscellaneousSettings.EmitGeneratedSourceCode)] +internal readonly partial struct Lexeme : IEquatable, IEquatable, IEquatable +{ + public Int32 Length => Match( + s => s.Length, + s => 1, + s => s.Length); + public static Lexeme Empty { get; } = String.Empty; + public Boolean Equals(Lexeme other) => + Match(other.Equals, other.Equals, other.Equals); + public override Int32 GetHashCode() => + Match(v => v.GetHashCode(), v => v.GetHashCode(), v => v.GetHashCode()); + public Boolean Equals(Char c) => + Match( + s => s.Length == 1 && s[0] == c, + thisChar => thisChar == c, + s => s.Equals(c)); + public Boolean Equals(String s) => + Match( + thisString => thisString == s, + c => s.Length == 1 && s[0] == c, + slice => slice.Equals(s)); + public Boolean Equals(StringSlice s) => + Match(s.Equals, s.Equals, s.Equals); + public String ToEscapedString() => + ToString()? + .Replace("\n", "\\n") + .Replace("\r", "\\r") + .Replace("\t", "\\t") + ?? String.Empty; +} diff --git a/DslGenerator/Lexing/Lexemes.cs b/DslGenerator/Lexing/Lexemes.cs new file mode 100644 index 0000000..7da2e26 --- /dev/null +++ b/DslGenerator/Lexing/Lexemes.cs @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Lexing; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +static class Lexemes +{ + public const Char Equal = '='; + public const Char Dash = '-'; + public const Char Period = '.'; + public const Char Alternative = '/'; + public const String IncrementalAlternative = "=/"; + public const Char GroupOpen = '('; + public const Char GroupClose = ')'; + public const Char VariableRepetition = '*'; + public const Char OptionalSequenceOpen = '['; + public const Char OptionalSequenceClose = ']'; + public const Char Semicolon = ';'; + public const Char Quote = '"'; + public const Char NewLine = '\n'; + public const Char Space = ' '; + public const Char Tab = '\t'; + public const Char CarriageReturn = '\r'; + public const Char Escape = '\\'; + public const Char Hash = '#'; + public const String Eof = ""; +} diff --git a/DslGenerator/Lexing/Location.cs b/DslGenerator/Lexing/Location.cs new file mode 100644 index 0000000..cfcfd86 --- /dev/null +++ b/DslGenerator/Lexing/Location.cs @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Lexing; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +sealed record Location( + TextSpan TextSpan, + LinePositionSpan LinePositionSpan +#if DSL_GENERATOR + , String FilePath +#endif + ) +{ + public static Location None { get; } = new(default, default +#if DSL_GENERATOR + , String.Empty +#endif + ); + public static Location Create(Int32 line, Int32 character, Int32 position +#if DSL_GENERATOR + , String filePath +#endif + ) => + new(new(position, 0), new(new(line, character), new(line, character)) //TODO: implement location spans +#if DSL_GENERATOR + , filePath +#endif + ); +#if DSL_GENERATOR + public Microsoft.CodeAnalysis.Location ToMsLocation() => + Equals(None) ? + Microsoft.CodeAnalysis.Location.None : + Microsoft.CodeAnalysis.Location.Create( + FilePath, + TextSpan.ToMsTextSpan(), + LinePositionSpan.ToMsLinePositionSpan()); +#endif +} + +internal readonly record struct LinePosition(Int32 Line, Int32 Character) +{ +#if DSL_GENERATOR + public Microsoft.CodeAnalysis.Text.LinePosition ToMsLinePosition() => + new(Line, Character); +#endif +} + +internal readonly record struct LinePositionSpan(LinePosition Start, LinePosition End) +{ +#if DSL_GENERATOR + public Microsoft.CodeAnalysis.Text.LinePositionSpan ToMsLinePositionSpan() => + new(Start.ToMsLinePosition(), End.ToMsLinePosition()); +#endif +} + +internal readonly record struct TextSpan(Int32 Position, Int32 Length) +{ +#if DSL_GENERATOR + public Microsoft.CodeAnalysis.Text.TextSpan ToMsTextSpan() => + new(Position, Length); +#endif +} diff --git a/DslGenerator/Lexing/SourceText.cs b/DslGenerator/Lexing/SourceText.cs new file mode 100644 index 0000000..66090bc --- /dev/null +++ b/DslGenerator/Lexing/SourceText.cs @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Lexing; + +using System; +using System.Text; + +#if DSL_GENERATOR +[IncludeFile] +#endif +[UnionType] +internal readonly partial struct SourceText : IDisposable +{ + public String ToString(CancellationToken cancellationToken) => + Match( + s => s, + s => + { + cancellationToken.ThrowIfCancellationRequested(); + var reader = new StreamReader(s); + var resultBuilder = new StringBuilder(); + var line = reader.ReadLine(); + while(line != null) + { + cancellationToken.ThrowIfCancellationRequested(); + _ = resultBuilder.AppendLine(line); + line = reader.ReadLine(); + } + + var result = resultBuilder.ToString(); + + return result; + }); + public static SourceText Empty { get; } = String.Empty; + public void Dispose() + { + if(TryAsStream(out var s)) + s.Dispose(); + } +} diff --git a/DslGenerator/Lexing/StringSlice.cs b/DslGenerator/Lexing/StringSlice.cs new file mode 100644 index 0000000..308dd55 --- /dev/null +++ b/DslGenerator/Lexing/StringSlice.cs @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Lexing; + +using System.Diagnostics; + +#if DSL_GENERATOR +[IncludeFile] +#endif +[DebuggerDisplay("{DebuggerDisplayString()}")] +internal readonly record struct StringSlice(String Source, Int32 Start, Int32 Length) : IEquatable, IEquatable +{ + public override String ToString() => Source.Substring(Start, Length); + private String DebuggerDisplayString() => ToString().Replace("\n", "\\n").Replace("\r", "\\r"); + public Boolean Equals(String other) + { + if(Length != other.Length) + return false; + for(var i = 0; i < Length; i++) + { + if(Source[i + Start] != other[i]) + return false; + } + + return true; + } + public Boolean Equals(Char other) => Length == 1 && Source[Start] == other; + public Boolean Equals(StringSlice other) + { + if(Length != other.Length) + return false; + for(var i = 0; i < Length; i++) + { + if(Source[i + Start] != other.Source[i + other.Start]) + return false; + } + + return true; + } + public override Int32 GetHashCode() + { + var result = EqualityComparer.Default.GetHashCode(Length); + for(var i = Start; i < Start + Length; i++) + { + result = result * -1521134295 + EqualityComparer.Default.GetHashCode(Source[i]); + } + + return result; + } +} diff --git a/DslGenerator/Lexing/Token.cs b/DslGenerator/Lexing/Token.cs new file mode 100644 index 0000000..e9d532a --- /dev/null +++ b/DslGenerator/Lexing/Token.cs @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Lexing; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +readonly record struct Token(TokenType Type, Lexeme Lexeme, Location Location) +{ + public Token(TokenType type, Lexeme lexeme) : this(type, lexeme, Location.None) { } + public override String ToString() => $"[{Type}:{Lexeme.ToEscapedString()}]"; + public static Token CreateUnknown(Lexeme lexeme) => new(TokenType.Unknown, lexeme); + public static Token CreateName(Lexeme lexeme) => new(TokenType.Name, lexeme); + public static Token CreateWhitespace(Lexeme lexeme) => new(TokenType.Whitespace, lexeme); + public static Token CreateSpecificRepetition(Lexeme lexeme) => new(TokenType.Number, lexeme); + public static Token CreateComment(Lexeme lexeme) => new(TokenType.Comment, lexeme); + public static Token CreateTerminal(Lexeme lexeme) => new(TokenType.Terminal, lexeme); + public Boolean Equals(Token other) => Type == other.Type && Lexeme == other.Lexeme; + public override Int32 GetHashCode() => (Type, Lexeme).GetHashCode(); +} + +internal readonly record struct Token(T Type, Lexeme Lexeme, Location Location) +{ + public Token(T type, Lexeme lexeme) : this(type, lexeme, Location.None) { } + public override String ToString() => $"[{Type}:{Lexeme.ToEscapedString()}]"; + public Boolean Equals(Token other) => (Type, Lexeme).Equals((other.Type, other.Lexeme)); + public override Int32 GetHashCode() => (Type, Lexeme).GetHashCode(); +} diff --git a/DslGenerator/Lexing/TokenType.cs b/DslGenerator/Lexing/TokenType.cs new file mode 100644 index 0000000..a1a0c52 --- /dev/null +++ b/DslGenerator/Lexing/TokenType.cs @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Lexing; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +enum TokenType +{ + Unknown, + Name, + Equal, + Whitespace, + Slash, + SlashEqual, + ParenLeft, + ParenRight, + Star, + Number, + BracketLeft, + BracketRight, + Semicolon, + Comment, + NewLine, + Terminal, + Dash, + Period, + Eof +} diff --git a/DslGenerator/Lexing/TokenizeResult.cs b/DslGenerator/Lexing/TokenizeResult.cs new file mode 100644 index 0000000..0d97200 --- /dev/null +++ b/DslGenerator/Lexing/TokenizeResult.cs @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Lexing; + +using RhoMicro.CodeAnalysis.DslGenerator.Analysis; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +readonly record struct TokenizeResult(IReadOnlyList Tokens, DiagnosticsCollection Diagnostics) +{ + public Boolean Equals(TokenizeResult other) => Tokens.SequenceEqual(other.Tokens); + public override Int32 GetHashCode() => Tokens.Aggregate(997021164, (hc, t) => hc * -1521134295 + t.GetHashCode()); +} diff --git a/DslGenerator/Lexing/Tokenizer.cs b/DslGenerator/Lexing/Tokenizer.cs new file mode 100644 index 0000000..f319888 --- /dev/null +++ b/DslGenerator/Lexing/Tokenizer.cs @@ -0,0 +1,302 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Lexing; + +using static RhoMicro.CodeAnalysis.DslGenerator.Analysis.DiagnosticDescriptors; +using RhoMicro.CodeAnalysis.DslGenerator.Grammar; +using RhoMicro.CodeAnalysis.DslGenerator.Analysis; + +using static Lexemes; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +partial class Tokenizer +{ + [UnionType] + private readonly partial struct TokenOrType; + public static Tokenizer Instance { get; } = new(); + + public TokenizeResult Tokenize(SourceText sourceText, CancellationToken cancellationToken +#if DSL_GENERATOR + , String filePath = "" +#endif + ) + { + cancellationToken.ThrowIfCancellationRequested(); + + var tokens = new List(); + var diagnostics = new DiagnosticsCollection(); + var source = sourceText.ToString(cancellationToken); + var isUnknown = false; + var (start, current, line, character) = (0, 0, 0, 0); + + while(!isAtEnd()) + { + cancellationToken.ThrowIfCancellationRequested(); + scanToken(); + } + + addToken(Tokens.Eof); + + return new(tokens, diagnostics); + + void scanToken() + { + var c = advance(); + switch(c) + { + case Equal: + addToken(TokenType.Equal); + break; + case Dash: + addToken(TokenType.Dash); + break; + case Period: + addToken(TokenType.Period); + break; + case Alternative: + //check for incremental alternative "/=" + var type = match(Equal) ? + TokenType.SlashEqual : + TokenType.Slash; + addToken(type); + break; + case GroupOpen: + addToken(TokenType.ParenLeft); + break; + case GroupClose: + addToken(TokenType.ParenRight); + break; + case VariableRepetition: + addToken(TokenType.Star); + break; + case OptionalSequenceOpen: + addToken(TokenType.BracketLeft); + break; + case OptionalSequenceClose: + addToken(TokenType.BracketRight); + break; + case Semicolon: + addToken(TokenType.Semicolon); + break; + case Hash: + comment(); + break; + case Quote: + terminal(); + break; + case NewLine: + addNewLine(); + break; + case CarriageReturn: + closeUnknown(); + if(lookAhead() == NewLine) + advancePure(); + addNewLine(); + break; + case Space: + consumeWhitespace(Space); + break; + case Tab: + consumeWhitespace(Tab); + break; + default: + if(isAlpha(c)) + { + name(); + } else if(isDigit(c)) + { + specificRepetition(); + } else + { + openUnknown(); + } + + break; + } + + void addNewLine() + { + closeUnknown(); + line++; + character = 0; + addToken(TokenType.NewLine); + } + } + + Boolean isAtEnd(Int32 lookaheadOffset = 0) => current + lookaheadOffset >= source!.Length; + + Char advance() + { + advancePure(); + return source![current - 1]; + } + + void specificRepetition() + { + closeUnknown(); + + while(isDigit(lookAhead())) + advancePure(); + addToken(TokenType.Number); + } + + void advancePure() + { + character++; + current++; + } + + void regressPure() + { + character--; + current--; + } + + void openUnknown() => isUnknown = true; + + void closeUnknown() + { + if(isUnknown) + { + if(!isAtEnd()) + regressPure(); + isUnknown = false; + addToken(TokenType.Unknown); + diagnostics!.Add(UnexpectedCharacter, getLocation()); + if(!isAtEnd()) + advancePure(); + } + } + + void resetLexemeStart() => start = current; + + void addToken(TokenOrType tokenOrType) + { + closeUnknown(); + + var token = tokenOrType.Match( + token => token, + type => new Token(type, getLexeme(), getLocation())); + tokens!.Add(token); + resetLexemeStart(); + } + + void discardToken() + { + closeUnknown(); + resetLexemeStart(); + } + + Boolean isDigit(Char? c) => c is not null and >= '0' and <= '9'; + + Boolean isAlpha(Char? c) => c is not null && Utils.IsValidNameChar(c.Value); + + Lexeme getLexeme() => new StringSlice(source!, start, current - start); + + Char? lookAhead(Int32 lookAheadOffset = 0) => current + lookAheadOffset >= source!.Length ? null : source![current + lookAheadOffset]; + + Char? lookBehind(Int32 lookBehindOffset = 0) => current - lookBehindOffset < 1 ? null : source![current - 1 - lookBehindOffset]; + + Location getLocation() => Location.Create( + line, + character, + current +#if DSL_GENERATOR + , filePath +#endif + ); + + Boolean match(Char expected) + { + if(isAtEnd() || source![current] != expected) + return false; + current++; + return true; + } + + Boolean isAtNewLine() => + lookAhead() switch + { + NewLine => true, + CarriageReturn => true, + _ => false + }; + + void comment() + { + discardToken(); //discard hash token + advancePure(); //consume hash + + if(isAtNewLine() || isAtEnd()) + return; + + while(!isAtNewLine() && !isAtEnd(lookaheadOffset: 1)) + advancePure(); + + if(!isAtNewLine()) + advancePure(); //consume last comment char + + addToken(TokenType.Comment); + } + + void consumeWhitespace(Char expected) + { + closeUnknown(); + + while(lookAhead() == expected) + advancePure(); + + addToken(TokenType.Whitespace); + } + + void terminal() + { + discardToken(); //discard quote token + var containsCharacters = false; + while(( lookAhead() != Quote || lookBehind() == Escape ) && !isAtEnd()) + { + if(lookAhead() == NewLine) + { + line++; + } + + advancePure(); + containsCharacters = true; + } + + if(containsCharacters) + { + addToken(TokenType.Terminal); + } + + if(isAtEnd()) + { + diagnostics.Add(UnterminatedTerminal, getLocation(), getLexeme()); + return; + } + + //add empty token + if(!containsCharacters) + { + addToken(TokenType.Terminal); + } + + //consume terminating quote + advancePure(); + discardToken(); //discard quote token + } + + void name() + { + closeUnknown(); + + while(isAlpha(lookAhead())) + advancePure(); + + addToken(TokenType.Name); + } + } +} diff --git a/DslGenerator/Lexing/Tokens.cs b/DslGenerator/Lexing/Tokens.cs new file mode 100644 index 0000000..c00e190 --- /dev/null +++ b/DslGenerator/Lexing/Tokens.cs @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Lexing; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +static class Tokens +{ + public static Token Equal { get; } = new(TokenType.Equal, Lexemes.Equal); + public static Token Period { get; } = new(TokenType.Period, Lexemes.Period); + public static Token Dash { get; } = new(TokenType.Dash, Lexemes.Dash); + public static Token Alternative { get; } = new(TokenType.Slash, Lexemes.Alternative); + public static Token IncrementalAlternative { get; } = new(TokenType.SlashEqual, Lexemes.IncrementalAlternative); + public static Token GroupOpen { get; } = new(TokenType.ParenLeft, Lexemes.GroupOpen); + public static Token GroupClose { get; } = new(TokenType.ParenRight, Lexemes.GroupClose); + public static Token VariableRepetition { get; } = new(TokenType.Star, Lexemes.VariableRepetition); + public static Token OptionalSequenceOpen { get; } = new(TokenType.BracketLeft, Lexemes.OptionalSequenceOpen); + public static Token OptionalSequenceClose { get; } = new(TokenType.BracketRight, Lexemes.OptionalSequenceClose); + public static Token Semicolon { get; } = new(TokenType.Semicolon, Lexemes.Semicolon); + public static Token NewLine { get; } = new(TokenType.NewLine, Lexemes.NewLine); + public static Token Eof { get; } = new(TokenType.Eof, Lexemes.Eof); +} diff --git a/DslGenerator/Parsing/ParseResult.cs b/DslGenerator/Parsing/ParseResult.cs new file mode 100644 index 0000000..88cb8bf --- /dev/null +++ b/DslGenerator/Parsing/ParseResult.cs @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Parsing; + +using RhoMicro.CodeAnalysis.DslGenerator.Analysis; +using RhoMicro.CodeAnalysis.DslGenerator.Grammar; + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +readonly record struct ParseResult(RuleList RuleList, DiagnosticsCollection Diagnostics) +{ + public Boolean Equals(ParseResult other) => RuleList.Equals(other.RuleList); + public override Int32 GetHashCode() => RuleList.GetHashCode(); +} diff --git a/DslGenerator/Parsing/Parser.cs b/DslGenerator/Parsing/Parser.cs new file mode 100644 index 0000000..292a4a9 --- /dev/null +++ b/DslGenerator/Parsing/Parser.cs @@ -0,0 +1,373 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace RhoMicro.CodeAnalysis.DslGenerator.Parsing; + +using RhoMicro.CodeAnalysis.DslGenerator.Analysis; +using RhoMicro.CodeAnalysis.DslGenerator.Grammar; +using RhoMicro.CodeAnalysis.DslGenerator.Lexing; +using static RhoMicro.CodeAnalysis.DslGenerator.Analysis.DiagnosticDescriptors; + +using System; +using static RhoMicro.CodeAnalysis.DslGenerator.Grammar.Rule; +using System.Text; + +/* +RhoMicroBackusNaurForm; + +RuleList = [Name ";"] *RuleDefinition; +RuleDefinition = Name "=" Rule ";"; + +Rule = Binary / Unary / Primary; + +Binary = Range / Concatenation / Alternative; +Concatenation = Unary / (Rule Whitespace Rule); +Alternative = Unary / (Rule "/" Rule); +Range = Unary / (SingleAlpha "-" SingleAlpha); + +Unary = VariableRepetition / SpecificRepetition; +VariableRepetition = Primary / ("*" Rule); +SpecificRepetition = Primary / (Digit Rule); + +Primary = Grouping / OptionalGrouping / TerminalOrName; +Grouping = TerminalOrName / ("(" Rule ")"); +OptionalGrouping = TerminalOrName / ("[" Rule "]"); +TerminalOrName = Terminal / Any / Name; +Name = Alpha; +Terminal = "\"" . "\""; +Any = "."; + +Trivia = Whitespace / NewLine; +Whitespace = Space / Tab *Whitespace; +Space = " "; +Tab = " "; +NewLine = "\n" / "\r\n" / "\r"; +SingleAlpha = "a"-"z" / "A"-"Z" / "_"; +Alpha = SingleAlpha *SingleAlpha; +Digit = "0"-"9" *Digit; + +*/ + +#if DSL_GENERATOR +[IncludeFile] +internal +#endif +sealed class Parser +{ + private sealed class ParseException : Exception { } + public static Parser Instance { get; } = new(); + public ParseResult Parse(SourceText sourceText, CancellationToken cancellationToken +#if DSL_GENERATOR + , String filePath = "" +#endif + ) => + Parse(Tokenizer.Instance.Tokenize(sourceText, cancellationToken +#if DSL_GENERATOR + , filePath +#endif + ), cancellationToken); + public ParseResult Parse(TokenizeResult tokenizeResult, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var current = 0; + var (tokens, tokenizeDiagnostics) = tokenizeResult; + var diagnostics = new DiagnosticsCollection + { + tokenizeDiagnostics + }; + + var ruleList = parseRuleList(); + + return new(ruleList, diagnostics); + + RuleList parseRuleList() + { + cancellationToken.ThrowIfCancellationRequested(); + + //RuleList = Name ";" *RuleDefinition; + Name? name = null; + try + { + discardTrivia(); + if(match(TokenType.Name)) + { + var nameToken = previous(); + discardTrivia(); + if(match(TokenType.Semicolon)) + { + name = new(nameToken); + } else + { + current = 0; + } + } + } catch(ParseException) + { + synchronize(); + } + + var definitions = new List(); + while(!isAtEnd()) + { + cancellationToken.ThrowIfCancellationRequested(); + + if(tryParseDefinition(out var def)) + { + definitions.Add(def!); + } + } + + var result = name != null ? + new NamedRuleList(name, definitions) : + new RuleList(definitions); + + return result; + } + + Boolean tryParseDefinition(out RuleDefinition? definition) + { + cancellationToken.ThrowIfCancellationRequested(); + + try + { + discardTrivia(); + definition = parseDefinition(); + discardTrivia(); + return true; + } catch(ParseException) + { + synchronize(); + definition = default; + return false; + } + } + + RuleDefinition parseDefinition() + { + cancellationToken.ThrowIfCancellationRequested(); + + //RueDefinition = Name "=" Rule; + var name = consume(TokenType.Name, "Expected rule name.").Lexeme.ToString() ?? String.Empty; + discardTrivia(); + var isInremental = match(TokenType.SlashEqual); + if(!isInremental) + { + _ = consume(TokenType.Equal, "Expected '=' or '/=' after rule name."); + } + + discardTrivia(); + var rule = parseRule(); + discardTrivia(); + _ = consume(TokenType.Semicolon, "Expected ';' after rule definition."); + RuleDefinition result = isInremental ? + new RuleDefinition.Incremental(new(name), rule) : + new RuleDefinition.New(new(name), rule); + + return result; + } + + Rule parseRule() + { + cancellationToken.ThrowIfCancellationRequested(); + + //Rule = Binary / Unary / Primary; + return parseBinary(); + } + + Rule parseBinary() + { + cancellationToken.ThrowIfCancellationRequested(); + + //Binary = Range / Concatenation / Alternative; + //Concatenation = Rule Rule; + //Alternative = Rule "/" Rule; + var left = parseRange(); + discardTrivia(discardWhitespace: false); + while(match(TokenType.Slash, TokenType.Whitespace) && !check(TokenType.Semicolon)) + { + cancellationToken.ThrowIfCancellationRequested(); + + var isAlternative = previous().Type == TokenType.Slash || + match(TokenType.Slash); // in case we have [Whitespace: ][Slash:/] + + discardTrivia(); + var right = parseRange(); + left = isAlternative ? + new Rule.Alternative(left, right) : + new Rule.Concatenation(left, right); + discardTrivia(discardWhitespace: false); + } + + return left; + } + + Rule parseRange() + { + cancellationToken.ThrowIfCancellationRequested(); + + //Range = Unary / Char "-" Char; + //Char = "\"" any character, quotes must be escaped "\""; + if(!check(TokenType.Terminal, t => t.Lexeme.Length == 1)) + { + return parseUnary(); + } + + var start = advance(); + if(!match(TokenType.Dash)) + { + return new Terminal(start); + } + + discardTrivia(); + var end = consume(TokenType.Terminal, "Expected terminal token of length one.", t => t.Lexeme.Length == 1); + + return new Rule.Range(new(start), new(end)); + } + + Rule parseUnary() + { + cancellationToken.ThrowIfCancellationRequested(); + + //Unary = Primary / VariableRepetition / SpecificRepetition; + //VariableRepetition = "*" Rule; + //SpecificRepetition = NUMBER Rule; + if(match(TokenType.Star, TokenType.Number)) + { + var @operator = previous(); + var isVariable = @operator.Type == TokenType.Star; + discardTrivia(); + var operand = parsePrimary(); + return isVariable ? + new Rule.VariableRepetition(operand) : + new Rule.SpecificRepetition(Int32.Parse(@operator.Lexeme.ToString()), operand); + } + + discardTrivia(); + return parsePrimary(); + } + + Rule parsePrimary() + { + cancellationToken.ThrowIfCancellationRequested(); + + //Primary = Grouping / OptionalGrouping / TerminalOrName; + //Grouping = TerminalOrName / ("(" Rule ")"); + //OptionalGrouping = TerminalOrName / ("[" Rule "]"); + var isSequence = match(TokenType.ParenLeft); //if false, none will have been consumed + if(!(isSequence || match(TokenType.BracketLeft))) //short cicuit prevents double consumption otherwise + { + discardTrivia(); + return parseTerminalOrName(); + } + //is either sequence or optional sequence + discardTrivia(); + var rule = parseRule(); + discardTrivia(); + _ = isSequence ? + consume(TokenType.ParenRight, "Expected ')' after rule grouping.") : + consume(TokenType.BracketRight, "Expected ']' after optional rule grouping."); + Rule result = isSequence ? + new Rule.Grouping(rule) : + new Rule.OptionalGrouping(rule); + + return result; + } + + Rule parseTerminalOrName() + { + cancellationToken.ThrowIfCancellationRequested(); + + //TerminalOrName = Terminal / Any / Name; + //Name = Alpha; + //Terminal = "\"". "\""; + //Any = "."; + if(match(TokenType.Name)) + { + var name = previous(); + return new Reference(new(name)); + } + + if(match(TokenType.Period)) + { + return Any.Instance; + } + + discardTrivia(); + return parseTerminal(); + } + + Rule parseTerminal() + { + var terminalValue = consume(TokenType.Terminal, "Expected terminal."); + return new Terminal(terminalValue); + } + + Boolean match(params TokenType[] types) + { + foreach(var type in types) + { + if(check(type)) + { + _ = advance(); + return true; + } + } + + return false; + } + + Token consume(TokenType type, String message, Func? matchPredicate = null) + { + if(check(type, matchPredicate)) + return advance(); + + throw error(peek(), message); + } + + void discardTrivia(Boolean discardWhitespace = true) + { + while(match(TokenType.NewLine, TokenType.Comment) || discardWhitespace && match(TokenType.Whitespace)) + { } + } + + Boolean check(TokenType type, Func? matchPredicate = null) + { + if(isAtEnd() || matchPredicate != null && !matchPredicate.Invoke(peek())) + return false; + + return peek().Type == type; + } + + Token advance() + { + if(!isAtEnd()) + current++; + return previous(); + } + + Boolean isAtEnd() => peek().Type == TokenType.Eof; + + Token peek() => tokens![current]; + + Token previous() => tokens![current - 1]; + + ParseException error(Token token, String message) + { + diagnostics!.Add(UnexpectedToken, token.Location, token.Type, message); + return new ParseException(); + } + + void synchronize() + { + _ = advance(); + + while(!isAtEnd()) + { + if(previous().Type == TokenType.Semicolon) + return; + + _ = advance(); + } + } + } +} diff --git a/DslGenerator/Properties/launchSettings.json b/DslGenerator/Properties/launchSettings.json new file mode 100644 index 0000000..5e98b4d --- /dev/null +++ b/DslGenerator/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "TestApp": { + "commandName": "DebugRoslynComponent", + "targetProject": "..\\DslGenerator.TestApp\\DslGenerator.TestApp.csproj" + } + } +} \ No newline at end of file diff --git a/DslGenerator/README.md b/DslGenerator/README.md new file mode 100644 index 0000000..dc9854f --- /dev/null +++ b/DslGenerator/README.md @@ -0,0 +1,3 @@ +# DslGenerator + +Lexes, Parses Abnf and more. \ No newline at end of file diff --git a/DslGenerator/Usings.cs b/DslGenerator/Usings.cs new file mode 100644 index 0000000..cba61db --- /dev/null +++ b/DslGenerator/Usings.cs @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: MPL-2.0 + +global using RhoMicro.CodeAnalysis.Library; +global using RhoMicro.CodeAnalysis.Library.Text; diff --git a/EnumConstStringGenerator/Generators/EnumConstStringGenerator.cs b/EnumConstStringGenerator/Generators/EnumConstStringGenerator.cs index fd1efeb..682e8c5 100644 --- a/EnumConstStringGenerator/Generators/EnumConstStringGenerator.cs +++ b/EnumConstStringGenerator/Generators/EnumConstStringGenerator.cs @@ -33,9 +33,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) { ct.ThrowIfCancellationRequested(); if(ctx.TargetSymbol is not INamedTypeSymbol { TypeKind: TypeKind.Enum } target) - { return null; - } var members = target .GetMembers() @@ -98,9 +96,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) IndentedStringBuilderOptions.GeneratedFile with { AmbientCancellationToken = ct }); if(@namespace != String.Empty) - { builder.Append("namespace ").Append(@namespace).Append(';').AppendLineCore(); - } builder.Append("public static class ").Append(name).Append("Strings") .OpenBracesBlock() diff --git a/EnumStringTestApp/EnumStringTestApp.csproj b/EnumStringTestApp/EnumStringTestApp.csproj new file mode 100644 index 0000000..5f95c1c --- /dev/null +++ b/EnumStringTestApp/EnumStringTestApp.csproj @@ -0,0 +1,14 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + diff --git a/EnumStringTestApp/GlobalSuppressions.cs b/EnumStringTestApp/GlobalSuppressions.cs new file mode 100644 index 0000000..da7b10a --- /dev/null +++ b/EnumStringTestApp/GlobalSuppressions.cs @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MPL-2.0 + +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "")] diff --git a/EnumStringTestApp/Program.cs b/EnumStringTestApp/Program.cs new file mode 100644 index 0000000..d73d13d --- /dev/null +++ b/EnumStringTestApp/Program.cs @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MPL-2.0 + +Console.WriteLine(TestNamespace.FooStrings.Foo); //generated members +Console.WriteLine(TestNamespace.FooStrings.FooBar); +Console.WriteLine(TestNamespace.FooStrings.Bar); +Console.WriteLine(TestNamespace.FooStrings.Baz); + +namespace TestNamespace +{ + [RhoMicro.CodeAnalysis.GenerateMemberStringConstants] + internal enum Foo: Int64 + { + Foo, + FooBar, + Bar = 23, + Baz + } +} diff --git a/IndentedStringBuilderTestApp/IndentedStringBuilderTestApp.csproj b/IndentedStringBuilderTestApp/IndentedStringBuilderTestApp.csproj new file mode 100644 index 0000000..a2df4f5 --- /dev/null +++ b/IndentedStringBuilderTestApp/IndentedStringBuilderTestApp.csproj @@ -0,0 +1,21 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/IndentedStringBuilderTestApp/Program.cs b/IndentedStringBuilderTestApp/Program.cs new file mode 100644 index 0000000..08afc55 --- /dev/null +++ b/IndentedStringBuilderTestApp/Program.cs @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace IndentedStringBuilderTestApp; + +using RhoMicro.CodeAnalysis.Library.Text.SourceTexts; + +using static RhoMicro.CodeAnalysis.Library.Text.SourceTexts.IndentedStringBuilder.Appendables; + +internal class Program +{ + public static void Main(String[] _0) + { + //confusing: mutating operand instead of returning mutated instance + var operators = new IndentedStringBuilder().Operators + + "Hello, World!" + AppendLine() + + "Here is a new line :)" + + OpenBracesBlock() + + "inside a block now..." + + CloseBlock() + + "and done :)"; + + var result = operators.Builder.ToString(); + + Console.WriteLine(result); + } + + private static void Test1() + { + var builder = new IndentedStringBuilder( + IndentedStringBuilderOptions.Default with { DefaultIndentation = " " }) + .Append("namespace ").Append("Some.Parameter.Namespace") + .OpenBracesBlock(); + _ = builder.Append("partial class ").Append("SomeParameterName") + .OpenBracesBlock() + .Comment.OpenSummary() + .Append("Sample method") + .CloseBlock() + .Comment.OpenParam("a") + .Append("Sample Argument.") + .CloseBlock() + .Append("public void Foo(Int32 a)") + .OpenBracesBlock() + .Append("String[] bar = ") + .OpenBracketsBlock() + .Append("\"foobar\"") + .CloseBlock() + .AppendLine(';'); + + using(builder.OpenBracesBlockScope()) + { + _ = builder.Append("_ = foobar();"); + } + + _ = builder + .AppendLine("throw new NotImplementedException();") + .CloseBlock() + .AppendLine("void Bar()"); + + using(builder.OpenBracesBlockScope()) + { + using(builder.CreateIndentScope()) + { + _ = builder.OpenBracesBlock().CloseBlock().Append("throw new NotImplementedException();"); + } + } + + var result = builder + .CloseAllBlocks() + .ToString(); + + Console.WriteLine(result); + } +} diff --git a/Janus.Analyzers/AnalyzerReleases.Shipped.md b/Janus.Analyzers/AnalyzerReleases.Shipped.md new file mode 100644 index 0000000..f3c849d --- /dev/null +++ b/Janus.Analyzers/AnalyzerReleases.Shipped.md @@ -0,0 +1,25 @@ +## Release 23.0.0 + +### New Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|-------------------- + RMJ0001 | Usage | Warning | `ToStringSetting` is ignored due to user defined `ToString` implementation + RMJ0002 | Usage | Error | `record` unions are disallowed + RMJ0003 | Usage | Error | Generic unions cannot be json serializable + RMJ0004 | Usage | Error | No more than 31 variant groups may be defined + RMJ0005 | Usage | Error | `static` unions are disallowed + RMJ0006 | Usage | Error | Duplicate variant names are disallowed + RMJ0007 | Design | Warning | At least one unmanaged or managed struct or nullable reference type variant must be defined for struct unions + RMJ0008 | Design | Warning | `interface` variants are excluded from conversion operators + RMJ0009 | Usage | Error | Duplicate variant types are disallowed + RMJ0010 | Usage | Error | `object` cannot be used as a variant + RMJ0012 | Usage | Error | Variants that are the union itself are disallowed + RMJ0013 | Usage | Error | Explicitly defined base classes are disallowed + RMJ0014 | Usage | Warning | Prefer `Nullable` over `IsNullable = true` + RMJ0015 | Usage | Error | `Nullable` and `T` variants for the same type `T` are disallowed + RMJ0016 | Usage | Warning | `UnionTypeSettings` are ignored if missing `UnionTypeAttribute` + RMJ0017 | Usage | Warning | Duplicate variant group names are ignored + RMJ0018 | Design | Warning | Class unions should be sealed + RMJ0019 | Usage | Error | `ValueType` cannot be used as a variant of struct unions + RMJ0020 | Usage | Error | `ref struct` cannot be union diff --git a/Janus.Analyzers/AnalyzerReleases.Unshipped.md b/Janus.Analyzers/AnalyzerReleases.Unshipped.md new file mode 100644 index 0000000..e69de29 diff --git a/Janus.Analyzers/Components/ConstructorComponent.cs b/Janus.Analyzers/Components/ConstructorComponent.cs index bb03836..70c5475 100644 --- a/Janus.Analyzers/Components/ConstructorComponent.cs +++ b/Janus.Analyzers/Components/ConstructorComponent.cs @@ -34,7 +34,7 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation if (validate) { var isValid = true; - Validate{{variant.Name}}(value, throwIfInvalid: true, ref isValid); + Validate(value, throwIfInvalid: true, ref isValid); } {{Create(variant, static (v, b, ct) => diff --git a/Janus.Analyzers/Components/FactoriesComponent.cs b/Janus.Analyzers/Components/FactoriesComponent.cs index aaa2df4..2ee34ac 100644 --- a/Janus.Analyzers/Components/FactoriesComponent.cs +++ b/Janus.Analyzers/Components/FactoriesComponent.cs @@ -149,7 +149,7 @@ public static bool TryCreate( b.Append($"{Langword("true")} if an instance of {Cref(m.DocsCommentId)} could be created; otherwise, {Langword("false")}."); })}} - public static bool TryCreateFrom( + public static bool TryCreate( {{new UnionTypeNameComponent(m)}} value, [{{typeof(NotNullWhenAttribute)}}(true)] out {{new UnionTypeNameComponent(m, RenderNullable: true)}} union) @@ -193,74 +193,10 @@ public static bool TryCreateFrom( b.Append($"The new instance of {Cref(m.DocsCommentId)}."); })}} - public static {{new UnionTypeNameComponent(m)}} CreateFrom( + public static {{new UnionTypeNameComponent(m)}} Create( {{variant.Type.NullableName}} value) => new {{new UnionTypeNameComponent(m)}}(value, validate: true); - {{Summary(m, static (m, b, ct) => - { - ct.ThrowIfCancellationRequested(); - - b.Append($"Creates an instance of {Cref(m.DocsCommentId)}."); - })}} - {{Param("value", m, static (m, b, ct) => - { - ct.ThrowIfCancellationRequested(); - - b.Append($"The value to create an instance of {Cref(m.DocsCommentId)} from."); - })}} - {{Returns(m, static (m, b, ct) => - { - ct.ThrowIfCancellationRequested(); - - b.Append($"The new instance of {Cref(m.DocsCommentId)}."); - })}} - public static {{new UnionTypeNameComponent(m)}} CreateFrom{{variant.Name}}( - {{variant.Type.NullableName}} value) - => new {{new UnionTypeNameComponent(m)}}(value, validate: true); - - {{Summary(m, static (m, b, ct) => - { - ct.ThrowIfCancellationRequested(); - - b.Append($"Attempts to create an instance of {Cref(m.DocsCommentId)}."); - })}} - {{Param("value", m, static (m, b, ct) => - { - ct.ThrowIfCancellationRequested(); - - b.Append($"The value to create an instance of {Cref(m.DocsCommentId)} from."); - })}} - {{Param("union", m, static (m, b, ct) => - { - ct.ThrowIfCancellationRequested(); - - b.Append( - $""" - The instance of {Cref(m.DocsCommentId)} if one could be created; otherwise, - {(m.TypeKind is UnionTypeKind.Class ? Langword("null") : Langword("default"))}. - """); - })}} - {{Returns(m, static (m, b, ct) => - { - ct.ThrowIfCancellationRequested(); - - b.Append($"{Langword("true")} if an instance of {Cref(m.DocsCommentId)} could be created; otherwise, {Langword("false")}."); - })}} - public static bool TryCreateFrom( - {{variant.Type.NullableName}} value, - [{{typeof(NotNullWhenAttribute)}}(true)] - out {{new UnionTypeNameComponent(m, RenderNullable: true)}} union) - { - var isValid = true; - Validate{{variant.Name}}(value, throwIfInvalid: false, ref isValid); - union = isValid - ? new {{new UnionTypeNameComponent(m)}}(value, validate: false) - : default; - - return isValid; - } - {{Summary(m, static (m, b, ct) => { ct.ThrowIfCancellationRequested(); @@ -289,13 +225,13 @@ public static bool TryCreateFrom( b.Append($"{Langword("true")} if an instance of {Cref(m.DocsCommentId)} could be created; otherwise, {Langword("false")}."); })}} - public static bool TryCreateFrom{{variant.Name}}( + public static bool TryCreate( {{variant.Type.NullableName}} value, [{{typeof(NotNullWhenAttribute)}}(true)] out {{new UnionTypeNameComponent(m, RenderNullable: true)}} union) { var isValid = true; - Validate{{variant.Name}}(value, throwIfInvalid: false, ref isValid); + Validate(value, throwIfInvalid: false, ref isValid); union = isValid ? new {{new UnionTypeNameComponent(m)}}(value, validate: false) : default; diff --git a/Janus.Analyzers/Components/FactoryComponent.cs b/Janus.Analyzers/Components/FactoryComponent.cs index 733a0ff..3dbbdcb 100644 --- a/Janus.Analyzers/Components/FactoryComponent.cs +++ b/Janus.Analyzers/Components/FactoryComponent.cs @@ -72,8 +72,6 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation { ct.ThrowIfCancellationRequested(); - var nullCaseHandled = false; - for (var i = 0; i < m.Variants.Count; i++) { ct.ThrowIfCancellationRequested(); @@ -84,16 +82,7 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation b.AppendLine(); } - b.Append($"case {variant.Type.Name} v: return {new UnionTypeNameComponent(m)}.TryCreateFrom{variant.Name}(v, out union);"); - - if (!variant.Type.IsNullable || nullCaseHandled) { - continue; - } - - b.AppendLine() - .Append($"case null: return {new UnionTypeNameComponent(m)}.TryCreateFrom{variant.Name}(({variant.Type.NullableName})null, out union);"); - - nullCaseHandled = true; + b.Append($"case {variant.Type.Name} v: return {new UnionTypeNameComponent(m)}.TryCreate(v, out union);"); } })}} case {{new UnionTypeNameComponent(m)}} v: @@ -144,8 +133,6 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation { ct.ThrowIfCancellationRequested(); - var nullCaseHandled = false; - for (var i = 0; i < m.Variants.Count; i++) { ct.ThrowIfCancellationRequested(); @@ -157,15 +144,6 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation } b.Append($"case {variant.Type.Name} v: return new {new UnionTypeNameComponent(m)}(v);"); - - if (!variant.Type.IsNullable || nullCaseHandled) { - continue; - } - - b.AppendLine() - .Append($"case null: return new {new UnionTypeNameComponent(m)}(({variant.Type.NullableName})null);"); - - nullCaseHandled = true; } })}} case {{new UnionTypeNameComponent(m)}} v: return new {{new UnionTypeNameComponent(m)}}(v); diff --git a/Janus.Analyzers/Components/OperatorsComponent.cs b/Janus.Analyzers/Components/OperatorsComponent.cs index 50b0935..73dd739 100644 --- a/Janus.Analyzers/Components/OperatorsComponent.cs +++ b/Janus.Analyzers/Components/OperatorsComponent.cs @@ -35,7 +35,7 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation b.Append( $""" {Inheritdoc()} - public static implicit operator {new UnionTypeNameComponent(m)}({variant.Type.NullableName} value) => CreateFrom{variant.Name}(value); + public static implicit operator {new UnionTypeNameComponent(m)}({variant.Type.NullableName} value) => Create(value); {Inheritdoc()} public static {(m.Variants.Count is 1 ? "implicit" : "explicit")} operator {variant.Type.NullableName}({new UnionTypeNameComponent(m)} union) => union.CastTo{variant.Name}; """ diff --git a/Janus.Analyzers/Components/ValidationComponent.cs b/Janus.Analyzers/Components/ValidationComponent.cs index 863189d..a6c389a 100644 --- a/Janus.Analyzers/Components/ValidationComponent.cs +++ b/Janus.Analyzers/Components/ValidationComponent.cs @@ -58,7 +58,7 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation {Param("value", "The value to validate.")} {Param("throwIfInvalid", "Indicates whether to throw an exception if the value is invalid.")} {Param("isValid", "Indicates whether the value is valid.")} - static partial void Validate{v.Name}({v.Type.NullableName} value, bool throwIfInvalid, ref bool isValid); + static partial void Validate({v.Type.NullableName} value, bool throwIfInvalid, ref bool isValid); """ ); }, separator: "\n\n")}} diff --git a/Janus.Analyzers/DiagnosticDescriptors.cs b/Janus.Analyzers/DiagnosticDescriptors.cs index c8db4db..daeed37 100644 --- a/Janus.Analyzers/DiagnosticDescriptors.cs +++ b/Janus.Analyzers/DiagnosticDescriptors.cs @@ -163,12 +163,4 @@ internal static class DiagnosticDescriptors category: "Usage", defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true); - - public static DiagnosticDescriptor TypeParameterVariantsShouldBeNamed { get; } = new( - id: "RMJ0021", - title: "Type parameter variants should be named", - messageFormat: "Variant `{0}` of union `{1}` should be named explicitly", - category: "Design", - defaultSeverity: DiagnosticSeverity.Warning, - isEnabledByDefault: true); } diff --git a/Janus.Analyzers/Janus.Analyzers.csproj b/Janus.Analyzers/Janus.Analyzers.csproj index 8505fef..4786796 100644 --- a/Janus.Analyzers/Janus.Analyzers.csproj +++ b/Janus.Analyzers/Janus.Analyzers.csproj @@ -32,4 +32,9 @@ + + + + + diff --git a/Janus.Analyzers/JanusAnalyzer.cs b/Janus.Analyzers/JanusAnalyzer.cs index cf4f820..88ad9a7 100644 --- a/Janus.Analyzers/JanusAnalyzer.cs +++ b/Janus.Analyzers/JanusAnalyzer.cs @@ -41,7 +41,6 @@ public sealed partial class JanusAnalyzer : DiagnosticAnalyzer DiagnosticDescriptors.DuplicateVariantGroupNamesAreIgnored, DiagnosticDescriptors.ClassUnionsShouldBeSealed, DiagnosticDescriptors.UnionCannotBeRefStruct, - DiagnosticDescriptors.TypeParameterVariantsShouldBeNamed, ]; /// @@ -90,92 +89,6 @@ public override void Initialize(AnalysisContext context) ReportClassUnionsShouldBeSealed, SymbolKind.NamedType); context.RegisterSymbolAction( ReportUnionCannotBeRefStruct, SymbolKind.NamedType); - context.RegisterOperationAction( - ReportTypeParameterVariantsShouldBeNamed, OperationKind.Attribute); - } - - private static void ReportTypeParameterVariantsShouldBeNamed(OperationAnalysisContext ctx) - { - var ct = ctx.CancellationToken; - - ct.ThrowIfCancellationRequested(); - - if (!AttributeAnalysisContext.TryCreateForUnionTypeAttribute(ctx, out var attributeContext)) - { - return; - } - - if (attributeContext.TargetSymbol is not ITypeParameterSymbol) - { - return; - } - - var initializers = attributeContext.AttributeOperation.Initializer?.Initializers ?? []; - - var hasNameAssignment = false; - - foreach (var initializer in initializers) - { - ct.ThrowIfCancellationRequested(); - - if (initializer is ISimpleAssignmentOperation - { - Target: IPropertyReferenceOperation - { - Member.Name: nameof(UnionTypeAttribute.Name) - } - }) - { - hasNameAssignment = true; - break; - } - } - - if (hasNameAssignment) - { - return; - } - - if (!attributeContext.TryGetUnionTypeSymbol(out var union)) - { - return; - } - - var unioName = union.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageFormat); - var variantName = attributeContext.TargetSymbol.Name; - foreach (var reference in union.DeclaringSyntaxReferences) - { - ct.ThrowIfCancellationRequested(); - - if (reference.GetSyntax(ct) is not TypeDeclarationSyntax - { - TypeParameterList.Parameters: [{ }, ..] typeParameters - }) - { - continue; - } - - foreach (var typeParameterSyntax in typeParameters) - { - ct.ThrowIfCancellationRequested(); - - if (typeParameterSyntax.Identifier.Text != variantName) - { - continue; - } - - var location = typeParameterSyntax.Identifier.GetLocation(); - var diagnostic = Diagnostic.Create( - DiagnosticDescriptors.TypeParameterVariantsShouldBeNamed, - location, - messageArgs: - [ - variantName, - unioName - ]); - ctx.ReportDiagnostic(diagnostic); - } - } } private static void ReportUnionCannotBeRefStruct(SymbolAnalysisContext ctx) diff --git a/Janus.Analyzers/Attributes/UnionTypeAttribute.cs b/Janus.Analyzers/UnionTypeAttribute.cs similarity index 100% rename from Janus.Analyzers/Attributes/UnionTypeAttribute.cs rename to Janus.Analyzers/UnionTypeAttribute.cs diff --git a/Janus.Analyzers/Attributes/UnionTypeSettingsAttribute.cs b/Janus.Analyzers/UnionTypeSettingsAttribute.cs similarity index 98% rename from Janus.Analyzers/Attributes/UnionTypeSettingsAttribute.cs rename to Janus.Analyzers/UnionTypeSettingsAttribute.cs index 86a3a6c..ae3019e 100644 --- a/Janus.Analyzers/Attributes/UnionTypeSettingsAttribute.cs +++ b/Janus.Analyzers/UnionTypeSettingsAttribute.cs @@ -80,13 +80,13 @@ enum JsonConverterSetting /// Inherit, /// - /// A JSON converter implementation is emitted. + /// No JSON converter implementation is emitted. /// - EmitJsonConverter, + OmitJsonConverter, /// - /// No JSON converter implementation is emitted. + /// A JSON converter implementation is emitted. /// - OmitJsonConverter + EmitJsonConverter } /// @@ -111,6 +111,8 @@ sealed partial class UnionTypeSettingsAttribute : global::System.Attribute /// reference equality for comparing reference union types via == or !=. /// public EqualityOperatorsSetting EqualityOperatorsSetting { get; set; } + + /// /// Gets or sets a value indicating whether to make the union type JSON serializable. /// diff --git a/Janus.Benchmarks/Directory.build.props b/Janus.Benchmarks/Directory.build.props deleted file mode 100644 index adf14e9..0000000 --- a/Janus.Benchmarks/Directory.build.props +++ /dev/null @@ -1,5 +0,0 @@ - - - $(NoWarn);CS1591 - - diff --git a/Janus.Benchmarks/Janus.Benchmarks.csproj b/Janus.Benchmarks/Janus.Benchmarks.csproj deleted file mode 100644 index 364ba0e..0000000 --- a/Janus.Benchmarks/Janus.Benchmarks.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - Exe - net10.0 - preview - enable - enable - Janus.Benchmarks - - - - - - - - - - - - - - diff --git a/Janus.Benchmarks/Program.cs b/Janus.Benchmarks/Program.cs deleted file mode 100644 index 919e08f..0000000 --- a/Janus.Benchmarks/Program.cs +++ /dev/null @@ -1,6 +0,0 @@ -// See https://aka.ms/new-console-template for more information - -using System.Security.Cryptography.X509Certificates; -using BenchmarkDotNet.Running; - -BenchmarkRunner.Run(typeof(Program).Assembly); diff --git a/Janus.Benchmarks/SwitchStateBenchmark.cs b/Janus.Benchmarks/SwitchStateBenchmark.cs deleted file mode 100644 index 9fbc8a1..0000000 --- a/Janus.Benchmarks/SwitchStateBenchmark.cs +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -using BenchmarkDotNet.Attributes; -using OneOf; -using RhoMicro.CodeAnalysis; - -[SimpleJob] -[MemoryDiagnoser] -public partial class SwitchStateBenchmark -{ - [UnionType] - partial struct JanusUnion; - - [Benchmark(Baseline = true)] - public String Janus() - { - var state = "State"; - - var result = new JanusUnion(32).Switch( - state, - onInt32: static (_, s) => s, - onSingle: static (_, s) => s); - - return result; - } - - [Benchmark] - public String OneOf() - { - var state = "State"; - - var result = OneOf.FromT0(32).Match( - f0: _ => state, - f1: _ => state); - - return result; - } -} diff --git a/Janus.Benchmarks/UnmanagedOverlayingBenchmark.cs b/Janus.Benchmarks/UnmanagedOverlayingBenchmark.cs deleted file mode 100644 index a8253c1..0000000 --- a/Janus.Benchmarks/UnmanagedOverlayingBenchmark.cs +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 - -using BenchmarkDotNet.Attributes; -using OneOf; -using RhoMicro.CodeAnalysis; - -[SimpleJob] -[MemoryDiagnoser] -public partial class UnmanagedOverlayingBenchmark -{ - [UnionType] - sealed partial class JanusUnion; - - class OneOfUnion : OneOfBase - { - public OneOfUnion(OneOf input) : base(input) - { - } - } - - [Benchmark(Baseline = true)] - public Object Janus() => new JanusUnion(0); - - [Benchmark] - public Object OneOf() => new OneOfUnion(0); -} diff --git a/Janus.TestApplication/Program.cs b/Janus.TestApplication/Program.cs index e6d7ca8..7bfd698 100644 --- a/Janus.TestApplication/Program.cs +++ b/Janus.TestApplication/Program.cs @@ -11,7 +11,6 @@ PropertyNamingPolicy = new CustomNamingPolicy(), PropertyNameCaseInsensitive = true, }; -// ReSharper disable once RedundantAssignment var union = IntDoubleString.Create(42d); union = "foo"; union = 47; @@ -31,12 +30,7 @@ Console.WriteLine(union); var serialized = JsonSerializer.Serialize(union, options); Console.WriteLine(serialized); -#pragma warning disable CA1308 -serialized = serialized - .ToLowerInvariant() -#pragma warning restore CA1308 - .Replace("47", "[9,8,7]", StringComparison.InvariantCulture) - .Replace("2", "3", StringComparison.InvariantCulture); +serialized = serialized.ToLower().Replace("47", "[9,8,7]").Replace("2", "3"); Console.WriteLine(serialized); var deserialized = JsonSerializer.Deserialize( diff --git a/Janus.Tests.EndToEnd/IsPropertyTests.cs b/Janus.Tests.EndToEnd/IsPropertyTests.cs index e877df3..874b8b2 100644 --- a/Janus.Tests.EndToEnd/IsPropertyTests.cs +++ b/Janus.Tests.EndToEnd/IsPropertyTests.cs @@ -1,6 +1,5 @@ // SPDX-License-Identifier: MPL-2.0 -#pragma warning disable RMJ0021 #pragma warning disable IDE0059 // Unnecessary assignment of a value #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member namespace RhoMicro.CodeAnalysis.Janus.EndToEnd.Tests; diff --git a/Janus.Tests.EndToEnd/ReadMeAssertions.cs b/Janus.Tests.EndToEnd/ReadMeAssertions.cs index bdd56f6..9a1b755 100644 --- a/Janus.Tests.EndToEnd/ReadMeAssertions.cs +++ b/Janus.Tests.EndToEnd/ReadMeAssertions.cs @@ -3,8 +3,6 @@ #pragma warning disable IDE0250 #pragma warning disable IDE0059 #pragma warning disable CS1591 -#pragma warning disable RMJ0021 - namespace RhoMicro.CodeAnalysis.Janus.EndToEnd.Tests; using System; diff --git a/Janus.Tests.EndToEnd/TryAsFunctionsTests.cs b/Janus.Tests.EndToEnd/TryAsFunctionsTests.cs index 6ac8b3c..a05e142 100644 --- a/Janus.Tests.EndToEnd/TryAsFunctionsTests.cs +++ b/Janus.Tests.EndToEnd/TryAsFunctionsTests.cs @@ -1,6 +1,5 @@ // SPDX-License-Identifier: MPL-2.0 -#pragma warning disable RMJ0021 #pragma warning disable IDE0059 // Unnecessary assignment of a value #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member namespace RhoMicro.CodeAnalysis.Janus.EndToEnd.Tests; diff --git a/Janus.Tests/Janus.Tests.csproj b/Janus.Tests/Janus.Tests.csproj index 82973aa..6685fb7 100644 --- a/Janus.Tests/Janus.Tests.csproj +++ b/Janus.Tests/Janus.Tests.csproj @@ -6,7 +6,6 @@ Exe Janus.Tests net10.0 - $(NoWarn);CS1591;NU1510;NU1701 + - - netstandard2.0 - false - true - true - true - enable - enable - + + netstandard2.0 + false + true + true + true + enable + enable + - - true - true - Generates json schemata from class definitions. - Source Generator;json schema;json;jsonschema;appsettings - logo_256.png - + + true + true + Generates json schemata from class definitions. + Source Generator;json schema;json;jsonschema;appsettings + logo_256.png + - - - + + + - - $(DefineConstants);GENERATOR - + + $(DefineConstants);GENERATOR + - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - - - - - - + + + + + + - - - true - TargetFramework=net9.0 - tool - tool - PreserveNewest - - + + + true + TargetFramework=net9.0 + tool + tool + PreserveNewest + + - - - PreserveNewest - - - PreserveNewest - - + + + PreserveNewest + + + PreserveNewest + + diff --git a/JsonSchemaGenerator/Models/Core/JsonListLikeModel.cs b/JsonSchemaGenerator/Models/Core/JsonListLikeModel.cs index 37aaf6f..713ef49 100644 --- a/JsonSchemaGenerator/Models/Core/JsonListLikeModel.cs +++ b/JsonSchemaGenerator/Models/Core/JsonListLikeModel.cs @@ -22,9 +22,7 @@ public override void AppendTo(StringBuilder sb, CancellationToken ct) { ct.ThrowIfCancellationRequested(); if(i > 0 && Value.Count > 1) - { _ = sb.Append(','); - } _ = sb.AppendModel(item, ct); i++; diff --git a/JsonSchemaGenerator/Models/Core/JsonNumberModel.cs b/JsonSchemaGenerator/Models/Core/JsonNumberModel.cs index b7a0d61..1b7dc95 100644 --- a/JsonSchemaGenerator/Models/Core/JsonNumberModel.cs +++ b/JsonSchemaGenerator/Models/Core/JsonNumberModel.cs @@ -12,7 +12,7 @@ public JsonNumberModel(Number value) : base(value) { } public override void AppendTo(StringBuilder sb, CancellationToken ct) { ct.ThrowIfCancellationRequested(); - _ = sb.Append(Value.Switch( + _ = sb.Append(Value.Match( static d => d.ToString("0.#", CultureInfo.InvariantCulture), static l => l.ToString("0.#", CultureInfo.InvariantCulture), static ul => ul.ToString("0.#", CultureInfo.InvariantCulture))); diff --git a/JsonSchemaGenerator/Models/Core/JsonObjectModel.cs b/JsonSchemaGenerator/Models/Core/JsonObjectModel.cs index d7627d6..b26752d 100644 --- a/JsonSchemaGenerator/Models/Core/JsonObjectModel.cs +++ b/JsonSchemaGenerator/Models/Core/JsonObjectModel.cs @@ -22,9 +22,7 @@ public override void AppendTo(StringBuilder sb, CancellationToken ct) { ct.ThrowIfCancellationRequested(); if(i > 0 && Value.Count > 1) - { _ = sb.Append(','); - } _ = sb.Append('"').Append(key).Append("\":").AppendModel(value, ct); i++; diff --git a/JsonSchemaGenerator/Models/Core/JsonSchemaModel.cs b/JsonSchemaGenerator/Models/Core/JsonSchemaModel.cs index 0387c72..d8e9abf 100644 --- a/JsonSchemaGenerator/Models/Core/JsonSchemaModel.cs +++ b/JsonSchemaGenerator/Models/Core/JsonSchemaModel.cs @@ -19,21 +19,15 @@ public SubSchemaModelBuilder SubSchema() public JsonObjectModel Build(Boolean includeId = false, CancellationToken ct = default) { if(_result is not null) - { return _result; - } while(Interlocked.CompareExchange(ref _buildLock, 1, 0) == 1) - { ct.ThrowIfCancellationRequested(); - } try { if(_result is not null) - { return _result; - } _result = BuildCore(includeId, ct); return _result; @@ -79,9 +73,7 @@ private JsonObjectModel BuildCore(Boolean includeId, CancellationToken ct) } if(!hasSimple) - { return anyOf; - } var result = new JsonObjectModel(); result.Array("anyOf").Value.Add(anyOf); diff --git a/JsonSchemaGenerator/Models/Wrappers/AdditionalPropertiesModel.cs b/JsonSchemaGenerator/Models/Wrappers/AdditionalPropertiesModel.cs index 2353757..20c0efe 100644 --- a/JsonSchemaGenerator/Models/Wrappers/AdditionalPropertiesModel.cs +++ b/JsonSchemaGenerator/Models/Wrappers/AdditionalPropertiesModel.cs @@ -12,9 +12,7 @@ public JsonValueModel ModelOrSetDefault get { if(Model.Value is JsonSchemaModel { Size: 0 }) - { False(); - } return Model.Value; } diff --git a/JsonSchemaGenerator/Models/Wrappers/AnnotationsBuilder.cs b/JsonSchemaGenerator/Models/Wrappers/AnnotationsBuilder.cs index b9cf7df..f867664 100644 --- a/JsonSchemaGenerator/Models/Wrappers/AnnotationsBuilder.cs +++ b/JsonSchemaGenerator/Models/Wrappers/AnnotationsBuilder.cs @@ -9,13 +9,8 @@ internal readonly record struct AnnotationsBuilder(JsonObjectModel Model) public void CopyTo(JsonObjectModel model) { if(Description.Value is { Length: > 0 } description) - { new AnnotationsBuilder(model).Description.Value = description; - } - if(Title.Value is { Length: > 0 } title) - { new AnnotationsBuilder(model).Title.Value = title; - } } } diff --git a/JsonSchemaGenerator/Models/Wrappers/Id.cs b/JsonSchemaGenerator/Models/Wrappers/Id.cs index 9b99c45..c084110 100644 --- a/JsonSchemaGenerator/Models/Wrappers/Id.cs +++ b/JsonSchemaGenerator/Models/Wrappers/Id.cs @@ -39,9 +39,7 @@ public static Id Create(JsonSchemaAttribute? attribute, ISymbol target, Cancella var value = attribute?.Id?.Trim(); if(String.IsNullOrWhiteSpace(value)) - { return Create(target, ct); - } return Create(value!); } diff --git a/JsonSchemaGenerator/Models/Wrappers/SubSchemaModelBuilder.cs b/JsonSchemaGenerator/Models/Wrappers/SubSchemaModelBuilder.cs index 86b45d4..c13a768 100644 --- a/JsonSchemaGenerator/Models/Wrappers/SubSchemaModelBuilder.cs +++ b/JsonSchemaGenerator/Models/Wrappers/SubSchemaModelBuilder.cs @@ -41,35 +41,20 @@ public void Build(List result, Boolean includeId, CancellationTo } else { if(simple.Items.IsValueCreated && simple.Items.Value.Build(includeId: false, ct) is { Value.Count: > 0 } items) - { simpleSchema.Value.SetProperty("items", items); - } - if(simple.Type.Model is { Value.Count: > 0 } types) - { simpleSchema.Value.SetProperty("type", types); - } - if(simple.Properties.Model is { Value.Count: > 0 } properties) - { simpleSchema.Value.SetProperty("properties", properties); - } - if(simple.Required.Model is { Value.Count: > 0 } required) - { simpleSchema.Value.SetProperty("required", required); - } } if(simple.Type.Model.Value.Any(t => t is JsonTypeModel { Value: JsonType.Object })) - { simpleSchema.Value.SetProperty("additionalProperties", simple.Additional.ModelOrSetDefault); - } if(includeId && simple.GetId() is { Value.Length: > 0 } id) - { simpleSchema.Value.SetProperty("$id", id); - } if(simpleSchema is { IsValueCreated: true, Value: { } schema }) { @@ -108,9 +93,7 @@ private void Populate( if(symbol is not INamedTypeSymbol target) { if(symbol is ITypeSymbol typeSymbol && populateNonProperties) - { PopulateNonProperties(rootId, typeSymbol, idCache, isKnownToBeRef: false, ct); - } return; } @@ -123,9 +106,7 @@ private void Populate( Ref.Value.Ref(id, rootId.Value); if(populateNonProperties) - { PopulateNonProperties(rootId, target, idCache, isKnownToBeRef: true, ct); - } return; } @@ -134,15 +115,11 @@ private void Populate( ct.ThrowIfCancellationRequested(); if(populateNonProperties) - { PopulateNonProperties(rootId, target, idCache, isKnownToBeRef: false, ct); - } ct.ThrowIfCancellationRequested(); if(Ref.IsValueCreated) - { return; - } ct.ThrowIfCancellationRequested(); // is schema? @@ -183,9 +160,7 @@ private void Populate( propSchema.Populate(rootId, propType, idCache, ct); if(propSymbol.IsRequired) - { Simple.Value.Required.Add(propName); - } if(propSymbol.TryGetFirstJsonSchemaPropertyAttribute(out var a)) { @@ -214,10 +189,7 @@ private void PopulateNonProperties( ct.ThrowIfCancellationRequested(); Simple.Value.Type.Add(JsonType.Null); if(typeArg is INamedTypeSymbol namedTypeArg) - { PopulateNonProperties(rootId, namedTypeArg, idCache, isKnownToBeRef, ct); - } - return; } // enum @@ -245,9 +217,7 @@ private void PopulateNonProperties( _ => null }; if(strongValue.HasValue) - { Enum.Value.Add(strongValue.Value); - } } Populate(rootId, underlyingType, idCache, ct); @@ -261,9 +231,7 @@ private void PopulateNonProperties( } if(isKnownToBeRef) - { return; - } ct.ThrowIfCancellationRequested(); var typeString = target.ToDisplayString(SymbolDisplayFormats.FullyQualifiedNoGlobalNamespaceFormat); diff --git a/Lyra/CSharpSourceBuilder.cs b/Lyra/CSharpSourceBuilder.cs index a61338a..fe90d9b 100644 --- a/Lyra/CSharpSourceBuilder.cs +++ b/Lyra/CSharpSourceBuilder.cs @@ -84,8 +84,8 @@ public CSharpSourceBuilder() : this(CSharpSourceBuilderOptions.Default) private IInterpolationIndentationDetector _detector; private Boolean _lastWasNewLine = true; - private Boolean _lastWasEmptyLine; - private Boolean _preludeWritten; + private Boolean _lastWasEmptyLine = false; + private Boolean _preludeWritten = false; private Char[] _buffer; public CancellationToken CancellationToken { get; private set; } diff --git a/OptionsGenerator.Tests/AnalyzerTests.cs b/OptionsGenerator.Tests/AnalyzerTests.cs index 4d96c40..47c51e7 100644 --- a/OptionsGenerator.Tests/AnalyzerTests.cs +++ b/OptionsGenerator.Tests/AnalyzerTests.cs @@ -36,7 +36,7 @@ protected override IEnumerable GetSourceGenerators() => } [Fact] - public Task NonTargetWritablePropertyDoesNotRaiseROG0002() => + public Task NonTargetWritablePropertyDoesNotRaise_ROG0002() => new OptionsGeneratorAnalyzerTest( $$""" public partial interface IFoo @@ -46,7 +46,7 @@ public partial interface IFoo """).RunAsync(TestContext.Current.CancellationToken); [Fact] - public Task ReadOnlyPropertyDoesNotRaiseROG0002() => + public Task ReadOnlyPropertyDoesNotRaise_ROG0002() => new OptionsGeneratorAnalyzerTest( $$""" using RhoMicro.CodeAnalysis; @@ -58,7 +58,7 @@ public partial interface IFoo } """).RunAsync(TestContext.Current.CancellationToken); [Fact] - public Task WritablePropertyRaisesROG0002() => + public Task WritablePropertyRaises_ROG0002() => new OptionsGeneratorAnalyzerTest( $$""" using RhoMicro.CodeAnalysis; @@ -76,13 +76,13 @@ public partial interface IFoo [InlineData("T, S")] [InlineData("TName")] [InlineData("TElement, TName")] - public Task NonTargetGenericInterfaceDoesNotRaiseROG0001(String typeParameters) => + public Task NonTargetGenericInterfaceDoesNotRaise_ROG0001(String typeParameters) => new OptionsGeneratorAnalyzerTest( $"public partial interface IFoo<{typeParameters}>;") .RunAsync(TestContext.Current.CancellationToken); [Fact] - public Task NestedTargetInterfaceRaisesROG0003() => + public Task NestedTargetInterfaceRaises_ROG0003() => new OptionsGeneratorAnalyzerTest( """ using RhoMicro.CodeAnalysis; @@ -99,7 +99,7 @@ public partial interface {|ROG0003:IFoo|}; [InlineData("T, S")] [InlineData("TName")] [InlineData("TElement, TName")] - public Task GenericTargetInterfaceRaisesROG0001(String typeParameters) => + public Task GenericTargetInterfaceRaises_ROG0001(String typeParameters) => new OptionsGeneratorAnalyzerTest( $$""" using RhoMicro.CodeAnalysis; @@ -109,7 +109,7 @@ public partial interface {|ROG0001:IFoo|}<{{typeParameters}}>; """).RunAsync(TestContext.Current.CancellationToken); [Fact] - public Task NonPartialTargetInterfaceRaisesCS0260() => + public Task NonPartialTargetInterfaceRaises_CS0260() => new OptionsGeneratorAnalyzerTest( $$""" using RhoMicro.CodeAnalysis; diff --git a/OptionsGenerator.Tests/OptionsGenerator.Tests.csproj b/OptionsGenerator.Tests/OptionsGenerator.Tests.csproj index b84585c..9b69846 100644 --- a/OptionsGenerator.Tests/OptionsGenerator.Tests.csproj +++ b/OptionsGenerator.Tests/OptionsGenerator.Tests.csproj @@ -1,42 +1,42 @@ - - enable - enable - Exe - OptionsGenerator.Tests - net10.0 - $(NoWarn);CS1591;NU1510;NU1701 - - + + enable + enable + Exe + OptionsGenerator.Tests + net8.0 + + - - - + + + - - - + + + - - - - - - - - - - - + + + + + + + + - - - + + + diff --git a/OptionsGenerator/AnalyzerReleases.Shipped.md b/OptionsGenerator/AnalyzerReleases.Shipped.md new file mode 100644 index 0000000..bea396b --- /dev/null +++ b/OptionsGenerator/AnalyzerReleases.Shipped.md @@ -0,0 +1,9 @@ +## Release 20.1.4 + +### New Rules + +Rule ID | Category | Severity | Notes +--------|------------------|----------|-------------------- +ROG0001 | OptionsGenerator | Error | Option interfaces cannot be generic +ROG0002 | OptionsGenerator | Error | Option properties must be readonly +ROG0003 | OptionsGenerator | Error | Option interfaces cannot be nested diff --git a/OptionsGenerator/AnalyzerReleases.Unshipped.md b/OptionsGenerator/AnalyzerReleases.Unshipped.md new file mode 100644 index 0000000..e69de29 diff --git a/OptionsGenerator/Analyzers/OptionsAnalyzer.cs b/OptionsGenerator/Analyzers/OptionsAnalyzer.cs index 4d8e0be..9f6186d 100644 --- a/OptionsGenerator/Analyzers/OptionsAnalyzer.cs +++ b/OptionsGenerator/Analyzers/OptionsAnalyzer.cs @@ -86,14 +86,10 @@ public override void Initialize(AnalysisContext context) private static void AnalyzeSymbol(SymbolAnalysisContext ctx) { if(AnalyzeNamedTypeSymbol(ctx)) - { return; - } if(AnalyzePropertySymbol(ctx)) - { return; - } } private static Boolean AnalyzePropertySymbol(SymbolAnalysisContext ctx) diff --git a/OptionsGenerator/Generators/OptionsGenerator.cs b/OptionsGenerator/Generators/OptionsGenerator.cs index 5ab56c3..f124c67 100644 --- a/OptionsGenerator/Generators/OptionsGenerator.cs +++ b/OptionsGenerator/Generators/OptionsGenerator.cs @@ -44,9 +44,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) using var modelContext = ModelCreationContext.CreateDefault(ct); if(!OptionsModel.TryCreate(target, attribute, out var result, in modelContext)) - { return null; - } return result; }) diff --git a/OptionsGenerator/Generators/OptionsModel.cs b/OptionsGenerator/Generators/OptionsModel.cs index 1c0821e..3e77103 100644 --- a/OptionsGenerator/Generators/OptionsModel.cs +++ b/OptionsGenerator/Generators/OptionsModel.cs @@ -35,9 +35,7 @@ public static Boolean TryCreate( ctx.ThrowIfCancellationRequested(); if(!OptionsAnalyzer.IsTargetProperty(member, out var p)) - { continue; - } if(!OptionsAnalyzer.IsValidTargetProperty(p)) { diff --git a/OptionsGenerator/Generators/PropertyModel.cs b/OptionsGenerator/Generators/PropertyModel.cs index 18466ce..b7611e8 100644 --- a/OptionsGenerator/Generators/PropertyModel.cs +++ b/OptionsGenerator/Generators/PropertyModel.cs @@ -104,9 +104,7 @@ private static String GetAttributeAnnotation(AttributeData data, CancellationTok ct.ThrowIfCancellationRequested(); if(i != 0) - { _ = resultBuilder.Append(", "); - } var arg = ctorArguments[i]; var param = ctorParameters[i]; @@ -125,9 +123,7 @@ private static String GetAttributeAnnotation(AttributeData data, CancellationTok ct.ThrowIfCancellationRequested(); if(ctorArguments.Length > 0 || i != 0) - { _ = resultBuilder.Append(", "); - } var arg = namedArguments[i]; @@ -182,9 +178,7 @@ StringBuilder appendArray() ct.ThrowIfCancellationRequested(); if(i != 0) - { _ = resultBuilder.Append(", "); - } var element = constant.Values[i]; appendFullyQualifiedCSharpString(element); diff --git a/OptionsGenerator/Generators/Templates/Configuration/ConfigurationFullyQualifiedNameTemplate.cs b/OptionsGenerator/Generators/Templates/Configuration/ConfigurationFullyQualifiedNameTemplate.cs index 23bf620..743ebe7 100644 --- a/OptionsGenerator/Generators/Templates/Configuration/ConfigurationFullyQualifiedNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Configuration/ConfigurationFullyQualifiedNameTemplate.cs @@ -2,7 +2,5 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template("(:model.FullyQualifiedNamespacePrefix:)(:model.Templates().TypeNames.Configuration:)"), NonEquatable] internal readonly partial struct ConfigurationFullyQualifiedNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/Configuration/ConfigurationNameTemplate.cs b/OptionsGenerator/Generators/Templates/Configuration/ConfigurationNameTemplate.cs index f20a96c..13202c8 100644 --- a/OptionsGenerator/Generators/Templates/Configuration/ConfigurationNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Configuration/ConfigurationNameTemplate.cs @@ -2,7 +2,5 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template("(:model.NormalizedName:)Configuration"), NonEquatable] internal readonly partial struct ConfigurationNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/Configuration/ConfigurationTemplate.cs b/OptionsGenerator/Generators/Templates/Configuration/ConfigurationTemplate.cs index c7c4619..7015429 100644 --- a/OptionsGenerator/Generators/Templates/Configuration/ConfigurationTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Configuration/ConfigurationTemplate.cs @@ -2,8 +2,6 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template( """ /// diff --git a/OptionsGenerator/Generators/Templates/Immutable/ImmutableOptionsFullyQualifiedNameTemplate.cs b/OptionsGenerator/Generators/Templates/Immutable/ImmutableOptionsFullyQualifiedNameTemplate.cs index 4e627b1..340c0b2 100644 --- a/OptionsGenerator/Generators/Templates/Immutable/ImmutableOptionsFullyQualifiedNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Immutable/ImmutableOptionsFullyQualifiedNameTemplate.cs @@ -2,7 +2,5 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template("(:model.FullyQualifiedNamespacePrefix:)(:model.Templates().TypeNames.Immutable:)"), NonEquatable] internal readonly partial struct ImmutableOptionsFullyQualifiedNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/Immutable/ImmutableOptionsNameTemplate.cs b/OptionsGenerator/Generators/Templates/Immutable/ImmutableOptionsNameTemplate.cs index c587c00..a8c0a20 100644 --- a/OptionsGenerator/Generators/Templates/Immutable/ImmutableOptionsNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Immutable/ImmutableOptionsNameTemplate.cs @@ -2,7 +2,5 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template("(:model.NormalizedName:)"), NonEquatable] internal readonly partial struct ImmutableOptionsNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/Immutable/ImmutableOptionsTemplate.cs b/OptionsGenerator/Generators/Templates/Immutable/ImmutableOptionsTemplate.cs index 5c9053c..6c5d49b 100644 --- a/OptionsGenerator/Generators/Templates/Immutable/ImmutableOptionsTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Immutable/ImmutableOptionsTemplate.cs @@ -2,8 +2,6 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template( """ /// diff --git a/OptionsGenerator/Generators/Templates/Immutable/ImmutablePropertyTemplate.cs b/OptionsGenerator/Generators/Templates/Immutable/ImmutablePropertyTemplate.cs index 0a3c128..d3bd5c5 100644 --- a/OptionsGenerator/Generators/Templates/Immutable/ImmutablePropertyTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Immutable/ImmutablePropertyTemplate.cs @@ -2,8 +2,6 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template( """ /// diff --git a/OptionsGenerator/Generators/Templates/Interface/InterfaceOptionsFullyQualifiedNameTemplate.cs b/OptionsGenerator/Generators/Templates/Interface/InterfaceOptionsFullyQualifiedNameTemplate.cs index beada86..3d368d4 100644 --- a/OptionsGenerator/Generators/Templates/Interface/InterfaceOptionsFullyQualifiedNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Interface/InterfaceOptionsFullyQualifiedNameTemplate.cs @@ -2,7 +2,5 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template("(:model.FullyQualifiedNamespacePrefix:)(:model.Templates().TypeNames.Interface:)"), NonEquatable] internal readonly partial struct InterfaceOptionsFullyQualifiedNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/Interface/InterfaceOptionsNameTemplate.cs b/OptionsGenerator/Generators/Templates/Interface/InterfaceOptionsNameTemplate.cs index 9489cd3..6990c1a 100644 --- a/OptionsGenerator/Generators/Templates/Interface/InterfaceOptionsNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Interface/InterfaceOptionsNameTemplate.cs @@ -2,7 +2,5 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template("(:model.Name:)"), NonEquatable] internal readonly partial struct InterfaceOptionsNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/Interface/InterfaceOptionsTemplate.cs b/OptionsGenerator/Generators/Templates/Interface/InterfaceOptionsTemplate.cs index 2f2b23d..710d7b6 100644 --- a/OptionsGenerator/Generators/Templates/Interface/InterfaceOptionsTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Interface/InterfaceOptionsTemplate.cs @@ -2,8 +2,6 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template( """ partial interface (:model.Templates().TypeNames.Interface:) diff --git a/OptionsGenerator/Generators/Templates/MonitorPropertyTemplate.cs b/OptionsGenerator/Generators/Templates/MonitorPropertyTemplate.cs index 9ad81b3..ea187a5 100644 --- a/OptionsGenerator/Generators/Templates/MonitorPropertyTemplate.cs +++ b/OptionsGenerator/Generators/Templates/MonitorPropertyTemplate.cs @@ -2,8 +2,6 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template( """ (:new PropertyAnnotationsTemplate(model):)public (:model.Type:) (:model.Name:) => monitor.CurrentValue.(:model.Name:); diff --git a/OptionsGenerator/Generators/Templates/Mutable/MutableDefaultExpressionTemplate.cs b/OptionsGenerator/Generators/Templates/Mutable/MutableDefaultExpressionTemplate.cs index 7ec2806..5cb993b 100644 --- a/OptionsGenerator/Generators/Templates/Mutable/MutableDefaultExpressionTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Mutable/MutableDefaultExpressionTemplate.cs @@ -2,8 +2,6 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template( """ {: diff --git a/OptionsGenerator/Generators/Templates/Mutable/MutableOptionsFullyQualifiedNameTemplate.cs b/OptionsGenerator/Generators/Templates/Mutable/MutableOptionsFullyQualifiedNameTemplate.cs index d4d1efe..e2394c9 100644 --- a/OptionsGenerator/Generators/Templates/Mutable/MutableOptionsFullyQualifiedNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Mutable/MutableOptionsFullyQualifiedNameTemplate.cs @@ -2,7 +2,5 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template("(:model.FullyQualifiedNamespacePrefix:)(:model.Templates().TypeNames.Mutable:)"), NonEquatable] internal readonly partial struct MutableOptionsFullyQualifiedNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/Mutable/MutableOptionsNameTemplate.cs b/OptionsGenerator/Generators/Templates/Mutable/MutableOptionsNameTemplate.cs index 487b67d..bdcac95 100644 --- a/OptionsGenerator/Generators/Templates/Mutable/MutableOptionsNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Mutable/MutableOptionsNameTemplate.cs @@ -2,7 +2,5 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template("Mutable(:model.NormalizedName:)"), NonEquatable] internal readonly partial struct MutableOptionsNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/Mutable/MutableOptionsTemplate.cs b/OptionsGenerator/Generators/Templates/Mutable/MutableOptionsTemplate.cs index 20533b2..3fc53d6 100644 --- a/OptionsGenerator/Generators/Templates/Mutable/MutableOptionsTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Mutable/MutableOptionsTemplate.cs @@ -2,8 +2,6 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template( """ /// diff --git a/OptionsGenerator/Generators/Templates/Mutable/MutablePropertyTemplate.cs b/OptionsGenerator/Generators/Templates/Mutable/MutablePropertyTemplate.cs index 64e631a..48aa826 100644 --- a/OptionsGenerator/Generators/Templates/Mutable/MutablePropertyTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Mutable/MutablePropertyTemplate.cs @@ -2,8 +2,6 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template( """ /// diff --git a/OptionsGenerator/Generators/Templates/Passthrough/DefaultOptionsFullyQualifiedNameTemplate.cs b/OptionsGenerator/Generators/Templates/Passthrough/DefaultOptionsFullyQualifiedNameTemplate.cs index bbc7452..2a60966 100644 --- a/OptionsGenerator/Generators/Templates/Passthrough/DefaultOptionsFullyQualifiedNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Passthrough/DefaultOptionsFullyQualifiedNameTemplate.cs @@ -2,7 +2,5 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template("(:model.FullyQualifiedNamespacePrefix:)(:model.Templates().TypeNames.Default:)"), NonEquatable] internal readonly partial struct DefaultOptionsFullyQualifiedNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/Passthrough/DefaultOptionsNameTemplate.cs b/OptionsGenerator/Generators/Templates/Passthrough/DefaultOptionsNameTemplate.cs index 1a834c1..6491138 100644 --- a/OptionsGenerator/Generators/Templates/Passthrough/DefaultOptionsNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Passthrough/DefaultOptionsNameTemplate.cs @@ -2,7 +2,5 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template("Default(:model.NormalizedName:)"), NonEquatable] internal readonly partial struct DefaultOptionsNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/Passthrough/MonitorOptionsFullyQualifiedNameTemplate.cs b/OptionsGenerator/Generators/Templates/Passthrough/MonitorOptionsFullyQualifiedNameTemplate.cs index 657ffbf..474b690 100644 --- a/OptionsGenerator/Generators/Templates/Passthrough/MonitorOptionsFullyQualifiedNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Passthrough/MonitorOptionsFullyQualifiedNameTemplate.cs @@ -2,7 +2,5 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template("(:model.FullyQualifiedNamespacePrefix:)(:model.Templates().TypeNames.Monitor:)"), NonEquatable] internal readonly partial struct MonitorOptionsFullyQualifiedNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/Passthrough/MonitorOptionsNameTemplate.cs b/OptionsGenerator/Generators/Templates/Passthrough/MonitorOptionsNameTemplate.cs index c7186b6..986a272 100644 --- a/OptionsGenerator/Generators/Templates/Passthrough/MonitorOptionsNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Passthrough/MonitorOptionsNameTemplate.cs @@ -2,7 +2,5 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template("Monitor(:model.NormalizedName:)"), NonEquatable] internal readonly partial struct MonitorOptionsNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/Passthrough/PassthroughOptionsImplementationTemplate.cs b/OptionsGenerator/Generators/Templates/Passthrough/PassthroughOptionsImplementationTemplate.cs index 4bf3cd7..59329c5 100644 --- a/OptionsGenerator/Generators/Templates/Passthrough/PassthroughOptionsImplementationTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Passthrough/PassthroughOptionsImplementationTemplate.cs @@ -2,8 +2,6 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - using RhoMicro.CodeAnalysis.Library.Text.Templating; [Template( diff --git a/OptionsGenerator/Generators/Templates/Passthrough/PassthroughPropertyTemplate.cs b/OptionsGenerator/Generators/Templates/Passthrough/PassthroughPropertyTemplate.cs index 17c3b74..aa98362 100644 --- a/OptionsGenerator/Generators/Templates/Passthrough/PassthroughPropertyTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Passthrough/PassthroughPropertyTemplate.cs @@ -2,8 +2,6 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template( """ /// diff --git a/OptionsGenerator/Generators/Templates/Passthrough/SnapshotOptionsFullyQualifiedNameTemplate.cs b/OptionsGenerator/Generators/Templates/Passthrough/SnapshotOptionsFullyQualifiedNameTemplate.cs index cd26d3a..7849dbc 100644 --- a/OptionsGenerator/Generators/Templates/Passthrough/SnapshotOptionsFullyQualifiedNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Passthrough/SnapshotOptionsFullyQualifiedNameTemplate.cs @@ -2,7 +2,5 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template("(:model.FullyQualifiedNamespacePrefix:)(:model.Templates().TypeNames.Snapshot:)"), NonEquatable] internal readonly partial struct SnapshotOptionsFullyQualifiedNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/Passthrough/SnapshotOptionsNameTemplate.cs b/OptionsGenerator/Generators/Templates/Passthrough/SnapshotOptionsNameTemplate.cs index 456a37e..1058489 100644 --- a/OptionsGenerator/Generators/Templates/Passthrough/SnapshotOptionsNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/Passthrough/SnapshotOptionsNameTemplate.cs @@ -2,7 +2,5 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template("Snapshot(:model.NormalizedName:)"), NonEquatable] internal readonly partial struct SnapshotOptionsNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/PropertyAnnotationsTemplate.cs b/OptionsGenerator/Generators/Templates/PropertyAnnotationsTemplate.cs index f883f04..aa77fb5 100644 --- a/OptionsGenerator/Generators/Templates/PropertyAnnotationsTemplate.cs +++ b/OptionsGenerator/Generators/Templates/PropertyAnnotationsTemplate.cs @@ -2,8 +2,6 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template( """ {: diff --git a/OptionsGenerator/Generators/Templates/RegistrationStrategy/RegistrationStrategyFullyQualifiedNameTemplate.cs b/OptionsGenerator/Generators/Templates/RegistrationStrategy/RegistrationStrategyFullyQualifiedNameTemplate.cs index d7f2951..39a6d01 100644 --- a/OptionsGenerator/Generators/Templates/RegistrationStrategy/RegistrationStrategyFullyQualifiedNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/RegistrationStrategy/RegistrationStrategyFullyQualifiedNameTemplate.cs @@ -2,7 +2,5 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template("(:model.FullyQualifiedNamespacePrefix:)(:model.Templates().TypeNames.RegistrationStrategy:)"), NonEquatable] internal readonly partial struct RegistrationStrategyFullyQualifiedNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/RegistrationStrategy/RegistrationStrategyNameTemplate.cs b/OptionsGenerator/Generators/Templates/RegistrationStrategy/RegistrationStrategyNameTemplate.cs index 0922062..d8b2a5a 100644 --- a/OptionsGenerator/Generators/Templates/RegistrationStrategy/RegistrationStrategyNameTemplate.cs +++ b/OptionsGenerator/Generators/Templates/RegistrationStrategy/RegistrationStrategyNameTemplate.cs @@ -2,7 +2,5 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template("(:model.NormalizedName:)RegistrationStrategy"), NonEquatable] internal readonly partial struct RegistrationStrategyNameTemplate(OptionsModel model); diff --git a/OptionsGenerator/Generators/Templates/RegistrationStrategy/RegistrationStrategyTemplate.cs b/OptionsGenerator/Generators/Templates/RegistrationStrategy/RegistrationStrategyTemplate.cs index 8e3f9de..ae2b8c0 100644 --- a/OptionsGenerator/Generators/Templates/RegistrationStrategy/RegistrationStrategyTemplate.cs +++ b/OptionsGenerator/Generators/Templates/RegistrationStrategy/RegistrationStrategyTemplate.cs @@ -2,8 +2,6 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template( """ /// diff --git a/OptionsGenerator/Generators/Templates/RootNamespaceTemplate.cs b/OptionsGenerator/Generators/Templates/RootNamespaceTemplate.cs index 00a16d5..e1b4d07 100644 --- a/OptionsGenerator/Generators/Templates/RootNamespaceTemplate.cs +++ b/OptionsGenerator/Generators/Templates/RootNamespaceTemplate.cs @@ -2,8 +2,6 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template( """ {: diff --git a/OptionsGenerator/Generators/Templates/RootTemplate.cs b/OptionsGenerator/Generators/Templates/RootTemplate.cs index 01c7dba..c5df67b 100644 --- a/OptionsGenerator/Generators/Templates/RootTemplate.cs +++ b/OptionsGenerator/Generators/Templates/RootTemplate.cs @@ -2,8 +2,6 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template( """ (:new RootNamespaceTemplate(model):) diff --git a/OptionsGenerator/Generators/Templates/ServiceCollectionExtensionsTemplate.cs b/OptionsGenerator/Generators/Templates/ServiceCollectionExtensionsTemplate.cs index 8bd8901..e576211 100644 --- a/OptionsGenerator/Generators/Templates/ServiceCollectionExtensionsTemplate.cs +++ b/OptionsGenerator/Generators/Templates/ServiceCollectionExtensionsTemplate.cs @@ -2,8 +2,6 @@ namespace RhoMicro.CodeAnalysis.OptionsGenerator.Generators; -#pragma warning disable CS9113 - [Template( """ /// diff --git a/OptionsGenerator/OptionsGenerator.csproj b/OptionsGenerator/OptionsGenerator.csproj index aebd1e6..82e5f12 100644 --- a/OptionsGenerator/OptionsGenerator.csproj +++ b/OptionsGenerator/OptionsGenerator.csproj @@ -41,9 +41,4 @@ - - - - - diff --git a/Project1/Program.cs b/Project1/Program.cs new file mode 100644 index 0000000..8a695a0 --- /dev/null +++ b/Project1/Program.cs @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MPL-2.0 + +using System.Diagnostics.Metrics; +using System.Globalization; +using System.Text; +using System.Text.RegularExpressions; + +using DiffPlex; +using DiffPlex.Chunkers; +using DiffPlex.DiffBuilder; +using DiffPlex.DiffBuilder.Model; + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +using Microsoft.CodeAnalysis.CSharp.Syntax; + +using RhoMicro.CodeAnalysis; +using RhoMicro.CodeAnalysis.Library.Text.Templating; + +internal partial class Program +{ + private static void Main(String[] _) + { + Console.WriteLine( + new Letter( + new Header("Greetings,"), + new Footer("Sincerely,"), + "TebBeCo")); + } + + [Template("(:Salutation:) (:Name:).", BodyParameterName = "Name")] + readonly partial record struct Header(String Salutation); + + [Template("(:Salutation:) (:Name:).", BodyParameterName = "Name")] + readonly partial record struct Footer(String Salutation); + + [Template( + """ + {: + (:Header:)<:(:Name:):> + (:" Why is your name ":)(:Name:)(:"? ":) + static (:Footer:)<:user:> + :} + + get some rizz like fr + """)] + partial record Letter(Header Header, Footer Footer, String Name); +} diff --git a/Project1/Project1.csproj b/Project1/Project1.csproj new file mode 100644 index 0000000..3c38e19 --- /dev/null +++ b/Project1/Project1.csproj @@ -0,0 +1,21 @@ + + + + Exe + net9.0 + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Project1/README.md b/Project1/README.md new file mode 100644 index 0000000..f1a9c27 --- /dev/null +++ b/Project1/README.md @@ -0,0 +1,3 @@ +# Project1 + +- \ No newline at end of file diff --git a/RhoMicro.CodeAnalysis.slnx b/RhoMicro.CodeAnalysis.slnx index 538ea28..f8eb793 100644 --- a/RhoMicro.CodeAnalysis.slnx +++ b/RhoMicro.CodeAnalysis.slnx @@ -1,4 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -7,30 +47,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/TestApp/Program.cs b/TestApp/Program.cs new file mode 100644 index 0000000..d0a51d6 --- /dev/null +++ b/TestApp/Program.cs @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: MPL-2.0 + +namespace VisitorExample; + +using System.Globalization; +using System.Linq.Expressions; +using System.Reflection; + +using RhoMicro.CodeAnalysis; + + +internal class Program +{ + private static void Main() + { + var program = new StatementList() + { + Statements = + [ + new ExpressionStatement() + { + Expression = new AssignmentExpression() + { + Name = "foo", + Expression = new AdditionExpression() + { + Lhs = new LiteralExpression() { Value = 6 }, + Rhs = new LiteralExpression() { Value = 5 } + } + } + }, + new ExpressionStatement() + { + Expression = new AssignmentExpression(){ + Name = "bar", + Expression =new SubtractionExpression(){ + Lhs = new DivisionExpression() + { + Lhs = new VariableExpression(){Name = "foo"}, + Rhs = new MultiplicationExpression(){ + Lhs = new LiteralExpression(){ Value = 6 }, + Rhs = new LiteralExpression(){ Value = 0.5 } + } + }, + Rhs = new DivisionExpression(){ + Lhs= new LiteralExpression(){ Value = 2}, + Rhs = new LiteralExpression(){Value = 3} + } + } + } + } + ] + }; + + program.Accept(Printer.Instance); + + var result = program.Accept(new Interpreter()); + + Console.WriteLine(result); + + program.Accept(Printer.Instance); + + result = program.Accept(new Interpreter()); + + Console.WriteLine(result); + } +} + +internal sealed class Printer : SyntaxNodeVisitor +{ + private Printer() { } + + public static Printer Instance { get; } = new(); + + public override void OnBeforeVisitAdditionExpression(AdditionExpression target, CancellationToken cancellationToken = default) + => Console.Write('('); + public override void TraverseAdditionExpression(AdditionExpression target, CancellationToken cancellationToken = default) + { + target.Lhs.Accept(this, cancellationToken); + Console.Write(" + "); + target.Rhs.Accept(this, cancellationToken); + } + public override void OnAfterVisitAdditionExpression(AdditionExpression target, CancellationToken cancellationToken = default) + => Console.Write(')'); + + public override void OnBeforeVisitSubtractionExpression(SubtractionExpression target, CancellationToken cancellationToken = default) + => Console.Write('('); + public override void TraverseSubtractionExpression(SubtractionExpression target, CancellationToken cancellationToken = default) + { + target.Lhs.Accept(this, cancellationToken); + Console.Write(" - "); + target.Rhs.Accept(this, cancellationToken); + } + public override void OnAfterVisitSubtractionExpression(SubtractionExpression target, CancellationToken cancellationToken = default) + => Console.Write(')'); + + public override void OnBeforeVisitMultiplicationExpression(MultiplicationExpression target, CancellationToken cancellationToken = default) + => Console.Write('('); + public override void TraverseMultiplicationExpression(MultiplicationExpression target, CancellationToken cancellationToken = default) + { + target.Lhs.Accept(this, cancellationToken); + Console.Write(" * "); + target.Rhs.Accept(this, cancellationToken); + } + public override void OnAfterVisitMultiplicationExpression(MultiplicationExpression target, CancellationToken cancellationToken = default) + => Console.Write(')'); + + public override void OnBeforeVisitDivisionExpression(DivisionExpression target, CancellationToken cancellationToken = default) + => Console.Write('('); + public override void TraverseDivisionExpression(DivisionExpression target, CancellationToken cancellationToken = default) + { + target.Lhs.Accept(this, cancellationToken); + Console.Write(" / "); + target.Rhs.Accept(this, cancellationToken); + } + public override void OnAfterVisitDivisionExpression(DivisionExpression target, CancellationToken cancellationToken = default) + => Console.Write(')'); + + public override void VisitLiteralExpression(LiteralExpression target, CancellationToken cancellationToken = default) + => Console.Write(target.Value.ToString(CultureInfo.InvariantCulture)); + public override void OnAfterVisitExpressionStatement(ExpressionStatement target, CancellationToken cancellationToken = default) + => Console.WriteLine(); + public override void VisitAssignmentExpression(AssignmentExpression target, CancellationToken cancellationToken = default) + { + Console.Write($"{target.Name} = "); + target.Expression.Accept(this, cancellationToken); + } + public override void VisitVariableExpression(VariableExpression target, CancellationToken cancellationToken = default) + => Console.Write(target.Name); +} + +internal sealed class Interpreter : SyntaxNodeVisitor +{ + protected override Double GetDefault() => 0; + + private readonly Dictionary _variables = []; + private Double _lastStatementValue; + + public override Double VisitLiteralExpression(LiteralExpression target, CancellationToken cancellationToken = default) + => target.Value; + public override Double VisitAdditionExpression(AdditionExpression target, CancellationToken cancellationToken = default) + => target.Lhs.Accept(this, cancellationToken) + target.Rhs.Accept(this, cancellationToken); + public override Double VisitSubtractionExpression(SubtractionExpression target, CancellationToken cancellationToken = default) + => target.Lhs.Accept(this, cancellationToken) - target.Rhs.Accept(this, cancellationToken); + public override Double VisitMultiplicationExpression(MultiplicationExpression target, CancellationToken cancellationToken = default) + => target.Lhs.Accept(this, cancellationToken) * target.Rhs.Accept(this, cancellationToken); + public override Double VisitDivisionExpression(DivisionExpression target, CancellationToken cancellationToken = default) + => target.Lhs.Accept(this, cancellationToken) / target.Rhs.Accept(this, cancellationToken); + public override Double VisitAssignmentExpression(AssignmentExpression target, CancellationToken cancellationToken = default) + => _variables[target.Name] = target.Expression.Accept(this, cancellationToken); + public override Double VisitVariableExpression(VariableExpression target, CancellationToken cancellationToken = default) + => _variables.GetValueOrDefault(target.Name); + public override Double VisitExpressionStatement(ExpressionStatement target, CancellationToken cancellationToken = default) + => _lastStatementValue = target.Expression.Accept(this, cancellationToken); + public override Double VisitStatementList(StatementList target, CancellationToken cancellationToken = default) + { + foreach (var statement in target.Statements) + _lastStatementValue = statement.Accept(this, cancellationToken); + + return _lastStatementValue; + } +} + +internal sealed partial class AssignmentExpression : Expression +{ + public required String Name { get; init; } + public required Expression Expression { get; init; } +} + +internal sealed partial class ExpressionStatement : Statement +{ + public required Expression Expression { get; init; } +} + +internal sealed partial class VariableExpression : Expression +{ + public required String Name { get; init; } +} + +internal sealed partial class LiteralExpression : Expression +{ + public required Double Value { get; init; } +} + +internal sealed partial class AdditionExpression : BinaryExpression; +internal sealed partial class SubtractionExpression : BinaryExpression; +internal sealed partial class MultiplicationExpression : BinaryExpression; +internal sealed partial class DivisionExpression : BinaryExpression; + +internal abstract partial class BinaryExpression : Expression +{ + public required Expression Lhs { get; init; } + public required Expression Rhs { get; init; } +} + +internal sealed partial class StatementList : SyntaxNode +{ + public required IEnumerable Statements { get; init; } +} + +internal abstract partial class Expression : SyntaxNode; + +internal abstract partial class Statement : SyntaxNode; + +[GenerateVisitor< + AdditionExpression, + SubtractionExpression, + MultiplicationExpression, + DivisionExpression, + LiteralExpression, + AssignmentExpression, + VariableExpression, + StatementList>, + GenerateVisitor< + ExpressionStatement>] +internal abstract partial class SyntaxNode; diff --git a/TestApp/TestApp.csproj b/TestApp/TestApp.csproj new file mode 100644 index 0000000..668e625 --- /dev/null +++ b/TestApp/TestApp.csproj @@ -0,0 +1,38 @@ + + + + Exe + net9.0 + + + + $(MSBuildProjectName) + $(SolutionName).$(MSBuildProjectName) + + $(MSBuildProjectName.Replace(" ", "_")) + $(SolutionName).$(MSBuildProjectName.Replace(".", "_")) + + $(WarningsAsErrors);NU1605;1591;1573;1712;nullable + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/TestApp/appsettings.json b/TestApp/appsettings.json new file mode 100644 index 0000000..8306e68 --- /dev/null +++ b/TestApp/appsettings.json @@ -0,0 +1,14 @@ +{ + "$schema": "./appsettings.schema.json", + "Settings": { + "ArrayProp": [ + { + "ListProp": [ + { + "IntProp": 0 + } + ] + } + ] + } +} \ No newline at end of file diff --git a/TestApp/appsettings.schema.json b/TestApp/appsettings.schema.json new file mode 100644 index 0000000..bd71716 --- /dev/null +++ b/TestApp/appsettings.schema.json @@ -0,0 +1,9 @@ +{ + "$id": "appsettings_schema", + "properties": { + "Settings": { + "$ref": "./schemata/Settings.json" + } + }, + "additionalProperties": true +} \ No newline at end of file diff --git a/UtilityGenerators.Benchmarks/BlockTerminatorsBenchmark.cs b/UtilityGenerators.Benchmarks/BlockTerminatorsBenchmark.cs index 50e03bd..3eadd57 100644 --- a/UtilityGenerators.Benchmarks/BlockTerminatorsBenchmark.cs +++ b/UtilityGenerators.Benchmarks/BlockTerminatorsBenchmark.cs @@ -55,9 +55,7 @@ public static Boolean LinearSearch(Char c) for(var i = 0; i < _orderedChars.Length; i++) { if(_orderedChars[i] == c) - { return true; - } } return false; @@ -65,34 +63,22 @@ public static Boolean LinearSearch(Char c) public static Boolean LinearSearchUnrolled(Char c) { if(c == '(') - { return true; - } if(c == ')') - { return true; - } if(c == '<') - { return true; - } if(c == '>') - { return true; - } if(c == '{') - { return true; - } if(c == '}') - { return true; - } return false; } @@ -101,34 +87,22 @@ public static Boolean LinearSearchUnrolledConditions(Char c) var result = false; if(c == '(') - { result = true; - } if(c == ')') - { result = true; - } if(c == '<') - { result = true; - } if(c == '>') - { result = true; - } if(c == '{') - { result = true; - } if(c == '}') - { result = true; - } return result; } @@ -222,10 +196,7 @@ public Int32 IsBlockTerminator() { var result = 0; foreach(var c in _characters) - { result += BlockTerminators.IsBlockTerminator(c) ? 1 : 0; - } - return result; } [Benchmark] @@ -233,10 +204,7 @@ public Int32 BinarySearch() { var result = 0; foreach(var c in _characters) - { result += BlockTerminators.BinarySearch(c) ? 1 : 0; - } - return result; } [Benchmark] @@ -244,10 +212,7 @@ public Int32 LinearSearch() { var result = 0; foreach(var c in _characters) - { result += BlockTerminators.LinearSearch(c) ? 1 : 0; - } - return result; } [Benchmark] @@ -255,10 +220,7 @@ public Int32 LinearSearchUnrolled() { var result = 0; foreach(var c in _characters) - { result += BlockTerminators.LinearSearchUnrolled(c) ? 1 : 0; - } - return result; } [Benchmark] @@ -266,10 +228,7 @@ public Int32 LinearSearchUnrolledConditions() { var result = 0; foreach(var c in _characters) - { result += BlockTerminators.LinearSearchUnrolledConditions(c) ? 1 : 0; - } - return result; } [Benchmark] @@ -277,10 +236,7 @@ public Int32 LinearSearchUnrolledShortCircuiting() { var result = 0; foreach(var c in _characters) - { result += BlockTerminators.LinearSearchUnrolledShortCircuiting(c) ? 1 : 0; - } - return result; } [Benchmark] @@ -288,10 +244,7 @@ public Int32 LinearSearchUnrolledPatternMatching() { var result = 0; foreach(var c in _characters) - { result += BlockTerminators.LinearSearchUnrolledPatternMatching(c) ? 1 : 0; - } - return result; } [Benchmark(Baseline = true)] @@ -299,10 +252,7 @@ public Int32 LinearSearchUnrolledTernary() { var result = 0; foreach(var c in _characters) - { result += BlockTerminators.LinearSearchUnrolledTernary(c) ? 1 : 0; - } - return result; } [Benchmark] @@ -310,10 +260,7 @@ public Int32 LinearSearchUnrolledTernary2() { var result = 0; foreach(var c in _characters) - { result += BlockTerminators.LinearSearchUnrolledTernary2(c) ? 1 : 0; - } - return result; } [Benchmark] @@ -321,10 +268,7 @@ public Int32 LinearSearchUnrolledTernary3() { var result = 0; foreach(var c in _characters) - { result += BlockTerminators.LinearSearchUnrolledTernary3(c) ? 1 : 0; - } - return result; } } diff --git a/UtilityGenerators.Benchmarks/ParserMatchBenchmark.cs b/UtilityGenerators.Benchmarks/ParserMatchBenchmark.cs index 87ba1e1..d388644 100644 --- a/UtilityGenerators.Benchmarks/ParserMatchBenchmark.cs +++ b/UtilityGenerators.Benchmarks/ParserMatchBenchmark.cs @@ -22,7 +22,7 @@ public enum TokenType [Orderer(SummaryOrderPolicy.FastestToSlowest)] public class ParserMatchBenchmark { - private TokenType[] _types = []; + private TokenType[] _types; [GlobalSetup] public void GlobalSetup() => _types = [A, B, C, D]; @@ -35,39 +35,19 @@ public Int32 MatchAnyNaive() foreach(var peek in _types) { if(MatchAnyNaive(peek, _types[0])) - { result++; - } - if(MatchAnyNaive(peek, _types[1])) - { result++; - } - if(MatchAnyNaive(peek, _types[2])) - { result++; - } - if(MatchAnyNaive(peek, _types[3])) - { result++; - } - if(MatchAnyNaive(peek, _types[0], _types[1])) - { result++; - } - if(MatchAnyNaive(peek, _types[2], _types[3])) - { result++; - } - if(MatchAnyNaive(peek, _types[0], _types[1], _types[2], _types[3])) - { result++; - } } return result; @@ -80,39 +60,19 @@ public Int32 MatchAnyTernary() foreach(var peek in _types) { if(MatchAnyTernary(peek, _types[0])) - { result++; - } - if(MatchAnyTernary(peek, _types[1])) - { result++; - } - if(MatchAnyTernary(peek, _types[2])) - { result++; - } - if(MatchAnyTernary(peek, _types[3])) - { result++; - } - if(MatchAnyTernary(peek, _types[0], _types[1])) - { result++; - } - if(MatchAnyTernary(peek, _types[2], _types[3])) - { result++; - } - if(MatchAnyTernary(peek, _types[0], _types[1], _types[2], _types[3])) - { result++; - } } return result; @@ -123,9 +83,7 @@ private Boolean MatchAnyNaive(TokenType peek, params ReadOnlySpan typ foreach(var t in types) { if(t == peek) - { return true; - } } return false; diff --git a/UtilityGenerators.Benchmarks/TemplateBenchmark.cs b/UtilityGenerators.Benchmarks/TemplateBenchmark.cs index c6d7634..9949bc8 100644 --- a/UtilityGenerators.Benchmarks/TemplateBenchmark.cs +++ b/UtilityGenerators.Benchmarks/TemplateBenchmark.cs @@ -67,9 +67,7 @@ public void AppendTo(StringBuilder builder) { builder.Append("Surfers gameplay at the bottom Sussy imposter Pibby glitch in real life No").Append('\n'); for(var i = 0; i < 10; i++) - { builder.Append(Parameter); - } } } private readonly record struct StringBuilderTextRender(String Parameter) diff --git a/UtilityGenerators.Benchmarks/UtilityGenerators.Benchmarks.csproj b/UtilityGenerators.Benchmarks/UtilityGenerators.Benchmarks.csproj index d2bd626..48fd120 100644 --- a/UtilityGenerators.Benchmarks/UtilityGenerators.Benchmarks.csproj +++ b/UtilityGenerators.Benchmarks/UtilityGenerators.Benchmarks.csproj @@ -19,10 +19,4 @@ - - - GlobalUsings.cs - - - diff --git a/UtilityGenerators.Dev/UtilityGenerators.Dev.csproj b/UtilityGenerators.Dev/UtilityGenerators.Dev.csproj index 412d87f..ce7bf6e 100644 --- a/UtilityGenerators.Dev/UtilityGenerators.Dev.csproj +++ b/UtilityGenerators.Dev/UtilityGenerators.Dev.csproj @@ -7,7 +7,6 @@ true true ..\dist\dev - $(NoWarn);NU5128 @@ -118,9 +117,7 @@ - - Templating\TemplateAttribute.Model.cs - + @@ -135,4 +132,8 @@ + + + + diff --git a/UtilityGenerators.Tests.E2E/TestAttribute.cs b/UtilityGenerators.Tests.E2E/TestAttribute.cs index 57554ee..d4856c2 100644 --- a/UtilityGenerators.Tests.E2E/TestAttribute.cs +++ b/UtilityGenerators.Tests.E2E/TestAttribute.cs @@ -3,7 +3,7 @@ namespace RhoMicro.CodeAnalysis.Tests.E2E; [GenerateFactory] -sealed partial class TestAttribute +partial class TestAttribute { public Type[] TypeArrayProperty { get; set; } = []; public Type?[] NullableTypeArrayProperty { get; set; } = []; diff --git a/UtilityGenerators.Tests.E2E/UtilityGenerators.Tests.E2E.csproj b/UtilityGenerators.Tests.E2E/UtilityGenerators.Tests.E2E.csproj index 3de922b..f669d64 100644 --- a/UtilityGenerators.Tests.E2E/UtilityGenerators.Tests.E2E.csproj +++ b/UtilityGenerators.Tests.E2E/UtilityGenerators.Tests.E2E.csproj @@ -25,10 +25,4 @@ - - - GlobalUsings.cs - - - diff --git a/UtilityGenerators.Tests/Library/Collections/EquatableDictionaryTests.cs b/UtilityGenerators.Tests/Library/Collections/EquatableDictionaryTests.cs index fbdf87f..7ee79d7 100644 --- a/UtilityGenerators.Tests/Library/Collections/EquatableDictionaryTests.cs +++ b/UtilityGenerators.Tests/Library/Collections/EquatableDictionaryTests.cs @@ -21,7 +21,7 @@ public class EquatableDictionaryTests public void ValueSemantics_VerifyEquality(Dictionary values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var dict1 = factory.CreateDictionary(); var dict2 = factory.CreateDictionary(); @@ -42,7 +42,7 @@ public void ValueSemantics_VerifyEquality(Dictionary values) public void ValueSemantics_VerifyInequality(Int32[] initialKeys, String[] initialValues, Int32[] modifiedKeys, String[] modifiedValues) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var dict1 = factory.CreateDictionary(); var dict2 = factory.CreateDictionary(); @@ -65,7 +65,7 @@ public void ValueSemantics_VerifyInequality(Int32[] initialKeys, String[] initia public void Immutability_AddThrowsAfterSetImmutable(Dictionary values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var dict = factory.CreateDictionary(); foreach(var kvp in values) @@ -85,7 +85,7 @@ public void Immutability_AddThrowsAfterSetImmutable(Dictionary va public void Immutability_ClearThrowsAfterSetImmutable(Dictionary values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var dict = factory.CreateDictionary(); foreach(var kvp in values) @@ -105,7 +105,7 @@ public void Immutability_ClearThrowsAfterSetImmutable(Dictionary public void Immutability_RemoveThrowsAfterSetImmutable(Dictionary values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var dict = factory.CreateDictionary(); foreach(var kvp in values) @@ -125,7 +125,7 @@ public void Immutability_RemoveThrowsAfterSetImmutable(Dictionary public void Mutability_AddSucceedsBeforeSetImmutable(Dictionary values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var dict = factory.CreateDictionary(); // Act @@ -147,7 +147,7 @@ public void Mutability_AddSucceedsBeforeSetImmutable(Dictionary v public void Mutability_ClearSucceedsBeforeSetImmutable(Dictionary values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var dict = factory.CreateDictionary(); foreach(var kvp in values) @@ -167,7 +167,7 @@ public void Mutability_ClearSucceedsBeforeSetImmutable(Dictionary public void Mutability_RemoveSucceedsBeforeSetImmutable(Dictionary values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var dict = factory.CreateDictionary(); foreach(var kvp in values) diff --git a/UtilityGenerators.Tests/Library/Collections/EquatableListTests.cs b/UtilityGenerators.Tests/Library/Collections/EquatableListTests.cs index 5497fac..a326e1f 100644 --- a/UtilityGenerators.Tests/Library/Collections/EquatableListTests.cs +++ b/UtilityGenerators.Tests/Library/Collections/EquatableListTests.cs @@ -22,7 +22,7 @@ public class EquatableListTests public void ValueSemantics_VerifyEquality(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var list1 = factory.CreateList(); var list2 = factory.CreateList(); @@ -45,7 +45,7 @@ public void ValueSemantics_VerifyEquality(Int32[] values) public void ValueSemantics_VerifyInequality(Int32[] initialValues, Int32[] modifiedValues) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var list1 = factory.CreateList(); var list2 = factory.CreateList(); @@ -68,7 +68,7 @@ public void ValueSemantics_VerifyInequality(Int32[] initialValues, Int32[] modif public void Immutability_AddThrowsAfterSetImmutable(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateList(); foreach(var value in values) @@ -88,7 +88,7 @@ public void Immutability_AddThrowsAfterSetImmutable(Int32[] values) public void Immutability_RemoveThrowsAfterSetImmutable(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateList(); foreach(var value in values) @@ -108,7 +108,7 @@ public void Immutability_RemoveThrowsAfterSetImmutable(Int32[] values) public void Immutability_ClearThrowsAfterSetImmutable(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateList(); foreach(var value in values) @@ -127,7 +127,7 @@ public void Immutability_ClearThrowsAfterSetImmutable(Int32[] values) public void Immutability_InsertThrowsAfterSetImmutable(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateList(); foreach(var value in values) @@ -147,12 +147,10 @@ public void Immutability_InsertThrowsAfterSetImmutable(Int32[] values) public void Immutability_RemoveAtThrowsAfterSetImmutable(Int32[] values) { if(values.Length == 0) - { return; - } // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateList(); foreach(var value in values) @@ -172,7 +170,7 @@ public void Immutability_RemoveAtThrowsAfterSetImmutable(Int32[] values) public void Mutability_AddSucceedsBeforeSetImmutable(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateList(); // Act @@ -193,12 +191,10 @@ public void Mutability_AddSucceedsBeforeSetImmutable(Int32[] values) public void Mutability_RemoveSucceedsBeforeSetImmutable(Int32[] values) { if(values.Length == 0) - { return; - } // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateList(); foreach(var value in values) @@ -218,7 +214,7 @@ public void Mutability_RemoveSucceedsBeforeSetImmutable(Int32[] values) public void Mutability_ClearSucceedsBeforeSetImmutable(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateList(); foreach(var value in values) @@ -237,7 +233,7 @@ public void Mutability_ClearSucceedsBeforeSetImmutable(Int32[] values) public void Mutability_InsertSucceedsBeforeSetImmutable(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateList(); foreach(var value in values) @@ -257,12 +253,10 @@ public void Mutability_InsertSucceedsBeforeSetImmutable(Int32[] values) public void Mutability_RemoveAtSucceedsBeforeSetImmutable(Int32[] values) { if(values.Length == 0) - { return; - } // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateList(); foreach(var value in values) diff --git a/UtilityGenerators.Tests/Library/Collections/EquatableSetTests.cs b/UtilityGenerators.Tests/Library/Collections/EquatableSetTests.cs index e8cf7e3..6af59dc 100644 --- a/UtilityGenerators.Tests/Library/Collections/EquatableSetTests.cs +++ b/UtilityGenerators.Tests/Library/Collections/EquatableSetTests.cs @@ -22,7 +22,7 @@ public class EquatableSetTests public void ValueSemantics_VerifyEquality(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var set1 = factory.CreateSet(); var set2 = factory.CreateSet(); @@ -45,7 +45,7 @@ public void ValueSemantics_VerifyEquality(Int32[] values) public void ValueSemantics_VerifyInequality(Int32[] initialValues, Int32[] modifiedValues) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var set1 = factory.CreateSet(); var set2 = factory.CreateSet(); @@ -68,7 +68,7 @@ public void ValueSemantics_VerifyInequality(Int32[] initialValues, Int32[] modif public void Immutability_AddThrowsAfterSetImmutable(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var set = factory.CreateSet(); foreach(var value in values) @@ -88,7 +88,7 @@ public void Immutability_AddThrowsAfterSetImmutable(Int32[] values) public void Immutability_ClearThrowsAfterSetImmutable(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var set = factory.CreateSet(); foreach(var value in values) @@ -108,7 +108,7 @@ public void Immutability_ClearThrowsAfterSetImmutable(Int32[] values) public void Immutability_ExceptWithThrowsAfterSetImmutable(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var set = factory.CreateSet(); foreach(var value in values) @@ -128,7 +128,7 @@ public void Immutability_ExceptWithThrowsAfterSetImmutable(Int32[] values) public void Immutability_IntersectWithThrowsAfterSetImmutable(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var set = factory.CreateSet(); foreach(var value in values) @@ -148,7 +148,7 @@ public void Immutability_IntersectWithThrowsAfterSetImmutable(Int32[] values) public void Immutability_SymmetricExceptWithThrowsAfterSetImmutable(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var set = factory.CreateSet(); foreach(var value in values) @@ -168,7 +168,7 @@ public void Immutability_SymmetricExceptWithThrowsAfterSetImmutable(Int32[] valu public void Immutability_UnionWithThrowsAfterSetImmutable(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var set = factory.CreateSet(); foreach(var value in values) @@ -188,7 +188,7 @@ public void Immutability_UnionWithThrowsAfterSetImmutable(Int32[] values) public void Mutability_AddSucceedsBeforeSetImmutable(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var set = factory.CreateSet(); // Act @@ -209,7 +209,7 @@ public void Mutability_AddSucceedsBeforeSetImmutable(Int32[] values) public void Mutability_ClearSucceedsBeforeSetImmutable(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var set = factory.CreateSet(); foreach(var value in values) @@ -229,7 +229,7 @@ public void Mutability_ClearSucceedsBeforeSetImmutable(Int32[] values) public void Mutability_ExceptWithSucceedsBeforeSetImmutable(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var set = factory.CreateSet(); foreach(var value in values) @@ -249,7 +249,7 @@ public void Mutability_ExceptWithSucceedsBeforeSetImmutable(Int32[] values) public void Mutability_IntersectWithSucceedsBeforeSetImmutable(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var set = factory.CreateSet(); foreach(var value in values) @@ -269,7 +269,7 @@ public void Mutability_IntersectWithSucceedsBeforeSetImmutable(Int32[] values) public void Mutability_SymmetricExceptWithSucceedsBeforeSetImmutable(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var set = factory.CreateSet(); foreach(var value in values) @@ -291,7 +291,7 @@ public void Mutability_SymmetricExceptWithSucceedsBeforeSetImmutable(Int32[] val public void Mutability_UnionWithSucceedsBeforeSetImmutable(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var set = factory.CreateSet(); foreach(var value in values) diff --git a/UtilityGenerators.Tests/Library/Collections/LazyEquatableDictionaryTests.cs b/UtilityGenerators.Tests/Library/Collections/LazyEquatableDictionaryTests.cs index a78bb16..9dc8614 100644 --- a/UtilityGenerators.Tests/Library/Collections/LazyEquatableDictionaryTests.cs +++ b/UtilityGenerators.Tests/Library/Collections/LazyEquatableDictionaryTests.cs @@ -21,7 +21,7 @@ public class LazyEquatableDictionaryTests public void ValueSemantics_VerifyEquality(Dictionary values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var dict1 = factory.CreateLazyDictionary(); var dict2 = factory.CreateLazyDictionary(); @@ -42,7 +42,7 @@ public void ValueSemantics_VerifyEquality(Dictionary values) public void ValueSemantics_VerifyInequality(Int32[] initialKeys, String[] initialValues, Int32[] modifiedKeys, String[] modifiedValues) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var dict1 = factory.CreateLazyDictionary(); var dict2 = factory.CreateLazyDictionary(); @@ -65,7 +65,7 @@ public void ValueSemantics_VerifyInequality(Int32[] initialKeys, String[] initia public void Immutability_AddThrowsAfterSetImmutable(Dictionary values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var dict = factory.CreateLazyDictionary(); foreach(var kvp in values) @@ -85,7 +85,7 @@ public void Immutability_AddThrowsAfterSetImmutable(Dictionary va public void Immutability_ClearThrowsAfterSetImmutable(Dictionary values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var dict = factory.CreateLazyDictionary(); foreach(var kvp in values) @@ -105,7 +105,7 @@ public void Immutability_ClearThrowsAfterSetImmutable(Dictionary public void Immutability_RemoveThrowsAfterSetImmutable(Dictionary values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var dict = factory.CreateLazyDictionary(); foreach(var kvp in values) @@ -125,7 +125,7 @@ public void Immutability_RemoveThrowsAfterSetImmutable(Dictionary public void Mutability_AddSucceedsBeforeSetImmutable(Dictionary values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var dict = factory.CreateLazyDictionary(); // Act @@ -147,7 +147,7 @@ public void Mutability_AddSucceedsBeforeSetImmutable(Dictionary v public void Mutability_ClearSucceedsBeforeSetImmutable(Dictionary values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var dict = factory.CreateLazyDictionary(); foreach(var kvp in values) @@ -167,7 +167,7 @@ public void Mutability_ClearSucceedsBeforeSetImmutable(Dictionary public void Mutability_RemoveSucceedsBeforeSetImmutable(Dictionary values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var dict = factory.CreateLazyDictionary(); foreach(var kvp in values) diff --git a/UtilityGenerators.Tests/Library/Collections/LazyEquatableListTests.cs b/UtilityGenerators.Tests/Library/Collections/LazyEquatableListTests.cs index d004939..41e052f 100644 --- a/UtilityGenerators.Tests/Library/Collections/LazyEquatableListTests.cs +++ b/UtilityGenerators.Tests/Library/Collections/LazyEquatableListTests.cs @@ -22,7 +22,7 @@ public class LazyEquatableListTests public void ValueSemantics_VerifyEquality(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var list1 = factory.CreateLazyList(); var list2 = factory.CreateLazyList(); @@ -45,7 +45,7 @@ public void ValueSemantics_VerifyEquality(Int32[] values) public void ValueSemantics_VerifyInequality(Int32[] initialValues, Int32[] modifiedValues) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var list1 = factory.CreateLazyList(); var list2 = factory.CreateLazyList(); @@ -68,7 +68,7 @@ public void ValueSemantics_VerifyInequality(Int32[] initialValues, Int32[] modif public void Immutability_AddThrowsAfterSetImmutable(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateLazyList(); foreach(var value in values) @@ -88,7 +88,7 @@ public void Immutability_AddThrowsAfterSetImmutable(Int32[] values) public void Immutability_RemoveThrowsAfterSetImmutable(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateLazyList(); foreach(var value in values) @@ -108,7 +108,7 @@ public void Immutability_RemoveThrowsAfterSetImmutable(Int32[] values) public void Immutability_ClearThrowsAfterSetImmutable(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateLazyList(); foreach(var value in values) @@ -127,7 +127,7 @@ public void Immutability_ClearThrowsAfterSetImmutable(Int32[] values) public void Immutability_InsertThrowsAfterSetImmutable(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateLazyList(); foreach(var value in values) @@ -147,12 +147,10 @@ public void Immutability_InsertThrowsAfterSetImmutable(Int32[] values) public void Immutability_RemoveAtThrowsAfterSetImmutable(Int32[] values) { if(values.Length == 0) - { return; - } // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateLazyList(); foreach(var value in values) @@ -172,7 +170,7 @@ public void Immutability_RemoveAtThrowsAfterSetImmutable(Int32[] values) public void Mutability_AddSucceedsBeforeSetImmutable(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateLazyList(); // Act @@ -193,12 +191,10 @@ public void Mutability_AddSucceedsBeforeSetImmutable(Int32[] values) public void Mutability_RemoveSucceedsBeforeSetImmutable(Int32[] values) { if(values.Length == 0) - { return; - } // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateLazyList(); foreach(var value in values) @@ -218,7 +214,7 @@ public void Mutability_RemoveSucceedsBeforeSetImmutable(Int32[] values) public void Mutability_ClearSucceedsBeforeSetImmutable(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateLazyList(); foreach(var value in values) @@ -237,7 +233,7 @@ public void Mutability_ClearSucceedsBeforeSetImmutable(Int32[] values) public void Mutability_InsertSucceedsBeforeSetImmutable(Int32[] values) { // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateLazyList(); foreach(var value in values) @@ -257,12 +253,10 @@ public void Mutability_InsertSucceedsBeforeSetImmutable(Int32[] values) public void Mutability_RemoveAtSucceedsBeforeSetImmutable(Int32[] values) { if(values.Length == 0) - { return; - } // Arrange - using var factory = EquatableCollectionFactory.CreateDefault(); + var factory = EquatableCollectionFactory.CreateDefault(); var list = factory.CreateLazyList(); foreach(var value in values) diff --git a/UtilityGenerators.Tests/Library/Text/Templating/TemplateRendererTests.cs b/UtilityGenerators.Tests/Library/Text/Templating/TemplateRendererTests.cs index 33633b7..7d8706c 100644 --- a/UtilityGenerators.Tests/Library/Text/Templating/TemplateRendererTests.cs +++ b/UtilityGenerators.Tests/Library/Text/Templating/TemplateRendererTests.cs @@ -34,23 +34,17 @@ public void IndentsCorrectly(String[] header, String[] body, String[] footer, St // Act foreach(var h in header) - { renderer.Render(h); - } renderer.Indent(indentation); foreach(var b in body) - { renderer.Render(b); - } renderer.Detent(indentation.Length); foreach(var f in footer) - { renderer.Render(f); - } var actual = renderer.ToString(); diff --git a/UtilityGenerators.Tests/Templating/LexerTests.cs b/UtilityGenerators.Tests/Templating/LexerTests.cs index 3ddcf57..d3d18f5 100644 --- a/UtilityGenerators.Tests/Templating/LexerTests.cs +++ b/UtilityGenerators.Tests/Templating/LexerTests.cs @@ -123,9 +123,7 @@ public void LexerRecognizesRequiredQuotes(String sourceText, Int32 expected) // Act using(var context = ModelCreationContext.CreateDefault(TestContext.Current.CancellationToken)) - { actual = Lexer.Scan(templateString, newlineLength: 1, in context).RequiredQuotes; - } // Assert Assert.Equal(expected, actual); diff --git a/UtilityGenerators.Tests/Templating/ParserTests.cs b/UtilityGenerators.Tests/Templating/ParserTests.cs index eef43d6..ffe9745 100644 --- a/UtilityGenerators.Tests/Templating/ParserTests.cs +++ b/UtilityGenerators.Tests/Templating/ParserTests.cs @@ -38,12 +38,10 @@ .MultiLineRawStringLiteralToken or ScanResult scanResult; using (var ctx = ModelCreationContext.CreateDefault(TestContext.Current.CancellationToken)) - { scanResult = Lexer.Scan( - templateString, - newlineLength, - in ctx); - } + templateString, + newlineLength, + in ctx); var nullableExpectedSyntax = syntaxFactory?.Invoke(new(scanResult.Tokens)); if (nullableExpectedSyntax is { } s) @@ -60,11 +58,9 @@ .MultiLineRawStringLiteralToken or // Act using (var context = ModelCreationContext.CreateDefault(TestContext.Current.CancellationToken)) - { (actualSyntax, _, actualDiagnostics) = Parser.Parse( - scanResult, - in context); - } + scanResult, + in context); // Assert if (nullableExpectedSyntax is { } expectedSyntax) diff --git a/UtilityGenerators.Tests/Templating/TemplateStringTests.cs b/UtilityGenerators.Tests/Templating/TemplateStringTests.cs index c084232..5fd3579 100644 --- a/UtilityGenerators.Tests/Templating/TemplateStringTests.cs +++ b/UtilityGenerators.Tests/Templating/TemplateStringTests.cs @@ -316,8 +316,8 @@ public void SourceTextIsCorrectlyCreated(String sourceText, Int32 startLine, Int { // Arrange var token = CSharpSyntaxTree - .ParseText(sourceText, cancellationToken: TestContext.Current.CancellationToken) - .GetRoot(TestContext.Current.CancellationToken) + .ParseText(sourceText) + .GetRoot() .DescendantTokens(_ => true) .Single(t => t.RawKind is (Int32)SyntaxKind.StringLiteralToken or diff --git a/UtilityGenerators.Tests/Templating/TestHelpers.cs b/UtilityGenerators.Tests/Templating/TestHelpers.cs index de870f7..c2c22b8 100644 --- a/UtilityGenerators.Tests/Templating/TestHelpers.cs +++ b/UtilityGenerators.Tests/Templating/TestHelpers.cs @@ -68,9 +68,7 @@ public static String GetDiff(String expected, String? actual, Int32 columnWidth) static String getFormattedText(DiffPiece? line, Int32 columnWidth) { if(line?.Text == null) - { return "".PadRight(columnWidth); // Blank space for missing lines - } // Return the line's text truncated or padded to fit the column width return line.Text.PadRight(columnWidth)[..columnWidth]; diff --git a/UtilityGenerators.Tests/TestBase.cs b/UtilityGenerators.Tests/TestBase.cs index d7a8a86..fa6f98d 100644 --- a/UtilityGenerators.Tests/TestBase.cs +++ b/UtilityGenerators.Tests/TestBase.cs @@ -41,9 +41,7 @@ protected void TestFactory(String source, String assertion, CancellationToken ct var runResult = RunGenerator(ref compilation, ct); if(runResult.Diagnostics.Where(d => d.IsWarningAsError || d.Severity is DiagnosticSeverity.Error).Any()) - { FailDiagnostics("Generator run produced diagnostics:", runResult.Diagnostics); - } } protected GeneratorDriverRunResult RunGenerator(ref Compilation compilation, CancellationToken ct) { @@ -61,9 +59,7 @@ protected GeneratorDriverRunResult RunGenerator(ref Compilation compilation, Can cancellationToken: ct); if(!diagnostics.IsEmpty) - { FailDiagnostics("Generator produced diagnostics:", diagnostics); - } var aggregateDiagnostics = compilation .GetDiagnostics(ct) @@ -84,9 +80,7 @@ protected GeneratorDriverRunResult RunGenerator(ref Compilation compilation, Can _ = messageBuilder.Append(diagnosticGroup.diagnostics.Length).Append(" diagnostics in ").AppendLine(diagnosticGroup.key); foreach(var diagnostic in diagnosticGroup.diagnostics) - { _ = messageBuilder.AppendLine(diagnostic.ToString().Split(".g.cs").Last()); - } if(trees.TryGetValue(diagnosticGroup.key, out var tree)) { @@ -118,7 +112,7 @@ private static void FailDiagnostics(String message, IEnumerable diag .OrderBy(d => d.Location.GetLineSpan().StartLinePosition.Line) .ThenBy(d => d.Location.GetLineSpan().StartLinePosition.Character))}"); } - private static Int32 _sourceIndex; + private static Int32 _sourceIndex = 0; protected CSharpCompilation CreateCompilation(CancellationToken ct, params String[] sources) { var options = CreateCompilationOptions(); diff --git a/UtilityGenerators.Tests/UtilityGenerators.Tests.csproj b/UtilityGenerators.Tests/UtilityGenerators.Tests.csproj index 63eadc3..af0bed4 100644 --- a/UtilityGenerators.Tests/UtilityGenerators.Tests.csproj +++ b/UtilityGenerators.Tests/UtilityGenerators.Tests.csproj @@ -1,15 +1,14 @@ - - enable - enable - Exe - RhoMicro.CodeAnalysis.UtilityGenerators.Tests - RhoMicro.CodeAnalysis.UtilityGenerators.Tests - net9.0 - false - $(NoWarn);CS1591 - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/UtilityGenerators/AttributeFactory/AttributeFactoryGenerator.cs b/UtilityGenerators/AttributeFactory/AttributeFactoryGenerator.cs index 74d7e43..2c9de73 100644 --- a/UtilityGenerators/AttributeFactory/AttributeFactoryGenerator.cs +++ b/UtilityGenerators/AttributeFactory/AttributeFactoryGenerator.cs @@ -52,9 +52,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) } if(attributeModel is null) - { return null; - } using var modelCreationContext = ModelCreationContext.CreateDefault(ct); var model = InitializationMethodModel.Create(target, attributeModel.Value, in modelCreationContext); @@ -93,9 +91,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) ct.ThrowIfCancellationRequested(); if(ctx is not { Attributes: [{ } attribute], TargetSymbol: INamedTypeSymbol target }) - { return null; - } using var modelCreationContext = ModelCreationContext.CreateDefault(ct); var model = AttributeFactoryModel.Create(target, attribute, in modelCreationContext); @@ -208,9 +204,7 @@ private static void AppendPropertyIdType(in SourceBuildingContext ctx) .AppendCore(mappedProperty.Name); if(i < ctx.Model.MappedProperties.Count - 1 || ctx.Model.UnmappedProperties.Count > 0) - { ctx.SourceBuilder.Append(',').AppendLineCore(); - } } _ = ctx.SourceBuilder.CloseBlock() @@ -232,9 +226,7 @@ private static void AppendPropertyIdType(in SourceBuildingContext ctx) .AppendCore(unmappedProperty.Name); if(i < ctx.Model.UnmappedProperties.Count - 1) - { ctx.SourceBuilder.Append(',').AppendLineCore(); - } } ctx.SourceBuilder @@ -380,9 +372,7 @@ private static void AppendConstructorAccessorFieldsAndProperties(in SourceBuildi ctx.ThrowIfCancellationRequested(); if(j > 0) - { ctx.SourceBuilder.Append(',').AppendLineCore(); - } var parameterIndex = ctor.Mappings[ctx.Model.MappedProperties[j].Name]?.ParameterIndex ?? -1; var parameterIndexString = parameterIndex.ToString(); @@ -414,9 +404,7 @@ private static void AppendConstructorAccessorFieldsAndProperties(in SourceBuildi var ctor = ctx.Model.Constructors[i]; if(ctor.Parameters.Count == 0) - { continue; - } ctx.SourceBuilder.AppendCore('['); @@ -425,9 +413,7 @@ private static void AppendConstructorAccessorFieldsAndProperties(in SourceBuildi ctx.ThrowIfCancellationRequested(); if(j > 0) - { ctx.SourceBuilder.AppendCore(", "); - } ctx.SourceBuilder.AppendCore(ctor.Parameters[j].Pattern); } @@ -1189,17 +1175,13 @@ private static void AppendPropertyAccessorFieldsAndProperties(in SourceBuildingC var property = ctx.Model.MappedProperties[i]; if(i > 0) - { ctx.SourceBuilder.Append(',').AppendLineCore(); - } ctx.SourceBuilder.Append("nameof(").Append(property.Name).AppendCore(')'); } if(ctx.Model.UnmappedProperties.Count > 0) - { ctx.SourceBuilder.Append(',').AppendLineCore(); - } ctx.SourceBuilder.CloseBlockCore(); } @@ -1215,9 +1197,7 @@ private static void AppendPropertyAccessorFieldsAndProperties(in SourceBuildingC var property = ctx.Model.UnmappedProperties[i]; if(i > 0) - { ctx.SourceBuilder.Append(',').AppendLineCore(); - } ctx.SourceBuilder.Append("nameof(").Append(property.Name).AppendCore(')'); } @@ -1250,9 +1230,7 @@ void appendSettablePropertyNames(in SourceBuildingContext ctx, IList 0) - { ctx.SourceBuilder.Append(',').AppendLineCore(); - } ctx.SourceBuilder.Append(ctx.Model.AttributeModel.PropertyIdTypeName).Append(".").AppendCore(property.Name); @@ -2049,9 +2027,7 @@ private static void AppendModelType(in SourceBuildingContext ctx) AppendModelProperties(in ctx); if(!ctx.Model.IsEquatable) - { AppendEquality(in ctx, $"{ctx.DisplayString}.{ctx.Model.AttributeModel.ModelTypeName}"); - } ctx.SourceBuilder.CloseBlockCore(); } @@ -2076,9 +2052,7 @@ private static void OpenModel(in SourceBuildingContext ctx) // If not equatable, we implement NonEquatable, it provides the impl for IEquatable. if(!ctx.Model.IsEquatable) - { ctx.SourceBuilder.Append(" : IEquatable<").Append(ctx.Model.AttributeModel.ModelTypeName).AppendCore('>'); - } ctx.SourceBuilder.OpenBracesBlock() .Append(accessibility).Append(ctx.Model.AttributeModel.ModelTypeName).Append("()") @@ -2090,9 +2064,7 @@ private static void AppendModelDefaultInstance(in SourceBuildingContext ctx) ctx.CancellationToken.ThrowIfCancellationRequested(); if(!ctx.Model.AttributeModel.GenerateDefaultInstance) - { return; - } ctx.SourceBuilder .Comment @@ -2176,9 +2148,7 @@ static void appendFactory(in SourceBuildingContext ctx, InitializationMethodMode ctx.ThrowIfCancellationRequested(); if(i != 0) - { ctx.SourceBuilder.AppendCore(", "); - } var parameter = initializationMethod.Parameters[i]; ctx.SourceBuilder.Append(parameter.Name).Append(": state.").AppendCore(parameter.PropertyName); @@ -2187,9 +2157,7 @@ static void appendFactory(in SourceBuildingContext ctx, InitializationMethodMode if(initializationMethod.CancellationTokenParameterName is { } name) { if(i != 0) - { ctx.SourceBuilder.AppendCore(", "); - } ctx.SourceBuilder.Append(name).AppendCore(": cancellationToken"); } @@ -2225,9 +2193,7 @@ private static void AppendModelApplyConstructorArguments(in SourceBuildingContex var kind = property.Type.Kind; if(kind.HasAnyFlagFast(AttributeParameterTypeKind.ValueType, AttributeParameterTypeKind.Enum)) - { ctx.SourceBuilder.AppendCore(".Value"); - } ctx.SourceBuilder.Append(';').AppendLineCore(); } @@ -2264,9 +2230,7 @@ static void append(in SourceBuildingContext ctx, IList properties ctx.ThrowIfCancellationRequested(); if(!property.HasSetter) - { continue; - } ctx.SourceBuilder .Append("case nameof(").Append(ctx.DisplayString).Append('.').Append(property.Name).Append("):") @@ -2275,9 +2239,7 @@ static void append(in SourceBuildingContext ctx, IList properties .Indent().Append(property.Name).AppendCore(" = value"); if(property.Type.Kind.HasAnyFlagFast(AttributeParameterTypeKind.ValueType, AttributeParameterTypeKind.Enum, AttributeParameterTypeKind.TypeArray, AttributeParameterTypeKind.NullableTypeArray)) - { ctx.SourceBuilder.AppendCore(".Value"); - } ctx.SourceBuilder.AppendLine(';').Detent() .AppendLine("break;") @@ -2304,9 +2266,7 @@ static void append(in SourceBuildingContext ctx, IList properties ctx.ThrowIfCancellationRequested(); if(property is { HasSetter: false, Mappings: [] }) - { continue; - } ctx.SourceBuilder.Comment .OpenSummary() @@ -2341,9 +2301,7 @@ private static void AppendFactoryStateTypes(in SourceBuildingContext ctx) ctx.ThrowIfCancellationRequested(); if(i != 0) - { ctx.SourceBuilder.AppendCore(", "); - } var parameter = initMethod.Parameters[i]; ctx.SourceBuilder.Append(parameter.Type).Append(' ').AppendCore(parameter.PropertyName); @@ -2556,9 +2514,7 @@ static void appendExtension(in SourceBuildingContext ctx, InitializationMethodMo .Append(ctx.DisplayString).Append('.').AppendCore(ctx.Model.AttributeModel.ModelTypeName); if(!ctx.Model.AttributeModel.GenerateModelTypeAsStruct) - { ctx.SourceBuilder.AppendCore('?'); - } ctx.SourceBuilder .Append(" model, bool checkType = true, global::System.Threading.CancellationToken cancellationToken = default)") @@ -2569,9 +2525,7 @@ static void appendExtension(in SourceBuildingContext ctx, InitializationMethodMo .Append("model = ").Append(ctx.DisplayString).Append('.').Append(ctx.Model.AttributeModel.ModelTypeName).AppendCore(".Create(data"); if(initializationMethod is { Parameters.Count: > 0 }) - { ctx.SourceBuilder.AppendCore(", in state"); - } ctx.SourceBuilder .Append(", cancellationToken);") @@ -2616,9 +2570,7 @@ static void appendExtension(in SourceBuildingContext ctx, InitializationMethodMo .AppendCore(" data"); if(initializationMethod is { Parameters.Count: > 0 }) - { ctx.SourceBuilder.Append(", in ").Append(initializationMethod.StateTypeDisplayString).AppendCore(" state"); - } ctx.SourceBuilder.Append(", global::System.Threading.CancellationToken cancellationToken = default)") .OpenBracesBlock() @@ -2626,9 +2578,7 @@ static void appendExtension(in SourceBuildingContext ctx, InitializationMethodMo .Append("return ").Append(ctx.DisplayString).Append('.').Append(ctx.Model.AttributeModel.ModelTypeName).AppendCore(".Create(data"); if(initializationMethod is { Parameters.Count: > 0 }) - { ctx.SourceBuilder.AppendCore(", in state"); - } ctx.SourceBuilder .Append(", cancellationToken);") @@ -2681,9 +2631,7 @@ static void appendExtension(in SourceBuildingContext ctx, InitializationMethodMo .AppendCore("(this global::System.Collections.Immutable.ImmutableArray data"); if(initializationMethod is { Parameters.Count: > 0 }) - { ctx.SourceBuilder.Append(", ").Append(initializationMethod.StateTypeDisplayString).AppendCore(" state"); - } ctx.SourceBuilder.Append(", global::System.Threading.CancellationToken cancellationToken = default)") .OpenBracesBlock() @@ -2695,9 +2643,7 @@ static void appendExtension(in SourceBuildingContext ctx, InitializationMethodMo .Indent().Append("yield return Get").Append(ctx.Model.Signature.Name).AppendCore("Model(datum"); if(initializationMethod is { Parameters.Count: > 0 }) - { ctx.SourceBuilder.AppendCore(", in state"); - } ctx.SourceBuilder.Append(", cancellationToken);").Detent() .CloseBlock() @@ -2730,9 +2676,7 @@ static void appendExtension(in SourceBuildingContext ctx, InitializationMethodMo .AppendCore("(this global::System.Collections.Generic.IEnumerable data"); if(initializationMethod is { Parameters.Count: > 0 }) - { ctx.SourceBuilder.Append(", ").Append(initializationMethod.StateTypeDisplayString).AppendCore(" state"); - } ctx.SourceBuilder.Append(", global::System.Threading.CancellationToken cancellationToken = default)") .OpenBracesBlock() @@ -2744,9 +2688,7 @@ static void appendExtension(in SourceBuildingContext ctx, InitializationMethodMo .Indent().Append("yield return Get").Append(ctx.Model.Signature.Name).AppendCore("Model(datum"); if(initializationMethod is { Parameters.Count: > 0 }) - { ctx.SourceBuilder.AppendCore(", in state"); - } ctx.SourceBuilder.Append(", cancellationToken);").Detent() .CloseBlock() diff --git a/UtilityGenerators/AttributeFactory/AttributeFactoryModel.cs b/UtilityGenerators/AttributeFactory/AttributeFactoryModel.cs index 4468239..00c8125 100644 --- a/UtilityGenerators/AttributeFactory/AttributeFactoryModel.cs +++ b/UtilityGenerators/AttributeFactory/AttributeFactoryModel.cs @@ -140,9 +140,7 @@ private static void GetConstructorModels(INamedTypeSymbol target, EquatableList< ctx.ThrowIfCancellationRequested(); if(mapping is { } m) - { propertyMappings[m.PropertyName].Add(m); - } } } } diff --git a/UtilityGenerators/AttributeFactory/ConstructorModel.cs b/UtilityGenerators/AttributeFactory/ConstructorModel.cs index 12c9be3..3671263 100644 --- a/UtilityGenerators/AttributeFactory/ConstructorModel.cs +++ b/UtilityGenerators/AttributeFactory/ConstructorModel.cs @@ -39,9 +39,7 @@ public static ConstructorModel Create( var model = ParameterModel.Create(parameter, parameterIndex, in ctx); if(model is { MappedProperty: { } propertyName, Name: { } parameterName }) - { mappings[propertyName] = new ParameterMapping(index, parameterIndex, ParameterName: parameterName, PropertyName: propertyName); - } parameters.Add(model); } @@ -62,29 +60,21 @@ public void AppendConstructorExpressionComment(in SourceBuildingContext ctx, Int ctx.ThrowIfCancellationRequested(); if(i > 0) - { ctx.SourceBuilder.AppendCore(", "); - } var param = Parameters[i]; _ = ctx.SourceBuilder.Comment.SeeCRef(param.Type.ElementDisplayString); if(param.Type.Kind.HasAnyFlagFast(AttributeParameterTypeKind.Array)) - { ctx.SourceBuilder.AppendCore("[]"); - } ctx.SourceBuilder.AppendCore(' '); if(( i == highlightIndex || highlightIndex == -1 ) && param.MappedProperty is { } mappedProperty) - { ctx.SourceBuilder.Comment.OpenEmphasis().Append(param.Name).Append("->").Append(mappedProperty).CloseBlockCore(); - } else - { ctx.SourceBuilder.AppendCore(param.Name); - } } ctx.SourceBuilder.AppendCore(')'); @@ -102,29 +92,21 @@ public void AppendConstructorExpression(in SourceBuildingContext ctx, Int32 high ctx.ThrowIfCancellationRequested(); if(i > 0) - { ctx.SourceBuilder.AppendCore(", "); - } var param = Parameters[i]; _ = ctx.SourceBuilder.Append(param.Type.ElementDisplayString); if(param.Type.Kind.HasAnyFlagFast(AttributeParameterTypeKind.Array)) - { ctx.SourceBuilder.AppendCore("[]"); - } ctx.SourceBuilder.AppendCore(' '); if(( i == highlightIndex || highlightIndex == -1 ) && param.MappedProperty is not null) - { ctx.SourceBuilder.Append('<').Append(param.Name).AppendCore('>'); - } else - { ctx.SourceBuilder.AppendCore(param.Name); - } } ctx.SourceBuilder.AppendCore(')'); diff --git a/UtilityGenerators/AttributeFactory/InitializationMethodModel.cs b/UtilityGenerators/AttributeFactory/InitializationMethodModel.cs index f1325dd..5e3b99a 100644 --- a/UtilityGenerators/AttributeFactory/InitializationMethodModel.cs +++ b/UtilityGenerators/AttributeFactory/InitializationMethodModel.cs @@ -67,9 +67,7 @@ private static void GetParameters(IMethodSymbol method, IList Mappings) { - public static PropertyModel Create(IPropertySymbol property, - LazyEquatableDictionary> - propertyMappings, in ModelCreationContext ctx) + [TypeSymbolPattern(typeof(String[]))] + private static partial Boolean IsStringArray(ITypeSymbol? type); + public static PropertyModel Create(IPropertySymbol property, LazyEquatableDictionary> propertyMappings, in ModelCreationContext ctx) { ctx.ThrowIfCancellationRequested(); @@ -28,16 +30,13 @@ public static PropertyModel Create(IPropertySymbol property, String? defaultValueExpression = null; - foreach (var attribute in property.GetAttributes()) + foreach(var attribute in property.GetAttributes()) { ctx.ThrowIfCancellationRequested(); - if (attribute.IsDefaultValueAttribute() && attribute.ConstructorArguments is [{ } defaultValue]) + if(attribute.IsDefaultValueAttribute() && attribute.ConstructorArguments is [{ } defaultValue]) { - defaultValueExpression = defaultValue.Type is IArrayTypeSymbol - { - ElementType.SpecialType: SpecialType.System_String - } + defaultValueExpression = IsStringArray(defaultValue.Type) ? $"[{String.Join(", ", defaultValue.Values.Select(c => c.ToCSharpString()))}]" : defaultValue.ToCSharpString(); } @@ -55,6 +54,5 @@ public static PropertyModel Create(IPropertySymbol property, return result; } - - public override String ToString() => $"{Name}{{g{(HasSetter ? "/s" : "")}}}<-[{String.Join(", ", Mappings)}]"; + public override String ToString() => $"{Name}{{g{( HasSetter ? "/s" : "" )}}}<-[{String.Join(", ", Mappings)}]"; } diff --git a/UtilityGenerators/GlobalUsings.cs b/UtilityGenerators/GlobalUsings.cs index 8f73aad..3eb00db 100644 --- a/UtilityGenerators/GlobalUsings.cs +++ b/UtilityGenerators/GlobalUsings.cs @@ -2,4 +2,3 @@ global using System.Diagnostics; global using System.Diagnostics.CodeAnalysis; -global using static RhoMicro.CodeAnalysis.Library.Text.Templating.Indentations; diff --git a/UtilityGenerators/IndentedStringBuilderAppendables/IndentedStringBuilderAppendablesGenerator.cs b/UtilityGenerators/IndentedStringBuilderAppendables/IndentedStringBuilderAppendablesGenerator.cs index 4f70670..7c6beeb 100644 --- a/UtilityGenerators/IndentedStringBuilderAppendables/IndentedStringBuilderAppendablesGenerator.cs +++ b/UtilityGenerators/IndentedStringBuilderAppendables/IndentedStringBuilderAppendablesGenerator.cs @@ -46,9 +46,7 @@ private static String GetArgumentsText(ParameterListSyntax parameterList) { var parameters = parameterList.Parameters; if(parameters.Count == 0) - { return String.Empty; - } var resultBuilder = new StringBuilder(); var i = 0; @@ -77,9 +75,7 @@ private static IList GetConstraintsText( var result = ctx.CollectionFactory.CreateList(); if(clauses.Count == 0) - { return result; - } var constraintBuilder = new StringBuilder(); @@ -142,9 +138,7 @@ Boolean tryAppend() .Type; if(type == null) - { return false; - } var fullName = type.ToDisplayString(_fullyQualifiedFormat); _ = constraintBuilder.Append(fullName); @@ -166,25 +160,19 @@ private static String GetParametersText( var enumerator = parameterList.Parameters.GetEnumerator(); if(!enumerator.MoveNext()) - { return String.Empty; - } var resultBuilder = new StringBuilder(); if(!tryAppend()) - { return String.Empty; - } while(enumerator.MoveNext()) { cancellationToken.ThrowIfCancellationRequested(); _ = resultBuilder.Append(", "); if(!tryAppend()) - { return String.Empty; - } } return resultBuilder.ToString(); @@ -198,9 +186,7 @@ Boolean tryAppend() null; if(type == null) - { return false; - } if(enumerator.Current.Modifiers.Count > 0) { @@ -213,9 +199,7 @@ Boolean tryAppend() .Append(enumerator.Current.Identifier.Text); if(enumerator.Current.Default != null) - { _ = resultBuilder.Append(enumerator.Current.Default.ToString()); - } return true; } @@ -270,16 +254,12 @@ private static String FinalStep(ImmutableHashSet signatures, Cancella .Indent(); if(constraints.Count > 0) - { _ = builder.AppendLine().AppendJoinLines(String.Empty, constraints); - } _ = builder.Append(" =>"); if(parameters == String.Empty && constraints.Count == 0) - { _ = builder.Append(" StaticAppendableInstances.").Append(fieldName).AppendLine(';'); - } _ = ( parameters == String.Empty && constraints.Count == 0 ? fieldsBuilder : builder ).AppendLine() .Append("new") @@ -304,9 +284,7 @@ private static ImmutableArray GetSignaturesStep(GeneratorSyntaxContex var symbol = context.SemanticModel.GetDeclaredSymbol(context.Node, ct); if(!IsTargetSymbol(symbol)) - { return _emptySignaturesArray; - } using var ctx = ModelCreationContext.CreateDefault(ct); var cds = (ClassDeclarationSyntax)context.Node; diff --git a/UtilityGenerators/Library/External/System/Buffers/ArrayBufferWriter.cs b/UtilityGenerators/Library/External/System/Buffers/ArrayBufferWriter.cs index e3f10bd..6b91094 100644 --- a/UtilityGenerators/Library/External/System/Buffers/ArrayBufferWriter.cs +++ b/UtilityGenerators/Library/External/System/Buffers/ArrayBufferWriter.cs @@ -49,9 +49,7 @@ public ArrayBufferWriter() public ArrayBufferWriter(int initialCapacity) { if(initialCapacity <= 0) - { throw new ArgumentException(null, nameof(initialCapacity)); - } _buffer = new T[initialCapacity]; _index = 0; @@ -131,14 +129,10 @@ public void Clear() public void Advance(int count) { if(count < 0) - { throw new ArgumentException(null, nameof(count)); - } if(_index > _buffer.Length - count) - { ThrowInvalidOperationException_AdvancedTooFar(_buffer.Length); - } _index += count; } @@ -208,9 +202,7 @@ public Span GetSpan(int sizeHint = 0) private void CheckAndResizeBuffer(int sizeHint) { if(sizeHint < 0) - { throw new ArgumentException(nameof(sizeHint)); - } if(sizeHint == 0) { diff --git a/UtilityGenerators/Library/External/System/HashCode.cs b/UtilityGenerators/Library/External/System/HashCode.cs index c1682f6..28652ef 100644 --- a/UtilityGenerators/Library/External/System/HashCode.cs +++ b/UtilityGenerators/Library/External/System/HashCode.cs @@ -346,23 +346,15 @@ private void Add(int value) // Switch can't be inlined. if(position == 0) - { _queue1 = val; - } else if(position == 1) - { _queue2 = val; - } else if(position == 2) - { _queue3 = val; - } else // position == 3 { if(previousLength == 3) - { Initialize(out _v1, out _v2, out _v3, out _v4); - } _v1 = Round(_v1, _queue1); _v2 = Round(_v2, _queue2); @@ -404,9 +396,7 @@ public int ToHashCode() { hash = QueueRound(hash, _queue2); if(position > 2) - { hash = QueueRound(hash, _queue3); - } } } diff --git a/UtilityGenerators/Library/External/System/LocalAppContextSwitches.cs b/UtilityGenerators/Library/External/System/LocalAppContextSwitches.cs index ec11dbf..71bbb46 100644 --- a/UtilityGenerators/Library/External/System/LocalAppContextSwitches.cs +++ b/UtilityGenerators/Library/External/System/LocalAppContextSwitches.cs @@ -30,14 +30,9 @@ internal static bool GetCachedSwitchValue(string switchName, ref int cachedSwitc { // The cached switch value has 3 states: 0 - unknown, 1 - true, -1 - false if(cachedSwitchValue < 0) - { return false; - } - if(cachedSwitchValue > 0) - { return true; - } return GetCachedSwitchValueInternal(switchName, ref cachedSwitchValue); } diff --git a/UtilityGenerators/Library/Models/AttributeParameterTypeKind.cs b/UtilityGenerators/Library/Models/AttributeParameterTypeKind.cs index 5188154..26ba2f3 100644 --- a/UtilityGenerators/Library/Models/AttributeParameterTypeKind.cs +++ b/UtilityGenerators/Library/Models/AttributeParameterTypeKind.cs @@ -43,9 +43,7 @@ public static Boolean HasAnyFlagFast(this AttributeParameterTypeKind value, para foreach(var flag in flags) { if(( value & flag ) == flag) - { return true; - } } return false; diff --git a/UtilityGenerators/Library/Models/Collections/DictionaryEqualityComparer.cs b/UtilityGenerators/Library/Models/Collections/DictionaryEqualityComparer.cs index 55defa7..047edd2 100644 --- a/UtilityGenerators/Library/Models/Collections/DictionaryEqualityComparer.cs +++ b/UtilityGenerators/Library/Models/Collections/DictionaryEqualityComparer.cs @@ -28,16 +28,12 @@ public Boolean Equals(TDictionaryX x, TDictionaryY y where TDictionaryY : IDictionary { if(x.Count != y.Count) - { return false; - } foreach(var kvp in x) { if(!y.TryGetValue(kvp.Key, out var value) || !valueComparer.Equals(kvp.Value, value)) - { return false; - } } return true; diff --git a/UtilityGenerators/Library/Models/Collections/EnumerableEqualityComparer.cs b/UtilityGenerators/Library/Models/Collections/EnumerableEqualityComparer.cs index 55c3d17..340aff2 100644 --- a/UtilityGenerators/Library/Models/Collections/EnumerableEqualityComparer.cs +++ b/UtilityGenerators/Library/Models/Collections/EnumerableEqualityComparer.cs @@ -17,9 +17,7 @@ public static Int32 GetHashCode(TEnumerable obj, IEqualityCompar var hc = new HashCode(); foreach(var element in obj) - { hc.Add(element, elementComparer); - } return hc.ToHashCode(); } @@ -39,9 +37,7 @@ public Boolean Equals(TEnumerableX x, TEnumerableY y where TEnumerableY : IEnumerable { if(x.GetType() != y.GetType()) - { return false; - } var result = x.SequenceEqual(y, elementComparer); diff --git a/UtilityGenerators/Library/Models/Collections/EquatableList.cs b/UtilityGenerators/Library/Models/Collections/EquatableList.cs index e2e6809..73c215e 100644 --- a/UtilityGenerators/Library/Models/Collections/EquatableList.cs +++ b/UtilityGenerators/Library/Models/Collections/EquatableList.cs @@ -56,9 +56,7 @@ public static EquatableList Create(ReadOnlySpan elements) var result = ctx.CollectionFactory.CreateList(); foreach(var element in elements) - { result.Add(element); - } return result; } diff --git a/UtilityGenerators/Library/Models/Collections/EquatableSet.cs b/UtilityGenerators/Library/Models/Collections/EquatableSet.cs index 30a34b9..23a5a71 100644 --- a/UtilityGenerators/Library/Models/Collections/EquatableSet.cs +++ b/UtilityGenerators/Library/Models/Collections/EquatableSet.cs @@ -66,9 +66,7 @@ public static EquatableSet Create(ReadOnlySpan elements) var result = ctx.CollectionFactory.CreateSet(); foreach(var element in elements) - { _ = result.Add(element); - } return result; } diff --git a/UtilityGenerators/Library/Models/Collections/LazyEquatableList.cs b/UtilityGenerators/Library/Models/Collections/LazyEquatableList.cs index c61a618..6dcb987 100644 --- a/UtilityGenerators/Library/Models/Collections/LazyEquatableList.cs +++ b/UtilityGenerators/Library/Models/Collections/LazyEquatableList.cs @@ -80,9 +80,7 @@ private void FillUpToIndex(Int32 index) { MutabilityContext.ThrowIfReadOnly(); while(Count <= index) - { Add(Factory.Invoke(Count, this)); - } } } @@ -94,9 +92,7 @@ file static class Builder var result = ctx.CollectionFactory.CreateLazyList(); foreach(var element in elements) - { result.Add(element); - } return result; } diff --git a/UtilityGenerators/Library/Models/Collections/MutabilityContext.cs b/UtilityGenerators/Library/Models/Collections/MutabilityContext.cs index f3c0fac..2de0ca5 100644 --- a/UtilityGenerators/Library/Models/Collections/MutabilityContext.cs +++ b/UtilityGenerators/Library/Models/Collections/MutabilityContext.cs @@ -19,9 +19,7 @@ internal sealed partial class MutabilityContext : IDisposable public void ThrowIfReadOnly([CallerMemberName] String? callerName = null) { if(IsImmutable) - { throw new InvalidOperationException($"Unable to mutate using '{callerName}' after its mutability context was set to immutable."); - } } public void Dispose() => SetImmutable(); } diff --git a/UtilityGenerators/Library/Models/Collections/SetEqualityComparer.cs b/UtilityGenerators/Library/Models/Collections/SetEqualityComparer.cs index 3015dc2..b379471 100644 --- a/UtilityGenerators/Library/Models/Collections/SetEqualityComparer.cs +++ b/UtilityGenerators/Library/Models/Collections/SetEqualityComparer.cs @@ -25,16 +25,12 @@ public Boolean Equals(TSetX x, TSetY y) where TSetY : ISet { if(x.Count != y.Count) - { return false; - } foreach(var element in x) { if(!y.Contains(element, elementComparer)) - { return false; - } } return true; diff --git a/UtilityGenerators/Library/Models/NamedTypeModel.cs b/UtilityGenerators/Library/Models/NamedTypeModel.cs index 677faa1..cbf48c9 100644 --- a/UtilityGenerators/Library/Models/NamedTypeModel.cs +++ b/UtilityGenerators/Library/Models/NamedTypeModel.cs @@ -61,9 +61,7 @@ private static void AddContainingTypes(INamedTypeSymbol? containingType, Equatab ctx.ThrowIfCancellationRequested(); if(containingType is null) - { return; - } AddContainingTypes(containingType.ContainingType, containingTypes, in ctx); @@ -79,26 +77,20 @@ public String GetHintName(CancellationToken ct) var resultBuilder = new StringBuilder(); foreach(var part in NamespaceParts) - { _ = resultBuilder.Append(part).Append('.'); - } foreach(var containingType in ContainingTypes) { _ = resultBuilder.Append(containingType.Name).Append('.'); if(containingType.TypeArguments is { Count: > 0 and var innerCount }) - { _ = resultBuilder.Append(innerCount).Append('.'); - } } _ = resultBuilder.Append(Name).Append('.'); if(TypeArguments is { Count: > 0 and var outerCount }) - { _ = resultBuilder.Append(outerCount).Append('.'); - } var result = resultBuilder.Append("g.cs").ToString(); @@ -127,9 +119,7 @@ private void Append(StringBuilder resultBuilder, in AppendTokenInfo tokenInfo, C AppendContainingTypes(resultBuilder, in tokenInfo, ct); if(!tokenInfo.SeparateParts && ( ContainingTypes.Count > 0 || NamespaceParts.Count > 0 )) - { _ = resultBuilder.Append(tokenInfo.SeparatorToken); - } AppendName(resultBuilder, Name, ct); AppendTypeArguments(resultBuilder, TypeArguments, in tokenInfo, ct); @@ -139,44 +129,34 @@ private void AppendNamespace(StringBuilder resultBuilder, in AppendTokenInfo tok ct.ThrowIfCancellationRequested(); if(NamespaceParts.Count <= 0) - { return; - } for(var i = 0; i < NamespaceParts.Count; i++) { ct.ThrowIfCancellationRequested(); if(i != 0) - { _ = resultBuilder.Append(tokenInfo.SeparatorToken); - } _ = resultBuilder.Append((String?)NamespaceParts[i]); } if(tokenInfo.SeparateParts) - { _ = resultBuilder.Append(tokenInfo.SeparatorToken); - } } private void AppendContainingTypes(StringBuilder resultBuilder, in AppendTokenInfo tokenInfo, CancellationToken ct) { ct.ThrowIfCancellationRequested(); if(ContainingTypes.Count == 0) - { return; - } for(var i = 0; i < ContainingTypes.Count; i++) { ct.ThrowIfCancellationRequested(); if(i != 0) - { _ = resultBuilder.Append(tokenInfo.SeparatorToken); - } var containingType = ContainingTypes[i]; @@ -185,9 +165,7 @@ private void AppendContainingTypes(StringBuilder resultBuilder, in AppendTokenIn } if(tokenInfo.SeparateParts) - { _ = resultBuilder.Append(tokenInfo.SeparatorToken); - } } private static void AppendName( StringBuilder resultBuilder, @@ -203,14 +181,10 @@ private static void AppendTypeArguments(StringBuilder resultBuilder, EquatableLi ct.ThrowIfCancellationRequested(); if(typeArguments.Count == 0) - { return; - } if(tokenInfo.SeparateParts) - { _ = resultBuilder.Append(tokenInfo.SeparatorToken); - } _ = resultBuilder.Append(tokenInfo.OpenToken); @@ -219,9 +193,7 @@ private static void AppendTypeArguments(StringBuilder resultBuilder, EquatableLi ct.ThrowIfCancellationRequested(); if(i != 0) - { _ = resultBuilder.Append(tokenInfo.ArgumentSeparatorToken); - } var typeArgument = typeArguments[i]; _ = resultBuilder.Append(typeArgument.Name); @@ -309,9 +281,7 @@ private static void AppendImplementedInterfaces( ct.ThrowIfCancellationRequested(); if(superTypes.Length == 0) - { return; - } using var _ = sourceBuilder.Append(" :").AppendLine().OpenIndentBlockScope(); @@ -320,9 +290,7 @@ private static void AppendImplementedInterfaces( ct.ThrowIfCancellationRequested(); if(i != 0) - { sourceBuilder.Append(',').AppendLineCore(); - } sourceBuilder.AppendCore(superTypes[i]); } @@ -350,9 +318,7 @@ private void AppendNamespace( ct.ThrowIfCancellationRequested(); if(NamespaceParts.Count <= 0) - { return; - } _ = sourceBuilder.Append("namespace "); @@ -382,9 +348,7 @@ private void AppendContainingTypes(StringBuilder hintNameBuilder, StringBuilder ct.ThrowIfCancellationRequested(); if(ContainingTypes.Count == 0) - { return; - } for(var i = 0; i < ContainingTypes.Count; i++) { @@ -417,9 +381,7 @@ private static void AppendTypeArguments(StringBuilder hintNameBuilder, StringBui ct.ThrowIfCancellationRequested(); if(typeArguments.Count == 0) - { return; - } _ = hintNameBuilder.Append("_of"); _ = displayStringBuilder.Append('<'); diff --git a/UtilityGenerators/Library/Models/TypeModel.cs b/UtilityGenerators/Library/Models/TypeModel.cs index 3ebd586..6acf52a 100644 --- a/UtilityGenerators/Library/Models/TypeModel.cs +++ b/UtilityGenerators/Library/Models/TypeModel.cs @@ -18,14 +18,10 @@ public static TypeModel Create(ITypeSymbol type, in ModelCreationContext ctx) ctx.ThrowIfCancellationRequested(); if(type is INamedTypeSymbol named) - { return NamedTypeModel.Create(named, in ctx); - } if(type is IArrayTypeSymbol array) - { return ArrayTypeModel.Create(array, in ctx); - } var namespaceParts = GetNamespaceParts(type, in ctx); var name = type.Name; @@ -51,9 +47,7 @@ private static void AddNamespaceParts(INamespaceSymbol? @namespace, EquatableLis ctx.ThrowIfCancellationRequested(); if(@namespace is null or { IsGlobalNamespace: true }) - { return; - } AddNamespaceParts(@namespace.ContainingNamespace, parts, in ctx); diff --git a/UtilityGenerators/Library/Text/SourceTexts/BlockScopeCollection.cs b/UtilityGenerators/Library/Text/SourceTexts/BlockScopeCollection.cs index 2ec1a11..0bfb245 100644 --- a/UtilityGenerators/Library/Text/SourceTexts/BlockScopeCollection.cs +++ b/UtilityGenerators/Library/Text/SourceTexts/BlockScopeCollection.cs @@ -20,8 +20,6 @@ sealed partial class BlockScopeCollection : IDisposable public void Dispose() { foreach(var scope in _scopes) - { scope.Dispose(); - } } } diff --git a/UtilityGenerators/Library/Text/SourceTexts/CommentBuilder.cs b/UtilityGenerators/Library/Text/SourceTexts/CommentBuilder.cs index ca18aee..8d03829 100644 --- a/UtilityGenerators/Library/Text/SourceTexts/CommentBuilder.cs +++ b/UtilityGenerators/Library/Text/SourceTexts/CommentBuilder.cs @@ -77,21 +77,15 @@ public IndentedStringBuilder SeeCRefMethod(String name, Int32 highlightIndex, pa for(var i = 0; i < parameterTypes.Length; i++) { if(i > 0) - { Builder.AppendCore(", "); - } if(highlightIndex == i) - { Builder.AppendCore('<'); - } Builder.AppendCore(parameterTypes[i]); if(highlightIndex == i) - { Builder.AppendCore('>'); - } } Builder.AppendCore(")\"/>"); @@ -109,21 +103,15 @@ public IndentedStringBuilder SeeCRefMethod(String name, Int32 highlightIndex, pa foreach(var type in parameterTypes) { if(i > 0) - { Builder.AppendCore(", "); - } if(highlightIndex == i) - { Builder.AppendCore('<'); - } Builder.AppendCore(type); if(highlightIndex == i) - { Builder.AppendCore('>'); - } i++; } @@ -138,32 +126,24 @@ public IndentedStringBuilder Langword(String name) => public IndentedStringBuilder InheritDoc(String name, Boolean topLevel = true) { if(topLevel) - { _ = Builder.Append("/// "); - } _ = Builder.Append(""); if(topLevel) - { _ = Builder.AppendLine(); - } return Builder; } public IndentedStringBuilder InheritDoc(Boolean topLevel = true) { if(topLevel) - { _ = Builder.Append("/// "); - } _ = Builder.Append(""); if(topLevel) - { _ = Builder.AppendLine(); - } return Builder; } diff --git a/UtilityGenerators/Library/Text/SourceTexts/IndentedStringBuilder.cs b/UtilityGenerators/Library/Text/SourceTexts/IndentedStringBuilder.cs index 2713dd2..a2b4601 100644 --- a/UtilityGenerators/Library/Text/SourceTexts/IndentedStringBuilder.cs +++ b/UtilityGenerators/Library/Text/SourceTexts/IndentedStringBuilder.cs @@ -33,34 +33,22 @@ private void InitializeBuilder() { if(Options.PrependMarkerComment) - { _ = _builder.Append("// ").Append(Options.NewLine); - } if(!String.IsNullOrWhiteSpace(Options.GeneratorName)) - { _ = _builder.Append("// This file was generated by ").Append(Options.GeneratorName).Append(Options.NewLine); - } if(!String.IsNullOrWhiteSpace(Options.License)) - { _ = _builder.Append(Options.License).Append(Options.NewLine); - } if(Options.PrependMarkerComment) - { _ = _builder.Append("// ").Append(Options.NewLine); - } if(Options.PrependWarningDisablePragma) - { _ = _builder.Append("#pragma warning disable").Append(Options.NewLine); - } if(Options.PrependNullableEnable) - { _ = _builder.Append("#nullable enable").Append(Options.NewLine); - } } public IndentedStringBuilder() : this(IndentedStringBuilderOptions.Default) @@ -79,16 +67,12 @@ private Boolean LastWasNewLine get { if(_builder.Length < Options.NewLine.Length) - { return false; - } for(var i = 1; i <= Options.NewLine.Length; i++) { if(_builder[^i] != Options.NewLine[^i]) - { return false; - } } return true; @@ -101,9 +85,7 @@ private Boolean LastWasNewLine public void OpenBlockCore(Block block) { if(block.PlaceDelimitersOnNewLine && !LastWasNewLine) - { AppendSingleLineCore(); - } AppendCore(block.OpeningDelimiter); @@ -162,18 +144,14 @@ public IndentedStringBuilder OpenRegionBlockScope(String name, BlockScopeCollect public void CloseBlockCore() { if(!LastBlock.HasValue) - { return; - } DetentUnsafeCore(); var block = LastBlock.Value; if(block.PlaceDelimitersOnNewLine) - { AppendSingleLineCore(); - } AppendCore(block.ClosingDelimiter); _ = _blocks.Pop(); @@ -209,9 +187,7 @@ public IndentedStringBuilder Indent() private void ApplyIndentation() { if(!LastWasNewLine) - { return; - } foreach(var indentation in _indentations.Reverse()) { @@ -230,9 +206,7 @@ public void DetentCore() if(_indentations.Count > 0) { if(!_indentations.Peek().IsWhitespace) - { throw new InvalidOperationException("Attempted to detent non-whitespace indentation. Use 'CloseBlock' or 'CloseAllBlocks' instead."); - } DetentUnsafeCore(); } @@ -487,9 +461,7 @@ public IndentedStringBuilder AppendJoin(StringOrChar separator, IEnumerable(StringOrChar separator, IEnumerable(StringOrChar separator, IEnumera var enumerator = values.GetEnumerator(); if(!enumerator.MoveNext()) - { return this; - } AppendCore(enumerator.Current); @@ -625,9 +587,7 @@ public IndentedStringBuilder AppendJoin(StringOrChar separator, IEnumerable characters) { if(characters.Length == 0) - { return; - } var target = Reserve(characters.Length); characters.CopyTo(target); @@ -113,9 +111,7 @@ private void EnsureLength(Int32 requiredLength) public readonly void Dispose() { if(_rented is not null) - { ArrayPool.Shared.Return(_rented); - } } public readonly override String ToString() => Span.ToString(); diff --git a/UtilityGenerators/Library/Text/Templating/TemplateRenderer.Render.cs b/UtilityGenerators/Library/Text/Templating/TemplateRenderer.Render.cs index 20e3703..07e4979 100644 --- a/UtilityGenerators/Library/Text/Templating/TemplateRenderer.Render.cs +++ b/UtilityGenerators/Library/Text/Templating/TemplateRenderer.Render.cs @@ -47,9 +47,7 @@ public void Render(params ReadOnlySpan value) // If the value is empty, we do not unnecessarily prepend indentation, // as no newline could be present. if(value is []) - { return; - } // Otherwise, we split at each newline and append the requested // indentation if the next line is not empty. Since the value will be diff --git a/UtilityGenerators/Library/ThrowHelpers.cs b/UtilityGenerators/Library/ThrowHelpers.cs index ea7511b..a9041c9 100644 --- a/UtilityGenerators/Library/ThrowHelpers.cs +++ b/UtilityGenerators/Library/ThrowHelpers.cs @@ -25,9 +25,7 @@ internal static class ArgumentNullException internal static void ThrowIfNull([NotNull] Object? argument, [CallerArgumentExpression(nameof(argument))] String? paramName = null) { if(argument is null) - { Throw(paramName); - } } [DoesNotReturn] @@ -78,9 +76,7 @@ private static void ThrowNotEqual(T value, T other, String? paramName) => internal static void ThrowIfZero(Single value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value == 0) - { ThrowZero(value, paramName); - } } /// Throws an if is zero. /// The argument to validate as non-zero. @@ -88,9 +84,7 @@ internal static void ThrowIfZero(Single value, [CallerArgumentExpression(nameof( internal static void ThrowIfZero(Double value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value == 0) - { ThrowZero(value, paramName); - } } /// Throws an if is zero. /// The argument to validate as non-zero. @@ -98,9 +92,7 @@ internal static void ThrowIfZero(Double value, [CallerArgumentExpression(nameof( internal static void ThrowIfZero(SByte value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value == 0) - { ThrowZero(value, paramName); - } } /// Throws an if is zero. /// The argument to validate as non-zero. @@ -108,9 +100,7 @@ internal static void ThrowIfZero(SByte value, [CallerArgumentExpression(nameof(v internal static void ThrowIfZero(Int16 value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value == 0) - { ThrowZero(value, paramName); - } } /// Throws an if is zero. /// The argument to validate as non-zero. @@ -118,9 +108,7 @@ internal static void ThrowIfZero(Int16 value, [CallerArgumentExpression(nameof(v internal static void ThrowIfZero(Int32 value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value == 0) - { ThrowZero(value, paramName); - } } /// Throws an if is zero. /// The argument to validate as non-zero. @@ -128,9 +116,7 @@ internal static void ThrowIfZero(Int32 value, [CallerArgumentExpression(nameof(v internal static void ThrowIfZero(Int64 value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value == 0) - { ThrowZero(value, paramName); - } } /// Throws an if is zero. /// The argument to validate as non-zero. @@ -138,9 +124,7 @@ internal static void ThrowIfZero(Int64 value, [CallerArgumentExpression(nameof(v internal static void ThrowIfZero(Byte value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value == 0) - { ThrowZero(value, paramName); - } } /// Throws an if is zero. /// The argument to validate as non-zero. @@ -148,9 +132,7 @@ internal static void ThrowIfZero(Byte value, [CallerArgumentExpression(nameof(va internal static void ThrowIfZero(UInt16 value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value == 0) - { ThrowZero(value, paramName); - } } /// Throws an if is zero. /// The argument to validate as non-zero. @@ -158,9 +140,7 @@ internal static void ThrowIfZero(UInt16 value, [CallerArgumentExpression(nameof( internal static void ThrowIfZero(UInt32 value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value == 0) - { ThrowZero(value, paramName); - } } /// Throws an if is zero. /// The argument to validate as non-zero. @@ -168,9 +148,7 @@ internal static void ThrowIfZero(UInt32 value, [CallerArgumentExpression(nameof( internal static void ThrowIfZero(UInt64 value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value == 0) - { ThrowZero(value, paramName); - } } /// Throws an if is negative. @@ -179,9 +157,7 @@ internal static void ThrowIfZero(UInt64 value, [CallerArgumentExpression(nameof( internal static void ThrowIfNegative(Single value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value < 0) - { ThrowNegative(value, paramName); - } } /// Throws an if is negative. /// The argument to validate as non-negative. @@ -189,9 +165,7 @@ internal static void ThrowIfNegative(Single value, [CallerArgumentExpression(nam internal static void ThrowIfNegative(Double value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value < 0) - { ThrowNegative(value, paramName); - } } /// Throws an if is negative. /// The argument to validate as non-negative. @@ -199,9 +173,7 @@ internal static void ThrowIfNegative(Double value, [CallerArgumentExpression(nam internal static void ThrowIfNegative(SByte value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value < 0) - { ThrowNegative(value, paramName); - } } /// Throws an if is negative. /// The argument to validate as non-negative. @@ -209,9 +181,7 @@ internal static void ThrowIfNegative(SByte value, [CallerArgumentExpression(name internal static void ThrowIfNegative(Int16 value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value < 0) - { ThrowNegative(value, paramName); - } } /// Throws an if is negative. /// The argument to validate as non-negative. @@ -219,9 +189,7 @@ internal static void ThrowIfNegative(Int16 value, [CallerArgumentExpression(name internal static void ThrowIfNegative(Int32 value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value < 0) - { ThrowNegative(value, paramName); - } } /// Throws an if is negative. /// The argument to validate as non-negative. @@ -229,9 +197,7 @@ internal static void ThrowIfNegative(Int32 value, [CallerArgumentExpression(name internal static void ThrowIfNegative(Int64 value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value < 0) - { ThrowNegative(value, paramName); - } } /// Throws an if is negative or zero. @@ -240,9 +206,7 @@ internal static void ThrowIfNegative(Int64 value, [CallerArgumentExpression(name internal static void ThrowIfNegativeOrZero(Single value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value <= 0) - { ThrowNegativeOrZero(value, paramName); - } } /// Throws an if is negative or zero. /// The argument to validate as non-zero or non-negative. @@ -250,9 +214,7 @@ internal static void ThrowIfNegativeOrZero(Single value, [CallerArgumentExpressi internal static void ThrowIfNegativeOrZero(Double value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value <= 0) - { ThrowNegativeOrZero(value, paramName); - } } /// Throws an if is negative or zero. /// The argument to validate as non-zero or non-negative. @@ -260,9 +222,7 @@ internal static void ThrowIfNegativeOrZero(Double value, [CallerArgumentExpressi internal static void ThrowIfNegativeOrZero(SByte value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value <= 0) - { ThrowNegativeOrZero(value, paramName); - } } /// Throws an if is negative or zero. /// The argument to validate as non-zero or non-negative. @@ -270,9 +230,7 @@ internal static void ThrowIfNegativeOrZero(SByte value, [CallerArgumentExpressio internal static void ThrowIfNegativeOrZero(Int16 value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value <= 0) - { ThrowNegativeOrZero(value, paramName); - } } /// Throws an if is negative or zero. /// The argument to validate as non-zero or non-negative. @@ -280,9 +238,7 @@ internal static void ThrowIfNegativeOrZero(Int16 value, [CallerArgumentExpressio internal static void ThrowIfNegativeOrZero(Int32 value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value <= 0) - { ThrowNegativeOrZero(value, paramName); - } } /// Throws an if is negative or zero. /// The argument to validate as non-zero or non-negative. @@ -290,9 +246,7 @@ internal static void ThrowIfNegativeOrZero(Int32 value, [CallerArgumentExpressio internal static void ThrowIfNegativeOrZero(Int64 value, [CallerArgumentExpression(nameof(value))] String? paramName = null) { if(value <= 0) - { ThrowNegativeOrZero(value, paramName); - } } /// Throws an if is equal to . @@ -302,9 +256,7 @@ internal static void ThrowIfNegativeOrZero(Int64 value, [CallerArgumentExpressio internal static void ThrowIfEqual(T value, T other, [CallerArgumentExpression(nameof(value))] String? paramName = null) where T : IEquatable? { if(EqualityComparer.Default.Equals(value, other)) - { ThrowEqual(value, other, paramName); - } } /// Throws an if is not equal to . @@ -314,9 +266,7 @@ internal static void ThrowIfEqual(T value, T other, [CallerArgumentExpression internal static void ThrowIfNotEqual(T value, T other, [CallerArgumentExpression(nameof(value))] String? paramName = null) where T : IEquatable? { if(!EqualityComparer.Default.Equals(value, other)) - { ThrowNotEqual(value, other, paramName); - } } /// Throws an if is greater than . @@ -327,9 +277,7 @@ internal static void ThrowIfGreaterThan(T value, T other, [CallerArgumentExpr where T : IComparable { if(value.CompareTo(other) > 0) - { ThrowGreater(value, other, paramName); - } } /// Throws an if is greater than or equal . @@ -340,9 +288,7 @@ internal static void ThrowIfGreaterThanOrEqual(T value, T other, [CallerArgum where T : IComparable { if(value.CompareTo(other) >= 0) - { ThrowGreaterEqual(value, other, paramName); - } } /// Throws an if is less than . @@ -353,9 +299,7 @@ internal static void ThrowIfLessThan(T value, T other, [CallerArgumentExpress where T : IComparable { if(value.CompareTo(other) < 0) - { ThrowLess(value, other, paramName); - } } /// Throws an if is less than or equal . @@ -366,9 +310,7 @@ internal static void ThrowIfLessThanOrEqual(T value, T other, [CallerArgument where T : IComparable { if(value.CompareTo(other) <= 0) - { ThrowLessEqual(value, other, paramName); - } } } } diff --git a/UtilityGenerators/NonEquatable/NonEquatableGenerator.cs b/UtilityGenerators/NonEquatable/NonEquatableGenerator.cs index 3ee8947..e69067a 100644 --- a/UtilityGenerators/NonEquatable/NonEquatableGenerator.cs +++ b/UtilityGenerators/NonEquatable/NonEquatableGenerator.cs @@ -27,9 +27,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) ct.ThrowIfCancellationRequested(); if(ctx.TargetSymbol is not INamedTypeSymbol target) - { return null; - } using var modelCtx = ModelCreationContext.CreateDefault(ct); var model = NamedTypeModel.Create(target, in modelCtx); @@ -54,9 +52,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .AppendCore("public "); if(m.Kind == PartialTypeKindModel.Class || m.Kind == PartialTypeKindModel.Record) - { sourceBuilder.AppendCore("sealed "); - } sourceBuilder.Append("override bool Equals(object obj) => throw new global::System.NotSupportedException(\"") .Append(displayName) @@ -69,9 +65,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .AppendCore("public "); if(m.Kind == PartialTypeKindModel.Class || m.Kind == PartialTypeKindModel.Record) - { sourceBuilder.AppendCore("sealed "); - } sourceBuilder.Append("override int GetHashCode() => throw new global::System.NotSupportedException(\"") .Append(displayName) diff --git a/UtilityGenerators/Templating/Lexer.cs b/UtilityGenerators/Templating/Lexer.cs index 4cfd92b..33fb521 100644 --- a/UtilityGenerators/Templating/Lexer.cs +++ b/UtilityGenerators/Templating/Lexer.cs @@ -278,9 +278,7 @@ private ReadOnlySpan PeekSpan(Int32 lookahead, Int32 maxLength) private Char Peek(Int32 lookahead = 0) { if(IsAtEnd(lookahead)) - { return '\0'; - } var result = _reifiedTemplateString[_index + _length + lookahead]; diff --git a/UtilityGenerators/Templating/Parser.cs b/UtilityGenerators/Templating/Parser.cs index a798a04..ca78708 100644 --- a/UtilityGenerators/Templating/Parser.cs +++ b/UtilityGenerators/Templating/Parser.cs @@ -67,9 +67,7 @@ private TemplateSyntax Template() : new(new EmptyTemplateSyntax()); if(!IsAtEnd()) - { EmitDiagnostic(Ids.UnexpectedToken, DiagnosticSeverity.Error); - } return result; } @@ -722,9 +720,7 @@ private void Return(TSyntax? syntax) where TSyntax : ISyntax { if(syntax is null) - { return; - } _currentIndex -= syntax.CountTokens(_ct); } @@ -741,9 +737,7 @@ private Boolean Peek( Int32 lookahead = 0) { if(Peek(lookahead) == kind) - { return true; - } return false; } @@ -756,9 +750,7 @@ private Boolean Peek( foreach(var kind in kinds) { if(kind == peeked) - { return true; - } } return false; diff --git a/UtilityGenerators/Templating/SourcePosition.cs b/UtilityGenerators/Templating/SourcePosition.cs index e45d251..d0e8588 100644 --- a/UtilityGenerators/Templating/SourcePosition.cs +++ b/UtilityGenerators/Templating/SourcePosition.cs @@ -50,24 +50,13 @@ public SourcePosition(Int32 line, Int32 character) public Int32 CompareTo(SourcePosition other) { if(Line > other.Line) - { return 1; - } - if(Line < other.Line) - { return -1; - } - if(Character > other.Character) - { return 1; - } - if(Character < other.Character) - { return -1; - } return 0; } diff --git a/UtilityGenerators/Templating/Syntax/Visitors/CommentXmlTreeStringBuilder.cs b/UtilityGenerators/Templating/Syntax/Visitors/CommentXmlTreeStringBuilder.cs index cc28a27..3e7c8df 100644 --- a/UtilityGenerators/Templating/Syntax/Visitors/CommentXmlTreeStringBuilder.cs +++ b/UtilityGenerators/Templating/Syntax/Visitors/CommentXmlTreeStringBuilder.cs @@ -51,9 +51,7 @@ protected override void Append(String production, Action'); if(appendNewLine) - { Builder.AppendLineCore(); - } } protected override void Append(String production, Boolean appendNewLine = true) { @@ -66,8 +64,6 @@ protected override void Append(String production, Boolean appendNewLine = true) .AppendLineCore(); if(appendNewLine) - { Builder.AppendLineCore(); - } } } diff --git a/UtilityGenerators/Templating/Syntax/Visitors/SourceGeneratingTemplateVisitor.CodeBlockVisitor.cs b/UtilityGenerators/Templating/Syntax/Visitors/SourceGeneratingTemplateVisitor.CodeBlockVisitor.cs index 0b4631d..6a74963 100644 --- a/UtilityGenerators/Templating/Syntax/Visitors/SourceGeneratingTemplateVisitor.CodeBlockVisitor.cs +++ b/UtilityGenerators/Templating/Syntax/Visitors/SourceGeneratingTemplateVisitor.CodeBlockVisitor.cs @@ -18,15 +18,11 @@ public override void Visit(WhitespacesSyntax syntax) { } protected override void OnToken(Token token) { if(token.TemplateString.Path is not { Length: > 0 } path) - { return; - } var detentCount = 0; for(; detentCount < parent._builder.OpenBlocks; detentCount++) - { parent._builder.DetentCore(); - } parent._builder .Append("#line (") @@ -48,9 +44,7 @@ protected override void OnToken(Token token) .AppendLineCore(); for(; detentCount > 0; detentCount--) - { parent._builder.IndentCore(); - } } } } diff --git a/UtilityGenerators/Templating/Syntax/Visitors/SourceGeneratingTemplateVisitor.TemplateBlockVisitor.cs b/UtilityGenerators/Templating/Syntax/Visitors/SourceGeneratingTemplateVisitor.TemplateBlockVisitor.cs index 5c185de..f7ddf4b 100644 --- a/UtilityGenerators/Templating/Syntax/Visitors/SourceGeneratingTemplateVisitor.TemplateBlockVisitor.cs +++ b/UtilityGenerators/Templating/Syntax/Visitors/SourceGeneratingTemplateVisitor.TemplateBlockVisitor.cs @@ -10,7 +10,7 @@ internal sealed partial class SourceGeneratingTemplateVisitor private sealed class TemplateBlockVisitor(SourceGeneratingTemplateVisitor parent, CancellationToken ct) : TreeWalkingSyntaxVisitor(ct) { private Int32 _index = -1; - private Int32 _length; + private Int32 _length = 0; public override void Visit(RenderBlockSyntax syntax) { @@ -39,9 +39,7 @@ public void Flush() Ct.ThrowIfCancellationRequested(); if(_index == -1) - { return; - } if(_length != 0) { @@ -105,9 +103,7 @@ private void OnSpans(TokenSpans spans) Ct.ThrowIfCancellationRequested(); if(_index == -1) - { _index = spans.NewlineAwareTemplateSpan.Index; - } _length += spans.NewlineAwareTemplateSpan.Length; } diff --git a/UtilityGenerators/Templating/Syntax/Visitors/SourceGeneratingTemplateVisitor.cs b/UtilityGenerators/Templating/Syntax/Visitors/SourceGeneratingTemplateVisitor.cs index 950f3ec..c0035d7 100644 --- a/UtilityGenerators/Templating/Syntax/Visitors/SourceGeneratingTemplateVisitor.cs +++ b/UtilityGenerators/Templating/Syntax/Visitors/SourceGeneratingTemplateVisitor.cs @@ -45,9 +45,7 @@ public override void Visit(RenderBlockSyntax syntax) var detentCount = 0; for(; detentCount < _builder.OpenBlocks; detentCount++) - { _builder.DetentCore(); - } _builder .Append("#line (") @@ -61,9 +59,7 @@ public override void Visit(RenderBlockSyntax syntax) .AppendCore(')'); if(_templateString.Path is { Length: > 0 } path) - { _builder.Append(" \"").Append(path).AppendCore('"'); - } _builder .AppendLine() @@ -77,9 +73,7 @@ public override void Visit(RenderBlockSyntax syntax) .AppendLineCore(); for(; detentCount > 0; detentCount--) - { _builder.IndentCore(); - } if(syntax.RenderBlockBody is { }) { diff --git a/UtilityGenerators/Templating/Syntax/Visitors/TemplateStringReconstructionVisitor.cs b/UtilityGenerators/Templating/Syntax/Visitors/TemplateStringReconstructionVisitor.cs index 6743179..0fc985b 100644 --- a/UtilityGenerators/Templating/Syntax/Visitors/TemplateStringReconstructionVisitor.cs +++ b/UtilityGenerators/Templating/Syntax/Visitors/TemplateStringReconstructionVisitor.cs @@ -15,12 +15,8 @@ protected override void OnToken(Token token) Ct.ThrowIfCancellationRequested(); if(token.Kind is TokenKind.Newline) - { builder.AppendCore(newline); - } else - { builder.AppendCore(token.Lexeme.ToString()); - } } } diff --git a/UtilityGenerators/Templating/Syntax/Visitors/TokenValidator.cs b/UtilityGenerators/Templating/Syntax/Visitors/TokenValidator.cs index e734032..48a5e44 100644 --- a/UtilityGenerators/Templating/Syntax/Visitors/TokenValidator.cs +++ b/UtilityGenerators/Templating/Syntax/Visitors/TokenValidator.cs @@ -15,9 +15,7 @@ private void AssertToken(Token token, TokenKind type, String lexeme, TS AssertToken(token, type, l => { if(l.Equals(lexeme)) - { return (true, null); - } return (false, $"expected:\n\n{lexeme}"); }, syntax); @@ -33,18 +31,14 @@ private void AssertLexeme(Token token, Func Ct.ThrowIfCancellationRequested(); if(lexemePredicate.Invoke(token.Lexeme.ToString()) is (var success, var message) && !success) - { OnError($"lexeme mismatch:\n{message}\n\nlexeme\n{token.Lexeme.ToString()}\ntoken:\n{token}\nsyntax:\n{syntax}"); - } } private void AssertKind(Token token, TokenKind kind, TSyntax syntax) { Ct.ThrowIfCancellationRequested(); if(kind != token.Kind) - { OnError($"token kind mismatch: expected '{kind}', actual was '{token.Kind}'\ntoken:\n{token}\nsyntax:\n{syntax}"); - } } protected virtual void OnError(String message) => throw new InvalidOperationException(message); diff --git a/UtilityGenerators/Templating/Syntax/Visitors/XmlTreeStringBuilder.cs b/UtilityGenerators/Templating/Syntax/Visitors/XmlTreeStringBuilder.cs index b53c254..31f966e 100644 --- a/UtilityGenerators/Templating/Syntax/Visitors/XmlTreeStringBuilder.cs +++ b/UtilityGenerators/Templating/Syntax/Visitors/XmlTreeStringBuilder.cs @@ -53,9 +53,7 @@ protected override void Append(String production, Action'); if(appendNewLine) - { Builder.AppendLineCore(); - } } protected override void Append(String production, Boolean appendNewLine = true) { @@ -68,8 +66,6 @@ protected override void Append(String production, Boolean appendNewLine = true) .AppendLineCore(); if(appendNewLine) - { Builder.AppendLineCore(); - } } } diff --git a/UtilityGenerators/Templating/TemplateAttribute.Model.cs b/UtilityGenerators/Templating/TemplateAttribute.Model.cs index 5bb22c5..36e507b 100644 --- a/UtilityGenerators/Templating/TemplateAttribute.Model.cs +++ b/UtilityGenerators/Templating/TemplateAttribute.Model.cs @@ -6,7 +6,7 @@ namespace RhoMicro.CodeAnalysis; internal partial class TemplateAttribute { - public partial record struct Model + public partial record Model { public String NewlineValue => Newline switch { diff --git a/UtilityGenerators/Templating/TemplateAttribute.cs b/UtilityGenerators/Templating/TemplateAttribute.cs index 89d07aa..f4b721f 100644 --- a/UtilityGenerators/Templating/TemplateAttribute.cs +++ b/UtilityGenerators/Templating/TemplateAttribute.cs @@ -16,7 +16,7 @@ namespace RhoMicro.CodeAnalysis; [NonEquatable] #endif #if RHOMICRO_CODEANALYSIS_UTILITYGENERATORS || RHOMICRO_CODEANALYSIS_UTILITYGENERATORS_DEV -[GenerateFactory(GenerateModelTypeAsStruct = true)] +[GenerateFactory] #endif internal sealed partial class TemplateAttribute : Attribute { @@ -99,7 +99,14 @@ internal sealed partial class TemplateAttribute : Attribute /// static global::RhoMicro.CodeAnalysis.Library.Text.Templating.Indentations /// /// - public String[]? Usings { get; set; } + [DefaultValue( + [ + "static global::RhoMicro.CodeAnalysis.Library.Text.Templating.Indentations", + ])] + public String[] Usings { get; set; } = + [ + "static global::RhoMicro.CodeAnalysis.Library.Text.Templating.Indentations", + ]; /// /// Gets or sets a value indicating whether to generate a structural /// representation and debugging information of the template for debugging diff --git a/UtilityGenerators/Templating/TemplateString.cs b/UtilityGenerators/Templating/TemplateString.cs index 5f8e0af..072b3cc 100644 --- a/UtilityGenerators/Templating/TemplateString.cs +++ b/UtilityGenerators/Templating/TemplateString.cs @@ -108,9 +108,7 @@ private static Int32 GetOpeningQuoteCount(in SyntaxToken token, CancellationToke var i = 0; for(; token.Text[i] == '"'; i++) - { cancellationToken.ThrowIfCancellationRequested(); - } return i; } diff --git a/UtilityGenerators/Templating/TemplatingGenerator.cs b/UtilityGenerators/Templating/TemplatingGenerator.cs index c7d3c14..2cd8ac4 100644 --- a/UtilityGenerators/Templating/TemplatingGenerator.cs +++ b/UtilityGenerators/Templating/TemplatingGenerator.cs @@ -3,8 +3,10 @@ namespace RhoMicro.CodeAnalysis; using System; + using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; + using RhoMicro.CodeAnalysis.Library.Models; using RhoMicro.CodeAnalysis.Library.Text.SourceTexts; using RhoMicro.CodeAnalysis.Library.Text.Templating; @@ -24,37 +26,36 @@ public class TemplatingGenerator : IIncrementalGenerator /// public void Initialize(IncrementalGeneratorInitializationContext context) { - var provider = context.SyntaxProvider - .ForAttributeWithMetadataName<(NamedTypeModel, TemplateString, TemplateAttribute.Model)?>( - _attributeMetadataName, - static (n, _) => n is RecordDeclarationSyntax or ClassDeclarationSyntax or StructDeclarationSyntax, - static (ctx, ct) => - { - ct.ThrowIfCancellationRequested(); - - if (ctx is not - { - TargetSymbol: INamedTypeSymbol target, - Attributes: [{ } attributeData, ..] - } - || !attributeData.TryGetTemplateAttributeModel(out var attribute, cancellationToken: ct) - || attributeData.ApplicationSyntaxReference?.GetSyntax(ct) is not AttributeSyntax - { - ArgumentList.Arguments: [{ Expression: LiteralExpressionSyntax { Token: var token } }, ..] - }) + var provider = context.SyntaxProvider.ForAttributeWithMetadataName<(NamedTypeModel, TemplateString, TemplateAttribute.Model)?>( + _attributeMetadataName, + static (n, _) => n is RecordDeclarationSyntax or ClassDeclarationSyntax or StructDeclarationSyntax, + static (ctx, ct) => + { + ct.ThrowIfCancellationRequested(); + + if(ctx is not { - return null; + TargetSymbol: INamedTypeSymbol target, + Attributes: [{ } attributeData, ..] } + || !attributeData.TryGetTemplateAttributeModel(out var attribute, cancellationToken: ct) + || attributeData.ApplicationSyntaxReference?.GetSyntax(ct) is not AttributeSyntax + { + ArgumentList.Arguments: [{ Expression: LiteralExpressionSyntax { Token: var token } }, ..] + }) + { + return null; + } - using var modelCtx = ModelCreationContext.CreateDefault(ct); - var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); - cts.CancelAfter(_parserTimeout); - var templateString = TemplateString.Create(token, cts.Token); - var typeModel = NamedTypeModel.Create(target, in modelCtx); - var result = (typeModel, templateString, attribute); + using var modelCtx = ModelCreationContext.CreateDefault(ct); + var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); + cts.CancelAfter(_parserTimeout); + var templateString = TemplateString.Create(token, cts.Token); + var typeModel = NamedTypeModel.Create(target, in modelCtx); + var result = (typeModel, templateString, attribute); - return result; - }) + return result; + }) .Where(static t => t.HasValue) .Select(static (t, ct) => { @@ -84,7 +85,6 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var isbProvider = provider.Select(IsbImpl); context.RegisterSourceOutput(isbProvider, (ctx, t) => ctx.AddSource(t.hintName, t.source)); } - private static (String hintName, String source) IsbImpl( (NamedTypeModel type, ParseResult parseResult, TemplateAttribute.Model attribute) t, CancellationToken ct) @@ -93,7 +93,8 @@ private static (String hintName, String source) IsbImpl( var sourceBuilder = new IndentedStringBuilder(IndentedStringBuilderOptions.GeneratedFile with { - AmbientCancellationToken = ct, GeneratorName = typeof(TemplatingGenerator).FullName + AmbientCancellationToken = ct, + GeneratorName = typeof(TemplatingGenerator).FullName }); var (type, parseResult, attribute) = t; @@ -102,10 +103,8 @@ private static (String hintName, String source) IsbImpl( var templateString = scanResult.TemplateString; var diagnostics = parseResult.AllDiagnostics; - foreach (var u in attribute.Usings ?? []) - { + foreach(var u in attribute.Usings) sourceBuilder.Append("using ").Append(u).Append(';').AppendLineCore(); - } type.BuildStrings( sourceBuilder, @@ -114,52 +113,53 @@ private static (String hintName, String source) IsbImpl( ["global::RhoMicro.CodeAnalysis.Library.Text.Templating.ITemplate"], ct); - if (attribute.GenerateToString) + if(attribute.GenerateToString) { sourceBuilder .AppendLine("public override string ToString()") .Indent() - .AppendLine("=> global::RhoMicro.CodeAnalysis.Library.Text.Templating.TemplateRenderer.Render(this);") - .AppendLine() + .AppendLine("=> global::RhoMicro.CodeAnalysis.Library.Text.Templating.TemplateRenderer.Render(this);") + .AppendLine() .Detent() + .Append("public string RenderToString<") .Append(attribute.BodyParameterTypeName) .Append(">(in ") .Append(attribute.BodyParameterTypeName).AppendLine(" body)") .Indent() - .Append("where ").Append(attribute.BodyParameterTypeName) - .AppendLine(" : global::RhoMicro.CodeAnalysis.Library.Text.Templating.ITemplate") - .Append("=> global::RhoMicro.CodeAnalysis.Library.Text.Templating.TemplateRenderer.Render<") - .Append(displayString) - .Append(", ") - .Append(attribute.BodyParameterTypeName) - .AppendLine(">(this, body);") - .AppendLine() + .Append("where ").Append(attribute.BodyParameterTypeName).AppendLine(" : global::RhoMicro.CodeAnalysis.Library.Text.Templating.ITemplate") + .Append("=> global::RhoMicro.CodeAnalysis.Library.Text.Templating.TemplateRenderer.Render<") + .Append(displayString) + .Append(", ") + .Append(attribute.BodyParameterTypeName) + .AppendLine(">(this, body);") + .AppendLine() .Detent() + .Append("public string RenderToString<") .Append(attribute.BodyParameterTypeName) .Append(">(in ") .Append(attribute.BodyParameterTypeName) .AppendLine(" body, global::System.Threading.CancellationToken cancellationToken)") .Indent() - .Append("where ").Append(attribute.BodyParameterTypeName) - .AppendLine(" : global::RhoMicro.CodeAnalysis.Library.Text.Templating.ITemplate") - .Append("=> global::RhoMicro.CodeAnalysis.Library.Text.Templating.TemplateRenderer.Render<") - .Append(displayString) - .Append(", ") - .Append(attribute.BodyParameterTypeName) - .AppendLine(">(this, body, cancellationToken);") - .AppendLine() + .Append("where ").Append(attribute.BodyParameterTypeName).AppendLine(" : global::RhoMicro.CodeAnalysis.Library.Text.Templating.ITemplate") + .Append("=> global::RhoMicro.CodeAnalysis.Library.Text.Templating.TemplateRenderer.Render<") + .Append(displayString) + .Append(", ") + .Append(attribute.BodyParameterTypeName) + .AppendLine(">(this, body, cancellationToken);") + .AppendLine() .Detent() + .AppendLine( "public string RenderToString(" + "global::System.Threading.CancellationToken cancellationToken)") .Indent() - .AppendLine("=> " + - "global::RhoMicro.CodeAnalysis.Library.Text.Templating.TemplateRenderer.Render(" + - "this, cancellationToken" + - ");") - .AppendLine() + .AppendLine("=> " + + "global::RhoMicro.CodeAnalysis.Library.Text.Templating.TemplateRenderer.Render(" + + "this, cancellationToken" + + ");") + .AppendLine() .DetentCore(); } @@ -168,29 +168,25 @@ private static (String hintName, String source) IsbImpl( .Append(attribute.BodyParameterTypeName) .AppendLine(">(") .Indent() - .Append("ref global::RhoMicro.CodeAnalysis.Library.Text.Templating.TemplateRenderer ") - .Append(attribute.RendererParameterName).AppendLine(',') - .Append(attribute.BodyParameterTypeName).Append(' ') - .Append(attribute.BodyParameterName).AppendLine(',') - .Append("global::System.Threading.CancellationToken ") - .Append(attribute.CancellationTokenParameterName).AppendLine(')') - .Append("where ").Append(attribute.BodyParameterTypeName) - .Append(" : global::RhoMicro.CodeAnalysis.Library.Text.Templating.ITemplate") + .Append("ref global::RhoMicro.CodeAnalysis.Library.Text.Templating.TemplateRenderer ") + .Append(attribute.RendererParameterName).AppendLine(',') + .Append(attribute.BodyParameterTypeName).Append(' ') + .Append(attribute.BodyParameterName).AppendLine(',') + .Append("global::System.Threading.CancellationToken ") + .Append(attribute.CancellationTokenParameterName).AppendLine(')') + .Append("where ").Append(attribute.BodyParameterTypeName).Append(" : global::RhoMicro.CodeAnalysis.Library.Text.Templating.ITemplate") .Detent() .OpenBracesBlock() .Append(attribute.CancellationTokenParameterName).AppendLine(".ThrowIfCancellationRequested();") .AppendLine() .Append("// This template was taken from: ") .Append(templateString.Path) - .Append('(').Append(templateString.Start.Line.ToString()).Append(',') - .Append(templateString.Start.Character.ToString()).AppendLine(')') + .Append('(').Append(templateString.Start.Line.ToString()).Append(',').Append(templateString.Start.Character.ToString()).AppendLine(')') .Append("const string __template =").AppendLineCore(); var detentCount = 0; - for (; detentCount < sourceBuilder.OpenBlocks; detentCount++) - { + for(; detentCount < sourceBuilder.OpenBlocks; detentCount++) sourceBuilder.DetentCore(); - } var quotes = new String('"', scanResult.RequiredQuotes); sourceBuilder @@ -199,19 +195,17 @@ private static (String hintName, String source) IsbImpl( template.Accept( new TemplateStringReconstructionVisitor( - newline: attribute.NewlineValue, - sourceBuilder, - ct)); + newline: attribute.NewlineValue, + sourceBuilder, + ct)); sourceBuilder .AppendLine() .Append(quotes) .AppendCore(";"); - for (; detentCount > 0; detentCount--) - { + for(; detentCount > 0; detentCount--) sourceBuilder.IndentCore(); - } sourceBuilder.AppendLine().AppendLineCore(); @@ -223,7 +217,6 @@ private static (String hintName, String source) IsbImpl( return (hintName, source); } - private static void AppendTemplate( IndentedStringBuilder sourceBuilder, TemplateSyntax template, @@ -235,13 +228,11 @@ private static void AppendTemplate( template.Accept(new SourceGeneratingTemplateVisitor(sourceBuilder, attribute, templateString, ct)); } - - private static void AppendDiagnostics(IndentedStringBuilder sourceBuilder, - IEnumerable diagnostics, CancellationToken ct) + private static void AppendDiagnostics(IndentedStringBuilder sourceBuilder, IEnumerable diagnostics, CancellationToken ct) { ct.ThrowIfCancellationRequested(); - foreach (var diagnostic in diagnostics) + foreach(var diagnostic in diagnostics) { sourceBuilder .Append("// ") @@ -249,7 +240,6 @@ private static void AppendDiagnostics(IndentedStringBuilder sourceBuilder, .AppendLineCore(); } } - private static void AppendDebugComment( IndentedStringBuilder sourceBuilder, TemplateAttribute.Model attribute, @@ -258,10 +248,8 @@ private static void AppendDebugComment( { ct.ThrowIfCancellationRequested(); - if (!attribute.GenerateDebugInfo) - { + if(!attribute.GenerateDebugInfo) return; - } var xmlTree = template.ToCommentXmlTreeString(ct); diff --git a/UtilityGenerators/TypeSymbolPattern/TypeSymbolPatternGenerator.cs b/UtilityGenerators/TypeSymbolPattern/TypeSymbolPatternGenerator.cs index 1f7d8df..057c072 100644 --- a/UtilityGenerators/TypeSymbolPattern/TypeSymbolPatternGenerator.cs +++ b/UtilityGenerators/TypeSymbolPattern/TypeSymbolPatternGenerator.cs @@ -5,6 +5,7 @@ namespace RhoMicro.CodeAnalysis; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; + using RhoMicro.CodeAnalysis.Library.Models; using RhoMicro.CodeAnalysis.Library.Text.SourceTexts; @@ -14,49 +15,37 @@ namespace RhoMicro.CodeAnalysis; [Generator(LanguageNames.CSharp)] public partial class TypeSymbolPatternGenerator : IIncrementalGenerator { - // [TypeSymbolPattern(typeof(ITypeSymbol))] - // private static partial Boolean IsTypeSymbol(ITypeSymbol type); + [TypeSymbolPattern(typeof(ITypeSymbol))] + private static partial Boolean IsTypeSymbol(ITypeSymbol type); /// public void Initialize(IncrementalGeneratorInitializationContext context) { var provider = context.SyntaxProvider.ForAttributeWithMetadataName( - "RhoMicro.CodeAnalysis.TypeSymbolPatternAttribute", - static (n, _) => n is MethodDeclarationSyntax - { - Modifiers: [.., { RawKind: (Int32)SyntaxKind.PartialKeyword }] - }, - static (ctx, ct) => - { - ct.ThrowIfCancellationRequested(); + "RhoMicro.CodeAnalysis.TypeSymbolPatternAttribute", + static (n, _) => n is MethodDeclarationSyntax + { + Modifiers: [.., { RawKind: (Int32)SyntaxKind.PartialKeyword }] + }, + static (ctx, ct) => + { + ct.ThrowIfCancellationRequested(); - // The target must be partial, return bool, and have single parameter of type ITypeSymbol. - if (ctx.TargetSymbol is not IMethodSymbol - { - ReturnType.SpecialType: SpecialType.System_Boolean, - IsPartialDefinition: true, - Parameters: [{ Type: { - Name: nameof(ITypeSymbol), - ContainingNamespace: - { - Name: "CodeAnalysis", - ContainingNamespace: - { - Name: "Microsoft", - ContainingNamespace.IsGlobalNamespace: true - } - } - } } - ] - } target) + // The target must be partial, return bool, and have single parameter of type ITypeSymbol. + if(ctx.TargetSymbol is not IMethodSymbol { - return null; - } + ReturnType.SpecialType: SpecialType.System_Boolean, + IsPartialDefinition: true, + Parameters: [{ } singleParameter] + } target || !IsTypeSymbol(singleParameter.Type)) + { + return null; + } - using var modelCtx = ModelCreationContext.CreateDefault(ct); - var result = TypeSymbolPatternMethodsPartialModel.Create(target, ctx.Attributes[0], in modelCtx); + using var modelCtx = ModelCreationContext.CreateDefault(ct); + var result = TypeSymbolPatternMethodsPartialModel.Create(target, ctx.Attributes[0], in modelCtx); - return result; - }).Where(static m => m is not null) + return result; + }).Where(static m => m is not null) .Collect() .SelectMany(static (m, ct) => { @@ -73,15 +62,14 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var sourceBuilder = new IndentedStringBuilder(IndentedStringBuilderOptions.GeneratedFile with { - GeneratorName = typeof(TypeSymbolPatternGenerator).FullName, AmbientCancellationToken = ct + GeneratorName = typeof(TypeSymbolPatternGenerator).FullName, + AmbientCancellationToken = ct }); m.ContainingType.BuildStrings(sourceBuilder, out var hintName, out _, ct); - foreach (var method in m.Methods) - { + foreach(var method in m.Methods) AppendPatternMethods(sourceBuilder, method, ct); - } var source = sourceBuilder .CloseAllBlocks() @@ -93,30 +81,24 @@ public void Initialize(IncrementalGeneratorInitializationContext context) context.RegisterSourceOutput(provider, (ctx, t) => ctx.AddSource(t.hintName, t.source)); } - private static void AppendPatternMethods(IndentedStringBuilder sourceBuilder, TypeSymbolPatternMethodModel method, - CancellationToken ct) + private static void AppendPatternMethods(IndentedStringBuilder sourceBuilder, TypeSymbolPatternMethodModel method, CancellationToken ct) { ct.ThrowIfCancellationRequested(); AppendPatternMethod(sourceBuilder, method, ct); AppendPatternOutMethod(sourceBuilder, method, ct); } - - private static void AppendPatternOutMethod(IndentedStringBuilder sourceBuilder, TypeSymbolPatternMethodModel method, - CancellationToken ct) + private static void AppendPatternOutMethod(IndentedStringBuilder sourceBuilder, TypeSymbolPatternMethodModel method, CancellationToken ct) { ct.ThrowIfCancellationRequested(); sourceBuilder.AppendCore(SyntaxFacts.GetText(method.Accessibility)); - if (method.IsStatic) - { + if(method.IsStatic) sourceBuilder.AppendCore(" static"); - } sourceBuilder - .Append(" bool ").Append(method.MethodName).AppendCore( - "(global::Microsoft.CodeAnalysis.ITypeSymbol? type, [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out "); + .Append(" bool ").Append(method.MethodName).AppendCore("(global::Microsoft.CodeAnalysis.ITypeSymbol? type, [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out "); AppendPatternTypeType(sourceBuilder, method, ct); @@ -133,22 +115,17 @@ private static void AppendPatternOutMethod(IndentedStringBuilder sourceBuilder, .Append("return result;") .CloseBlockCore(); } - - private static void AppendPatternMethod(IndentedStringBuilder sourceBuilder, TypeSymbolPatternMethodModel method, - CancellationToken ct) + private static void AppendPatternMethod(IndentedStringBuilder sourceBuilder, TypeSymbolPatternMethodModel method, CancellationToken ct) { ct.ThrowIfCancellationRequested(); sourceBuilder.AppendCore(SyntaxFacts.GetText(method.Accessibility)); - if (method.IsStatic) - { + if(method.IsStatic) sourceBuilder.AppendCore(" static"); - } sourceBuilder - .Append(" partial bool ").Append(method.MethodName) - .AppendCore("(global::Microsoft.CodeAnalysis.ITypeSymbol? type"); + .Append(" partial bool ").Append(method.MethodName).AppendCore("(global::Microsoft.CodeAnalysis.ITypeSymbol? type"); sourceBuilder .Append(')') @@ -167,32 +144,29 @@ private static void AppendPatternMethod(IndentedStringBuilder sourceBuilder, Typ .CloseBlockCore(); } - private static void AppendTypePattern(IndentedStringBuilder sourceBuilder, TypeModel type, - Boolean checkTypeArguments, ref CancellationToken ct) + private static void AppendTypePattern(IndentedStringBuilder sourceBuilder, TypeModel type, Boolean checkTypeArguments, ref CancellationToken ct) { ct.ThrowIfCancellationRequested(); - if (type is NamedTypeModel named) + if(type is NamedTypeModel named) { sourceBuilder .Append("global::Microsoft.CodeAnalysis.INamedTypeSymbol") .OpenBracesBlock() - .Append("Name: \"").Append(type.Name).Append("\",").AppendLineCore(); + .Append("Name: \"").Append(type.Name).Append("\",").AppendLineCore(); - if (checkTypeArguments) + if(checkTypeArguments) { _ = sourceBuilder .Append("TypeArguments:") .OpenBracketsBlock(); - for (var i = 0; i < named.TypeArguments.Count; i++) + for(var i = 0; i < named.TypeArguments.Count; i++) { ct.ThrowIfCancellationRequested(); - if (i != 0) - { + if(i != 0) sourceBuilder.AppendCore(", "); - } var arg = named.TypeArguments[i]; AppendTypePattern(sourceBuilder, arg, checkTypeArguments, ref ct); @@ -205,38 +179,35 @@ private static void AppendTypePattern(IndentedStringBuilder sourceBuilder, TypeM } AppendNamespacePatternPart(sourceBuilder, type, ct); - } - else if (type is ArrayTypeModel array) + } else if(type is ArrayTypeModel array) { sourceBuilder .Append("global::Microsoft.CodeAnalysis.IArrayTypeSymbol") .OpenBracesBlock() - .AppendCore("ElementType: "); + .AppendCore("ElementType: "); AppendTypePattern(sourceBuilder, array.ElementType, checkTypeArguments, ref ct); sourceBuilder.AppendLineCore(); - } - else + } else { sourceBuilder .OpenBracesBlock() - .Append("Name: \"").Append(type.Name).Append("\",").AppendLineCore(); + .Append("Name: \"").Append(type.Name).Append("\",").AppendLineCore(); AppendNamespacePatternPart(sourceBuilder, type, ct); } sourceBuilder.CloseBlockCore(); } - private static void AppendNamespacePatternPart(IndentedStringBuilder sourceBuilder, TypeModel type, - CancellationToken ct) + private static void AppendNamespacePatternPart(IndentedStringBuilder sourceBuilder, TypeModel type, CancellationToken ct) { ct.ThrowIfCancellationRequested(); sourceBuilder .AppendCore("ContainingNamespace: "); - for (var i = type.NamespaceParts.Count - 1; i > -1; i--) + for(var i = type.NamespaceParts.Count - 1; i > -1; i--) { ct.ThrowIfCancellationRequested(); @@ -252,24 +223,17 @@ private static void AppendNamespacePatternPart(IndentedStringBuilder sourceBuild .OpenBracesBlock() .AppendCore("IsGlobalNamespace: true"); - for (var i = -1; i < type.NamespaceParts.Count; i++) - { + for(var i = -1; i < type.NamespaceParts.Count; i++) sourceBuilder.CloseBlockCore(); - } } - private static void AppendPatternTypeType(IndentedStringBuilder sourceBuilder, TypeSymbolPatternMethodModel method, - CancellationToken ct) + private static void AppendPatternTypeType(IndentedStringBuilder sourceBuilder, TypeSymbolPatternMethodModel method, CancellationToken ct) { ct.ThrowIfCancellationRequested(); - if (method.Type.Type is ArrayTypeModel) - { + if(method.Type.Type is ArrayTypeModel) sourceBuilder.AppendCore("global::Microsoft.CodeAnalysis.IArrayTypeSymbol"); - } - else if (method.Type.Type is NamedTypeModel) - { + else if(method.Type.Type is NamedTypeModel) sourceBuilder.AppendCore("global::Microsoft.CodeAnalysis.INamedTypeSymbol"); - } } } diff --git a/UtilityGenerators/bootstrap.sh b/UtilityGenerators/bootstrap.sh deleted file mode 100644 index c416f16..0000000 --- a/UtilityGenerators/bootstrap.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/bash - -# Default configuration -CONFIGURATION="Debug" - -# Get the directory of the script -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -PROPS_PATH="$SCRIPT_DIR/../Directory.Build.props" - -# Increment assembly revision -REVISION=$(grep -oP '(?<=)\d+' "$PROPS_PATH") -NEW_REVISION=$((REVISION + 1)) -sed -i "s/$REVISION$NEW_REVISION /dev/null 2>&1 -if [ $? -ne 0 ]; then - echo -e "\033[31mError while building UtilityGenerators.csproj\033[0m" - exit 1 -else - echo -e "\033[32mBuilt UtilityGenerators.csproj\033[0m" -fi - -# Build UtilityGenerators.Dev.csproj -echo -e "\033[33mBuilding UtilityGenerators.Dev.csproj\033[0m" -dotnet build "$SCRIPT_DIR/../UtilityGenerators.Dev/UtilityGenerators.Dev.csproj" -c "$CONFIGURATION" -v q -p:SolutionName="RhoMicro.CodeAnalysis" > /dev/null 2>&1 -if [ $? -ne 0 ]; then - echo -e "\033[31mError while building UtilityGenerators.Dev.csproj\033[0m" - exit 1 -else - echo -e "\033[32mBuilt UtilityGenerators.Dev.csproj\033[0m" -fi - -# Restore UtilityGenerators.csproj -echo -e "\033[33mRestoring UtilityGenerators.csproj\033[0m" -dotnet restore --force > /dev/null 2>&1 -if [ $? -ne 0 ]; then - echo -e "\033[31mError while restoring UtilityGenerators.csproj\033[0m" - exit 1 -else - echo -e "\033[32mRestored UtilityGenerators.csproj\033[0m" -fi diff --git a/VisitorGenerator/Generators/Models/NodeModel.cs b/VisitorGenerator/Generators/Models/NodeModel.cs index 306f2f4..ad33675 100644 --- a/VisitorGenerator/Generators/Models/NodeModel.cs +++ b/VisitorGenerator/Generators/Models/NodeModel.cs @@ -26,34 +26,22 @@ public static void AddModels( ctx.ThrowIfCancellationRequested(); if (SymbolEqualityComparer.Default.Equals(nodeTypeCandidate, baseNodeType)) - { return; - } if (!handledTypes.Add(nodeTypeCandidate)) - { return; - } if (nodeTypeCandidate is not INamedTypeSymbol namedNodeTypeCandidate) - { return; - } if (namedNodeTypeCandidate.ContainingType is not null) - { return; - } if (!namedNodeTypeCandidate.Inherits(baseNodeType)) - { return; - } if (!NodeSignatureModel.TryCreate(namedNodeTypeCandidate, out var signature, in ctx)) - { return; - } var properties = ctx.CollectionFactory.CreateList(); foreach (var member in namedNodeTypeCandidate.GetMembers()) @@ -61,18 +49,14 @@ public static void AddModels( ctx.ThrowIfCancellationRequested(); if (PropertyModel.TryCreate(member, baseNodeType, out var property, in ctx)) - { properties.Add(property); - } } var model = new NodeModel(properties, signature, baseSignature); models.Add(model); if (nodeTypeCandidate.BaseType is { } baseNodeTypeCandidate) - { AddModels(baseNodeTypeCandidate, baseNodeType, baseSignature, models, handledTypes, in ctx); - } } public Boolean IsLeaf() => Signature.Flags.HasFlag(NodeSignatureFlags.IsSealed); diff --git a/VisitorGenerator/Generators/Models/NodeSignatureModel.cs b/VisitorGenerator/Generators/Models/NodeSignatureModel.cs index cacf4bc..e76c5b0 100644 --- a/VisitorGenerator/Generators/Models/NodeSignatureModel.cs +++ b/VisitorGenerator/Generators/Models/NodeSignatureModel.cs @@ -31,19 +31,13 @@ public static Boolean TryCreate(ITypeSymbol type, [NotNullWhen(true)] out NodeSi var flags = NodeSignatureFlags.None; if (type.IsRecord) - { flags |= NodeSignatureFlags.IsRecord; - } if (type.DeclaredAccessibility is Accessibility.Public) - { flags |= NodeSignatureFlags.IsPublic; - } if (type.IsSealed) - { flags |= NodeSignatureFlags.IsSealed; - } var typeParameters = ctx.CollectionFactory.CreateList(); diff --git a/VisitorGenerator/Generators/Models/PropertyModel.cs b/VisitorGenerator/Generators/Models/PropertyModel.cs index 3cc9738..ecd0ec7 100644 --- a/VisitorGenerator/Generators/Models/PropertyModel.cs +++ b/VisitorGenerator/Generators/Models/PropertyModel.cs @@ -24,9 +24,7 @@ public static Boolean TryCreate( result = null; if(propertyCandidate is not IPropertySymbol property) - { return false; - } if(property is not { @@ -74,9 +72,7 @@ @interface.TypeArguments is [INamedTypeSymbol arg] && } if(propertyType is null) - { return false; - } var type = propertyType.ToDisplayString(); diff --git a/VisitorGenerator/Generators/VisitorGenerator.cs b/VisitorGenerator/Generators/VisitorGenerator.cs index d26db24..57c4cb2 100644 --- a/VisitorGenerator/Generators/VisitorGenerator.cs +++ b/VisitorGenerator/Generators/VisitorGenerator.cs @@ -119,9 +119,7 @@ private static void AddModels( ctx.ThrowIfCancellationRequested(); if (model is null) - { continue; - } var signature = model.Signature; @@ -138,9 +136,7 @@ private static void AddModels( ctx.ThrowIfCancellationRequested(); if (handledNodes.Add(node.Signature)) - { newDuplicate.Nodes.Add(node); - } } duplicate = newDuplicate; @@ -154,9 +150,7 @@ private static void AddModels( ctx.ThrowIfCancellationRequested(); if (handledNodes.Add(node.Signature)) - { duplicate.Nodes.Add(node); - } } } else @@ -172,22 +166,16 @@ private static void AddModels( ct.ThrowIfCancellationRequested(); if (ctx.TargetSymbol is not INamedTypeSymbol baseNodeType) - { return null; - } if (baseNodeType.ContainingType is not null) - { return null; - } using var modelCtx = ModelCreationContext.CreateDefault(ct); var nodes = modelCtx.CollectionFactory.CreateList(); if (!NodeSignatureModel.TryCreate(baseNodeType, out var baseSignature, in modelCtx)) - { return null; - } var handledTypes = new HashSet(SymbolEqualityComparer.Default); var result = new BaseNodeModel(nodes, baseSignature); @@ -201,16 +189,12 @@ private static void AddModels( ct.ThrowIfCancellationRequested(); if (arg is not { Kind: TypedConstantKind.Array, Values: { } @params }) - { continue; - } foreach (var param in @params) { if (param is not { Kind: TypedConstantKind.Type, Value: ITypeSymbol typeArg }) - { continue; - } NodeModel.AddModels( typeArg, @@ -223,9 +207,7 @@ private static void AddModels( } if (attribute.AttributeClass?.TypeArguments is not [_, ..] args) - { continue; - } foreach (var arg in args) { @@ -265,18 +247,14 @@ private static Boolean IsGenerateVisitorAttributeTarget(SyntaxNode n, Cancellati if (modifier.IsKind(SyntaxKind.AbstractKeyword)) { if (isAbstract) - { return false; - } isAbstract = true; } } if (!isAbstract) - { return false; - } return true; } diff --git a/VisitorGenerator/VisitorGenerator.csproj b/VisitorGenerator/VisitorGenerator.csproj index 3e69b3c..6b81fbc 100644 --- a/VisitorGenerator/VisitorGenerator.csproj +++ b/VisitorGenerator/VisitorGenerator.csproj @@ -9,7 +9,7 @@ enable enable preview - $(NoWarn);CS1591;CS9113 + $(NoWarn);CS1591 diff --git a/WorkerService1/Program.cs b/WorkerService1/Program.cs new file mode 100644 index 0000000..7d5eb5b --- /dev/null +++ b/WorkerService1/Program.cs @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: MPL-2.0 + +using Microsoft.Extensions.Options; + +using RhoMicro.CodeAnalysis.WorkerService1; + +using System.ComponentModel.DataAnnotations; +using System.Text.Json; + +var builder = Host.CreateApplicationBuilder(args); + +_ = builder.Services + .AddFoo(c => c.UseMonitorOptions()) + .AddHostedService(); + +var host = builder.Build(); +host.Run(); + +namespace RhoMicro.CodeAnalysis.WorkerService1 +{ + partial class BarRegistrationStrategy + { + partial class Pattern + { + static partial void ConfigureOptionsBuilder( + OptionsBuilder builder, + BarConfiguration configuration) + => builder.ValidateDataAnnotations().ValidateOnStart(); + } + } + + public class Worker(ILogger logger, IFoo options) : BackgroundService + { + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + var jsonOptions = new JsonSerializerOptions() { WriteIndented = true }; + + while(!stoppingToken.IsCancellationRequested) + { + logger.LogInformation("{Data}", JsonSerializer.Serialize(options, jsonOptions)); + await Task.Delay(500, stoppingToken).ConfigureAwait(false); + } + } + } + + // Process: + // An interface is consumed by the library: public interface IFooOptions + // An implementation is provided for in-process configuration: public sealed class ImmutableFooOptions + // An implementation is provided for out-of-process configuration: public sealed class FooOptions + + // By default, FooOptions will be registered via the options pattern + // In addition to the options pattern, we map the IOptions onto IFooOptions via some form of '.AddTransient(sp => sp.GetRequiredService>().Value)' + // The registration may be overridden by IFooOptions, e.g. via ImmutableFooOptions + // Since FooOptions is not explicitly depended upon from anywhere, this causes the options pattern to become inert, unless validation is performed at startup. + + // Related: validate required properties automatically + + // The options model needs to contain (we do not support nested interfaces for now): + // - Properties + // - name + // - namespace + // - normalized name + // - because we're including arbitrary expressions (attributes & dve), we also need to clone using statements + // The property model needs to contain: + // - Type display string + // - readonly? init? set? + // - default value expression + // - list of attribute expressions + // - location of the name for diagnostics like non-nullable prop being unassigned + // The default value expression needs to contain: + // - expression text + // - location + [Options] + public partial interface IFoo + { + [DefaultValueExpression("String.Empty")] + [AllowedValues(123, 456L)] + String Prop { get; } + + Int32 IntProp { get; } + IBar Bar { get; } + } + + [Options] + public partial interface IBar + { + String StringProp { get; } + } +} diff --git a/WorkerService1/Properties/launchSettings.json b/WorkerService1/Properties/launchSettings.json new file mode 100644 index 0000000..e5d9dbd --- /dev/null +++ b/WorkerService1/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "WorkerService1": { + "commandName": "Project", + "dotnetRunMessages": true, + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} diff --git a/WorkerService1/WorkerService1.csproj b/WorkerService1/WorkerService1.csproj new file mode 100644 index 0000000..0245c41 --- /dev/null +++ b/WorkerService1/WorkerService1.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + enable + enable + dotnet-WorkerService1-e2bcb5cd-902f-4d76-828b-e06aff08fdb1 + $(NoWarn);CS1591;CS1716;CA1716,CA1848;IDE0040 + + + + + + + + + + + diff --git a/WorkerService1/appsettings.Development.json b/WorkerService1/appsettings.Development.json new file mode 100644 index 0000000..b2dcdb6 --- /dev/null +++ b/WorkerService1/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/WorkerService1/appsettings.json b/WorkerService1/appsettings.json new file mode 100644 index 0000000..7b281b0 --- /dev/null +++ b/WorkerService1/appsettings.json @@ -0,0 +1,14 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "Foo": { + "Prop": "Hello, World!", + "Bar": { + "StringProp": "Goodbye, World!" + } + } +} \ No newline at end of file From 87a39d4a783e1feeb8ba72f1a9fcdc80cf66149e Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Sun, 21 Dec 2025 07:47:13 +0100 Subject: [PATCH 26/36] add utilitygenerators.dev --- dist/dev/IMPORTANT_README_IMPORTANT.txt | 2 ++ ...eAnalysis.UtilityGenerators.Dev.1.0.0.nupkg | Bin 0 -> 166416 bytes 2 files changed, 2 insertions(+) create mode 100644 dist/dev/IMPORTANT_README_IMPORTANT.txt create mode 100644 dist/dev/RhoMicro.CodeAnalysis.UtilityGenerators.Dev.1.0.0.nupkg diff --git a/dist/dev/IMPORTANT_README_IMPORTANT.txt b/dist/dev/IMPORTANT_README_IMPORTANT.txt new file mode 100644 index 0000000..3ed96aa --- /dev/null +++ b/dist/dev/IMPORTANT_README_IMPORTANT.txt @@ -0,0 +1,2 @@ +DO NOT DELETE THESE PACKAGES. +THEY ARE REQUIRED FOR BOOTSTRAPPING THE GENERATORS BUILDS. \ No newline at end of file diff --git a/dist/dev/RhoMicro.CodeAnalysis.UtilityGenerators.Dev.1.0.0.nupkg b/dist/dev/RhoMicro.CodeAnalysis.UtilityGenerators.Dev.1.0.0.nupkg new file mode 100644 index 0000000000000000000000000000000000000000..d2a05ac553187bdfecfda9951b812cd7d260b98d GIT binary patch literal 166416 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%0DX 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 a|7S;Kxo^<_Ap!mMf`2vdYvfk|_P+pnBITa| literal 0 HcmV?d00001 From 6f807122595cc5788c5bdf73a35fd68cfb3871bf Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Sun, 21 Dec 2025 07:54:27 +0100 Subject: [PATCH 27/36] emit janus package on debug build to dev --- Janus/Janus.csproj | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Janus/Janus.csproj b/Janus/Janus.csproj index 1d8f1ab..f1df3cd 100644 --- a/Janus/Janus.csproj +++ b/Janus/Janus.csproj @@ -21,6 +21,15 @@ + + $(AssemblyName) + ..\dist\dev + + + + + + From ba3b0ac8461b5386266f62ef53e8bfe7dbc1a25a Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Sun, 21 Dec 2025 08:02:54 +0100 Subject: [PATCH 28/36] use janus directly --- Janus.TestLibrary/Janus.TestLibrary.csproj | 11 +++++++---- Janus/Janus.csproj | 14 -------------- RhoMicro.CodeAnalysis.slnx | 4 ++-- 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/Janus.TestLibrary/Janus.TestLibrary.csproj b/Janus.TestLibrary/Janus.TestLibrary.csproj index 51108bc..8d7cc78 100644 --- a/Janus.TestLibrary/Janus.TestLibrary.csproj +++ b/Janus.TestLibrary/Janus.TestLibrary.csproj @@ -7,14 +7,17 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + + + + diff --git a/Janus/Janus.csproj b/Janus/Janus.csproj index f1df3cd..a79aee6 100644 --- a/Janus/Janus.csproj +++ b/Janus/Janus.csproj @@ -16,20 +16,6 @@ https://raw.githubusercontent.com/PaulBraetz/RhoMicro.CodeAnalysis/master/Janus/PackageLogo.svg - - - - - - - $(AssemblyName) - ..\dist\dev - - - - - - diff --git a/RhoMicro.CodeAnalysis.slnx b/RhoMicro.CodeAnalysis.slnx index f8eb793..6b0120e 100644 --- a/RhoMicro.CodeAnalysis.slnx +++ b/RhoMicro.CodeAnalysis.slnx @@ -4,8 +4,6 @@ - - @@ -21,6 +19,8 @@ + + From f751f5816f9320be01dc227cd0f19ad084723b01 Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Sun, 21 Dec 2025 08:41:40 +0100 Subject: [PATCH 29/36] fix dsl generator --- DslGenerator/DslGenerator.csproj | 109 +++++++++--------- DslGenerator/Lexing/Lexeme.cs | 47 ++++---- DslGenerator/Lexing/SourceText.cs | 12 +- DslGenerator/Lexing/Tokenizer.cs | 70 +++++------ .../Components/EqualityOperatorComponent.cs | 7 +- Janus.Tests.EndToEnd/EqualityTests.cs | 13 ++- Janus.Tests/JanusAnalyzerTests.cs | 19 +++ ...xTests.cs => JanusCodeFixProviderTests.cs} | 2 +- Janus.Tests/JanusTest.cs | 4 +- 9 files changed, 158 insertions(+), 125 deletions(-) rename Janus.Tests/{CodeFixTests.cs => JanusCodeFixProviderTests.cs} (94%) diff --git a/DslGenerator/DslGenerator.csproj b/DslGenerator/DslGenerator.csproj index b109d5c..d66c0b0 100644 --- a/DslGenerator/DslGenerator.csproj +++ b/DslGenerator/DslGenerator.csproj @@ -1,56 +1,59 @@  - - - - netstandard2.0 - false - true - true - true - enable - enable - - - - - - - - true - true - Generates utilities for lexing and parsing domain specific languages, little languages etc. - Source Generator - - - - $(DefineConstants);DSL_GENERATOR - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - + + + + netstandard2.0 + false + true + true + true + enable + enable + + + + + + + + true + true + Generates utilities for lexing and parsing domain specific languages, little languages etc. + Source Generator + + + + $(DefineConstants);DSL_GENERATOR + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + diff --git a/DslGenerator/Lexing/Lexeme.cs b/DslGenerator/Lexing/Lexeme.cs index dede6dc..3a24701 100644 --- a/DslGenerator/Lexing/Lexeme.cs +++ b/DslGenerator/Lexing/Lexeme.cs @@ -8,36 +8,35 @@ namespace RhoMicro.CodeAnalysis.DslGenerator.Lexing; [IncludeFile] #endif [UnionType] -[UnionTypeSettings( - ToStringSetting = ToStringSetting.Simple, - Miscellaneous = MiscellaneousSettings.Default | MiscellaneousSettings.EmitGeneratedSourceCode)] +[UnionTypeSettings(ToStringSetting = ToStringSetting.Simple)] internal readonly partial struct Lexeme : IEquatable, IEquatable, IEquatable { - public Int32 Length => Match( - s => s.Length, - s => 1, - s => s.Length); + public Int32 Length => Switch( + onString: s => s.Length, + onChar: s => 1, + onStringSlice: s => s.Length); + public static Lexeme Empty { get; } = String.Empty; - public Boolean Equals(Lexeme other) => - Match(other.Equals, other.Equals, other.Equals); - public override Int32 GetHashCode() => - Match(v => v.GetHashCode(), v => v.GetHashCode(), v => v.GetHashCode()); + public Boolean Equals(Char c) => - Match( - s => s.Length == 1 && s[0] == c, - thisChar => thisChar == c, - s => s.Equals(c)); + Switch( + onString: s => s.Length == 1 && s[0] == c, + onChar: thisChar => thisChar == c, + onStringSlice: s => s.Equals(c)); + public Boolean Equals(String s) => - Match( - thisString => thisString == s, - c => s.Length == 1 && s[0] == c, - slice => slice.Equals(s)); + Switch( + onString: thisString => thisString == s, + onChar: c => s.Length == 1 && s[0] == c, + onStringSlice: slice => slice.Equals(s)); + public Boolean Equals(StringSlice s) => - Match(s.Equals, s.Equals, s.Equals); + Switch(s.Equals, s.Equals, s.Equals); + public String ToEscapedString() => ToString()? - .Replace("\n", "\\n") - .Replace("\r", "\\r") - .Replace("\t", "\\t") - ?? String.Empty; + .Replace("\n", "\\n") + .Replace("\r", "\\r") + .Replace("\t", "\\t") + ?? String.Empty; } diff --git a/DslGenerator/Lexing/SourceText.cs b/DslGenerator/Lexing/SourceText.cs index 66090bc..a7a3d99 100644 --- a/DslGenerator/Lexing/SourceText.cs +++ b/DslGenerator/Lexing/SourceText.cs @@ -12,15 +12,15 @@ namespace RhoMicro.CodeAnalysis.DslGenerator.Lexing; internal readonly partial struct SourceText : IDisposable { public String ToString(CancellationToken cancellationToken) => - Match( - s => s, - s => + Switch( + onString: s => s, + onStream: s => { cancellationToken.ThrowIfCancellationRequested(); var reader = new StreamReader(s); var resultBuilder = new StringBuilder(); var line = reader.ReadLine(); - while(line != null) + while (line != null) { cancellationToken.ThrowIfCancellationRequested(); _ = resultBuilder.AppendLine(line); @@ -31,10 +31,12 @@ public String ToString(CancellationToken cancellationToken) => return result; }); + public static SourceText Empty { get; } = String.Empty; + public void Dispose() { - if(TryAsStream(out var s)) + if (TryCastToStream(out var s)) s.Dispose(); } } diff --git a/DslGenerator/Lexing/Tokenizer.cs b/DslGenerator/Lexing/Tokenizer.cs index f319888..17f2521 100644 --- a/DslGenerator/Lexing/Tokenizer.cs +++ b/DslGenerator/Lexing/Tokenizer.cs @@ -5,24 +5,24 @@ namespace RhoMicro.CodeAnalysis.DslGenerator.Lexing; using static RhoMicro.CodeAnalysis.DslGenerator.Analysis.DiagnosticDescriptors; using RhoMicro.CodeAnalysis.DslGenerator.Grammar; using RhoMicro.CodeAnalysis.DslGenerator.Analysis; - using static Lexemes; #if DSL_GENERATOR [IncludeFile] internal #endif -partial class Tokenizer + partial class Tokenizer { [UnionType] private readonly partial struct TokenOrType; + public static Tokenizer Instance { get; } = new(); public TokenizeResult Tokenize(SourceText sourceText, CancellationToken cancellationToken #if DSL_GENERATOR - , String filePath = "" + , String filePath = "" #endif - ) + ) { cancellationToken.ThrowIfCancellationRequested(); @@ -32,7 +32,7 @@ public TokenizeResult Tokenize(SourceText sourceText, CancellationToken cancella var isUnknown = false; var (start, current, line, character) = (0, 0, 0, 0); - while(!isAtEnd()) + while (!isAtEnd()) { cancellationToken.ThrowIfCancellationRequested(); scanToken(); @@ -45,7 +45,7 @@ public TokenizeResult Tokenize(SourceText sourceText, CancellationToken cancella void scanToken() { var c = advance(); - switch(c) + switch (c) { case Equal: addToken(TokenType.Equal); @@ -58,9 +58,7 @@ void scanToken() break; case Alternative: //check for incremental alternative "/=" - var type = match(Equal) ? - TokenType.SlashEqual : - TokenType.Slash; + var type = match(Equal) ? TokenType.SlashEqual : TokenType.Slash; addToken(type); break; case GroupOpen: @@ -92,7 +90,7 @@ void scanToken() break; case CarriageReturn: closeUnknown(); - if(lookAhead() == NewLine) + if (lookAhead() == NewLine) advancePure(); addNewLine(); break; @@ -103,13 +101,15 @@ void scanToken() consumeWhitespace(Tab); break; default: - if(isAlpha(c)) + if (isAlpha(c)) { name(); - } else if(isDigit(c)) + } + else if (isDigit(c)) { specificRepetition(); - } else + } + else { openUnknown(); } @@ -138,7 +138,7 @@ void specificRepetition() { closeUnknown(); - while(isDigit(lookAhead())) + while (isDigit(lookAhead())) advancePure(); addToken(TokenType.Number); } @@ -159,14 +159,14 @@ void regressPure() void closeUnknown() { - if(isUnknown) + if (isUnknown) { - if(!isAtEnd()) + if (!isAtEnd()) regressPure(); isUnknown = false; addToken(TokenType.Unknown); diagnostics!.Add(UnexpectedCharacter, getLocation()); - if(!isAtEnd()) + if (!isAtEnd()) advancePure(); } } @@ -177,9 +177,9 @@ void addToken(TokenOrType tokenOrType) { closeUnknown(); - var token = tokenOrType.Match( - token => token, - type => new Token(type, getLexeme(), getLocation())); + var token = tokenOrType.Switch( + onToken: token => token, + onTokenType: type => new Token(type, getLexeme(), getLocation())); tokens!.Add(token); resetLexemeStart(); } @@ -196,9 +196,11 @@ void discardToken() Lexeme getLexeme() => new StringSlice(source!, start, current - start); - Char? lookAhead(Int32 lookAheadOffset = 0) => current + lookAheadOffset >= source!.Length ? null : source![current + lookAheadOffset]; + Char? lookAhead(Int32 lookAheadOffset = 0) => + current + lookAheadOffset >= source!.Length ? null : source![current + lookAheadOffset]; - Char? lookBehind(Int32 lookBehindOffset = 0) => current - lookBehindOffset < 1 ? null : source![current - 1 - lookBehindOffset]; + Char? lookBehind(Int32 lookBehindOffset = 0) => + current - lookBehindOffset < 1 ? null : source![current - 1 - lookBehindOffset]; Location getLocation() => Location.Create( line, @@ -207,11 +209,11 @@ Location getLocation() => Location.Create( #if DSL_GENERATOR , filePath #endif - ); + ); Boolean match(Char expected) { - if(isAtEnd() || source![current] != expected) + if (isAtEnd() || source![current] != expected) return false; current++; return true; @@ -230,13 +232,13 @@ void comment() discardToken(); //discard hash token advancePure(); //consume hash - if(isAtNewLine() || isAtEnd()) + if (isAtNewLine() || isAtEnd()) return; - while(!isAtNewLine() && !isAtEnd(lookaheadOffset: 1)) + while (!isAtNewLine() && !isAtEnd(lookaheadOffset: 1)) advancePure(); - if(!isAtNewLine()) + if (!isAtNewLine()) advancePure(); //consume last comment char addToken(TokenType.Comment); @@ -246,7 +248,7 @@ void consumeWhitespace(Char expected) { closeUnknown(); - while(lookAhead() == expected) + while (lookAhead() == expected) advancePure(); addToken(TokenType.Whitespace); @@ -256,9 +258,9 @@ void terminal() { discardToken(); //discard quote token var containsCharacters = false; - while(( lookAhead() != Quote || lookBehind() == Escape ) && !isAtEnd()) + while ((lookAhead() != Quote || lookBehind() == Escape) && !isAtEnd()) { - if(lookAhead() == NewLine) + if (lookAhead() == NewLine) { line++; } @@ -267,19 +269,19 @@ void terminal() containsCharacters = true; } - if(containsCharacters) + if (containsCharacters) { addToken(TokenType.Terminal); } - if(isAtEnd()) + if (isAtEnd()) { diagnostics.Add(UnterminatedTerminal, getLocation(), getLexeme()); return; } //add empty token - if(!containsCharacters) + if (!containsCharacters) { addToken(TokenType.Terminal); } @@ -293,7 +295,7 @@ void name() { closeUnknown(); - while(isAlpha(lookAhead())) + while (isAlpha(lookAhead())) advancePure(); addToken(TokenType.Name); diff --git a/Janus.Analyzers/Components/EqualityOperatorComponent.cs b/Janus.Analyzers/Components/EqualityOperatorComponent.cs index ed11b31..fda9b42 100644 --- a/Janus.Analyzers/Components/EqualityOperatorComponent.cs +++ b/Janus.Analyzers/Components/EqualityOperatorComponent.cs @@ -15,13 +15,10 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation if (Model is { Settings.EqualityOperatorsSetting: EqualityOperatorsSetting.OmitOperators - } or not - { - Settings.EqualityOperatorsSetting: EqualityOperatorsSetting.EmitOperators - } and not + } or { Settings.EqualityOperatorsSetting: EqualityOperatorsSetting.EmitOperatorsIfValueType, - TypeKind: UnionTypeKind.Class + TypeKind: not UnionTypeKind.Struct }) { return; diff --git a/Janus.Tests.EndToEnd/EqualityTests.cs b/Janus.Tests.EndToEnd/EqualityTests.cs index 834c383..19a6763 100644 --- a/Janus.Tests.EndToEnd/EqualityTests.cs +++ b/Janus.Tests.EndToEnd/EqualityTests.cs @@ -10,11 +10,22 @@ public partial class EqualityTests [UnionType] private sealed partial class Foo; + [UnionType] + private readonly partial struct StructUnion; + [Fact] - public void EqualisIsValueEquality() + public void EqualsIsValueEquality() { Foo expected = 32; Foo actual = 32; Assert.Equal(expected, actual); } + + [Fact] + public void StructUnionHasEqualityOperator() + { + StructUnion expected = 32; + StructUnion actual = 32; + Assert.True(expected == actual); + } } diff --git a/Janus.Tests/JanusAnalyzerTests.cs b/Janus.Tests/JanusAnalyzerTests.cs index 89fc60e..81fe363 100644 --- a/Janus.Tests/JanusAnalyzerTests.cs +++ b/Janus.Tests/JanusAnalyzerTests.cs @@ -2,8 +2,27 @@ namespace Janus.Tests; +using Microsoft.CodeAnalysis.Testing; +using RhoMicro.CodeAnalysis.Janus; + public class JanusAnalyzerTests { + [Fact] + public Task StructUnionHasEqualityOperatorEmitted() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + + [UnionType] + partial struct Union + { + public static bool operator !=(Union a, Union b) => true; + } + """, + DiagnosticResult + .CompilerError("CS0111") + .WithSpan("RhoMicro.CodeAnalysis.Janus.Analyzers/RhoMicro.CodeAnalysis.Janus.JanusGenerator/Union.g.cs", 1387, 33, 1387, 35) + .WithArguments("op_Inequality", "Union")); + [Theory] [InlineData("None")] [InlineData("Simple")] diff --git a/Janus.Tests/CodeFixTests.cs b/Janus.Tests/JanusCodeFixProviderTests.cs similarity index 94% rename from Janus.Tests/CodeFixTests.cs rename to Janus.Tests/JanusCodeFixProviderTests.cs index 483edb6..e9016c8 100644 --- a/Janus.Tests/CodeFixTests.cs +++ b/Janus.Tests/JanusCodeFixProviderTests.cs @@ -8,7 +8,7 @@ namespace Janus.Tests; using Microsoft.CodeAnalysis.Testing; using RhoMicro.CodeAnalysis.Janus; -public class CodeFixTests +public class JanusCodeFixProviderTests { [Fact] public Task ClassUnionsShouldBeSealed() => diff --git a/Janus.Tests/JanusTest.cs b/Janus.Tests/JanusTest.cs index a9cbde9..9d82255 100644 --- a/Janus.Tests/JanusTest.cs +++ b/Janus.Tests/JanusTest.cs @@ -24,10 +24,10 @@ public static Task TestCodeFix(String source, String fixedSource) .WaitAsync(TestContext.Current.CancellationToken); } - public static Task TestAnalyzer(String source) + public static Task TestAnalyzer(String source, params DiagnosticResult[] expected) { return CodeFixVerifier - .VerifyAnalyzerAsync(source) + .VerifyAnalyzerAsync(source, expected) .WaitAsync(TestContext.Current.CancellationToken); } From 49d85fa6bdca0e7f0cb58d14c23f8ce031c5c42e Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Sun, 21 Dec 2025 09:31:40 +0100 Subject: [PATCH 30/36] allow user provided equality operators --- DslGenerator/Lexing/Lexeme.cs | 2 + .../Components/EqualityComponent.cs | 110 +++++++++++------- .../Components/EqualityOperatorComponent.cs | 9 +- Janus.Analyzers/Models/UnionModel.cs | 54 ++++++++- Janus.Tests/JanusAnalyzerTests.cs | 21 +++- ...Analysis.UtilityGenerators.Dev.1.0.0.nupkg | Bin 166416 -> 166664 bytes 6 files changed, 141 insertions(+), 55 deletions(-) diff --git a/DslGenerator/Lexing/Lexeme.cs b/DslGenerator/Lexing/Lexeme.cs index 3a24701..75b33a8 100644 --- a/DslGenerator/Lexing/Lexeme.cs +++ b/DslGenerator/Lexing/Lexeme.cs @@ -18,6 +18,8 @@ namespace RhoMicro.CodeAnalysis.DslGenerator.Lexing; public static Lexeme Empty { get; } = String.Empty; + public Boolean Equals(Lexeme other) => Switch(other.Equals, other.Equals, other.Equals); + public Boolean Equals(Char c) => Switch( onString: s => s.Length == 1 && s[0] == c, diff --git a/Janus.Analyzers/Components/EqualityComponent.cs b/Janus.Analyzers/Components/EqualityComponent.cs index 0cd6488..03bb174 100644 --- a/Janus.Analyzers/Components/EqualityComponent.cs +++ b/Janus.Analyzers/Components/EqualityComponent.cs @@ -20,62 +20,82 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation $$""" {{Inheritdoc()}} public override bool Equals(object? obj) => obj is {{new UnionTypeNameComponent(m)}} other && Equals(other); - - {{Inheritdoc()}} - public bool Equals({{new UnionTypeNameComponent(m, RenderNullable: true)}} other) - { - {{ComponentFactory.Create(m, static (m, b, ct) => + """ + ); + + if (!m.IsEqualsUserProvided) + { + b.Append( + $$""" + + {{Inheritdoc()}} + public bool Equals({{new UnionTypeNameComponent(m, RenderNullable: true)}} other) { - ct.ThrowIfCancellationRequested(); + {{ComponentFactory.Create(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + if (m.TypeKind is not UnionTypeKind.Class) + { + return; + } - if (m.TypeKind is not UnionTypeKind.Class) + b.Append( + $$""" + if(other is null) + { + return false; + } + + if(object.ReferenceEquals(this, other)) + { + return true; + } + + + """ + ); + })}}if(Variant != other.Variant) { - return; + return false; } + + {{new VariantsSwitchComponent(m, static (v, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); - b.Append( - $$""" - if(other is null) - { - return false; - } - - if(object.ReferenceEquals(this, other)) - { - return true; - } - """ - ); - })}} - - if(Variant != other.Variant) - { - return false; + b.Append($"return global::System.Collections.Generic.EqualityComparer<{v.Type.NullableName}>.Default.Equals({new VariantAccessorComponent(v)}, {new VariantAccessorComponent(v, InstanceExpression: "other")});"); + })}} } - - {{new VariantsSwitchComponent(m, static (v, _, b, ct) => - { - ct.ThrowIfCancellationRequested(); + """ + ); + } - b.Append($"return global::System.Collections.Generic.EqualityComparer<{v.Type.NullableName}>.Default.Equals({new VariantAccessorComponent(v)}, {new VariantAccessorComponent(v, InstanceExpression: "other")});"); - })}} - } + if (!m.IsGetHashCodeUserProvided) + { + b.Append($$""" - {{Inheritdoc()}} - public override int GetHashCode() - { - {{new VariantsSwitchComponent(m, static (v, _, b, ct) => - { - ct.ThrowIfCancellationRequested(); + {{Inheritdoc()}} + public override int GetHashCode() + { + {{new VariantsSwitchComponent(m, static (v, _, b, ct) => + { + ct.ThrowIfCancellationRequested(); - b.Append($"return {typeof(HashCode)}.Combine(Variant, {new VariantAccessorComponent(v)});"); - })}} - } + b.Append($"return {typeof(HashCode)}.Combine(Variant, {new VariantAccessorComponent(v)});"); + })}} + } + """ + ); + } - {{new EqualityOperatorComponent(m)}} + b.Append( + $""" - {{new ToStringComponent(m)}} - """ + {new EqualityOperatorComponent(m)} + + {new ToStringComponent(m)} + """ ); }); diff --git a/Janus.Analyzers/Components/EqualityOperatorComponent.cs b/Janus.Analyzers/Components/EqualityOperatorComponent.cs index fda9b42..64460b5 100644 --- a/Janus.Analyzers/Components/EqualityOperatorComponent.cs +++ b/Janus.Analyzers/Components/EqualityOperatorComponent.cs @@ -14,11 +14,16 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation if (Model is { - Settings.EqualityOperatorsSetting: EqualityOperatorsSetting.OmitOperators + AreEqualityOperatorsUserProvided: true } or + { + Settings.EqualityOperatorsSetting: EqualityOperatorsSetting.OmitOperators + } + or { Settings.EqualityOperatorsSetting: EqualityOperatorsSetting.EmitOperatorsIfValueType, - TypeKind: not UnionTypeKind.Struct + TypeKind: + not UnionTypeKind.Struct }) { return; diff --git a/Janus.Analyzers/Models/UnionModel.cs b/Janus.Analyzers/Models/UnionModel.cs index 1a86e2c..f8044ba 100644 --- a/Janus.Analyzers/Models/UnionModel.cs +++ b/Janus.Analyzers/Models/UnionModel.cs @@ -8,6 +8,7 @@ namespace RhoMicro.CodeAnalysis.Janus; using Library.Models; using Library.Models.Collections; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; internal sealed partial record UnionModel( UnionTypeKind TypeKind, @@ -19,6 +20,9 @@ internal sealed partial record UnionModel( EquatableList TypeParameters, UnionTypeSettingsAttribute.Model Settings, Boolean IsToStringUserProvided, + Boolean IsEqualsUserProvided, + Boolean IsGetHashCodeUserProvided, + Boolean AreEqualityOperatorsUserProvided, String DocsCommentId, Boolean EmitDocsComment) { @@ -114,8 +118,51 @@ public static UnionModel Create( AppendContainingType(t, containingTypes, in ctx); } - var isToStringUserProvided = - target.GetMembers(nameof(ToString)).Any(m => m is IMethodSymbol { Parameters: [] }); + var (isToStringUserProvided, + isEqualsUserProvided, + isGetHashCodeUserProvided, + areEqualityOperatorsUserProvided) = (false, false, false, false); + var members = target.GetMembers(); + + foreach (var member in members) + { + ct.ThrowIfCancellationRequested(); + + switch (member.Name) + { + case nameof(ToString): + if (member is IMethodSymbol { Parameters: [] }) + { + isToStringUserProvided = true; + } + + break; + case nameof(Equals): + if (member is IMethodSymbol { Parameters: [{ } singleParameter] } + && SymbolEqualityComparer.Default.Equals(singleParameter.Type, target)) + { + isEqualsUserProvided = true; + } + + break; + case nameof(GetHashCode): + if (member is IMethodSymbol { Parameters: [] }) + { + isGetHashCodeUserProvided = true; + } + + break; + case "op_Equality" or "op_Inequality": + if (member is IMethodSymbol { Parameters: [{ } firstParameter, { } secondParameter] } + && SymbolEqualityComparer.Default.Equals(firstParameter.Type, target) + && SymbolEqualityComparer.Default.Equals(secondParameter.Type, target)) + { + areEqualityOperatorsUserProvided = true; + } + + break; + } + } var emitDocsComment = target.GetDocumentationCommentXml(cancellationToken: ct) is null or []; @@ -129,6 +176,9 @@ public static UnionModel Create( TypeParameters: typeParameters, Settings: settings, IsToStringUserProvided: isToStringUserProvided, + IsEqualsUserProvided: isEqualsUserProvided, + IsGetHashCodeUserProvided: isGetHashCodeUserProvided, + AreEqualityOperatorsUserProvided: areEqualityOperatorsUserProvided, DocsCommentId: docsCommentId, EmitDocsComment: emitDocsComment ); diff --git a/Janus.Tests/JanusAnalyzerTests.cs b/Janus.Tests/JanusAnalyzerTests.cs index 81fe363..1f4f82b 100644 --- a/Janus.Tests/JanusAnalyzerTests.cs +++ b/Janus.Tests/JanusAnalyzerTests.cs @@ -7,6 +7,18 @@ namespace Janus.Tests; public class JanusAnalyzerTests { + [Fact] + public Task EqualsIsNotOverriddenIfUserProvided() => JanusTest.TestAnalyzer( + """ + using RhoMicro.CodeAnalysis; + + [UnionType] + partial struct Union + { + public bool Equals(Union other) => false; + } + """); + [Fact] public Task StructUnionHasEqualityOperatorEmitted() => JanusTest.TestAnalyzer( """ @@ -15,13 +27,10 @@ public Task StructUnionHasEqualityOperatorEmitted() => JanusTest.TestAnalyzer( [UnionType] partial struct Union { - public static bool operator !=(Union a, Union b) => true; + public static bool operator !=(Union a, Union b) => throw null; + public static bool operator ==(Union a, Union b) => throw null; } - """, - DiagnosticResult - .CompilerError("CS0111") - .WithSpan("RhoMicro.CodeAnalysis.Janus.Analyzers/RhoMicro.CodeAnalysis.Janus.JanusGenerator/Union.g.cs", 1387, 33, 1387, 35) - .WithArguments("op_Inequality", "Union")); + """); [Theory] [InlineData("None")] diff --git a/dist/dev/RhoMicro.CodeAnalysis.UtilityGenerators.Dev.1.0.0.nupkg b/dist/dev/RhoMicro.CodeAnalysis.UtilityGenerators.Dev.1.0.0.nupkg index d2a05ac553187bdfecfda9951b812cd7d260b98d..fa86fdbd4992bc440dc7715061152ce8c20af725 100644 GIT binary patch literal 166664 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/@tHkYiHvQD3{);A5Wrw{zPT1vK8d*gY`!(Z znLfDIeA;-uK}9#Ts4di=2f8#;q2jqnKnJN>r0ij^&VE@zP^If+{1=xEbT1&PZAyWp zGaw|gt^WGYA^qHnQ+&;W&L5AovYZ#_5iRNazxHC{AaX7#MwY5Q{kA;8tP1Pq1HK}4 zyF6(}(1!7zQ6RHl42IMjI5&^?SVIS~5rP+jD4gkQV^!0r3wY{2i!Je7O8UMX0&Ukq zENo*TydqsVuhEDW?{SkxdrBkO*3_!&N^!S_&u+o5+TNjC-l4ygb<0`P0jYAffV5@x zs@CwC)^HjS_pOO;A6bC1YrA>Lw#aM5)9kaSa>mA0Y#A?_bqW`rEW(n}#qPM`*@s=@ z>izTpJ<^ zf8ed}R}9&T@Pw;p8iiACJY-x4AP2kqY#sd!uG?Z{`^iDK#63GM;CpNQ-zj%_K;}Zm zR2thq$BGPGCgGuW42nPK^W^qC^>t&nb|W$5(_{4>^KP7WBcQL++=+30UfE|}my0p( zZ85$}pMhhYqP*mc@ey2Zb7|Wzxu|X{FeCOM@9sB0y#$so8N*2O-x)sQ_sv&X_0)6B zTwaye`y)>fQzt(x8P*)D6TBZy&7C~m!u&AE^C8HSm2wR>6mAUJ_mDDy)!E*i@h3n9 z6QJ~hh)=Xy*cSRcRMwaJ(EeO`+v91EErDJN2=jIWa%#Qg=Ps*PpUk5%x&!t^QvvUH z(*P5oXzY0QelnPmSFiGxuRIFWD{^u!%8gw@1OSq(D{B+zmnA-ZqrY;)w%HO4e0Ds4 zv-<peTR~OsI<661>0r zi0%WQmG}wfSkE!42htEg*A1r*q4^YaD(eBLds$bk5pyjhJ>;vYUus&t$ zx~F`leQMR4y053SX6AqSC4I2nHg{^zTG|hxNl0woOpC2)O-YtM%|<0$cuc#H?Q&du zX};V2kerk(`%=W?;*8hBZ@6F7RD9|*#vm@iZ9{+)$S;8Z8rt@nEzX^dz!-Q3_p1D& z4a%D$_C1nUlJ6!n?f;SE2beAmdu6d0b0!WOM;Wp;O~ujKH&Y>H`x0b$)&aCZvCU|} zGyysxw&Ph35jR*&PwMVt;$ z=t4agYNz{N029{%o-o2IkLYBqko?(P%euaEQ{F!5*&Nn{b@{B#%1tHe5a?*(-DG+o zhTH%SU_CYF6gHq8ftmhzyTk_=j|5=Yz83#eK0F2ggF&TJu0h(_78OV_?W}JBx%XXo zKVb&9p)D%ZtU@(59)C7fVOTE`++(9owpE~va45Xi_DrkYHXYDiRmm3xP_3X71FW52P`;)5gNZAks{!gbpEr2^X>>-CJ= z6}DyqZvqmN)i=&4d-NuuFU}mIcaLsf%^#B1{@=+>16ND>uypA@1J%)PN!H>`(Qc1* z@#j1V9SF{QzY{pVS3hDiFB0#z7(XfkL=w+kTdG~aHF`dksk8u3OPLKd^p7|~rP}%e zk`VzdS@gkodU*_Aztl6OI?h{e&8S@RLFtp}lNkyZFsrr$ra{GF=z(10p4;LGt4Y$A zc{MEcZgPWh{(g;Gu9cad>mpFIk`mW4f_=gfl}G&)2FkJi`g!^DD$<(4THVS)-(I)W zF}nz%D`{csCcjB6DvO#lP21Ea#fH6h)PX4O=Vwq30GLiLK@%vd@WpIByioe03U+Q*8%H;Cf2c^|mu>FWZ;0gEqzQ7gew$RNPUZw0xj zkN{;;jEeC!ZMh36*3jq$tB@R9aw(*jf674jU*-6u5UMGy+OCyNT1!0Hm*%J2?u1S_ zv+Gd~`sV-_lISBzoS_jX%<|7=--vNTd|Rhiaa$~gf6Z|~Y?=mW8{Y}Zz?+Yylb-J! z%KEYQ_Q@+BQgPYYNKukA@EQVz+C7Jt*SB{$fbQJ@ta6@Lr20psdRE6%g{RKvawCnNyx33^Fm-{uFYA1m3GOc{q!d!)g+jX%m)Xt z_aR%d9F~=8ZHnzn6f?H#$?qZR_y}5)9*)7w*iC6D9UxKw6e_ChDWP19h zc|ZAO@%;Mdtu@)xpg}Z?016*urz}0HritExdShE?pkg2n>`&^|lsQRovi$c2m6ng{ z?>5vMhg2s5+6KL=cblDxcV@I;!kxLtwQh3aGxn%CwV9d^Zf{nQHa})g#eoXeYly%h zRt@l$L;!K<(%I;$f-RTf|M2H}70T6R8(W6cyPCg-se*F-UQ>UIIQP3%0*AnVHd9V$M3>X*qlcw{yA`FQ8Smgoo zhhz-@P$6+lH{kyuFKhOqG12{b^8$I!7gU6tF>KW%)ji#vVGZvyRcuFSAj6-_;^%|Z z>+9<8eG!EHZ9U|Q)00+_)@h7&zi+DjAmNQI-{wV?UF7ZS>GjtCTi4GfVev0+*7#!2 z@E`9hjsZK{WbcV&8{q8u_@#gda0V*%f+FHUoPpe06J;)^brEihsxeZ&H(5Q@8dtwIxi>-vjh;k>9V!^%JwhMB9rZ+h%Y%F zMtk7>@OE3-HEw{_3(C4%yxjuDb$a@!A?<%JgH-_yd(2e;7&fAs>M%i5d}3{Xs+R9{ z@X(Uw)qRw5(A^U}=O}wun_Jkoh@daq5c`8)jX9mGV|Ri4syY?Jz4k*G207JpGY1lF zU^hVjbYDUQC}9v51Th28U6xutG`!1Ab7lzMJfkbEV+y!gN@TpwM~H#UW_)Zyl(fA(oG0pVADqNb;}_O1UNju6n>YC+NP6na!WGpar| zK32a%g_R%g82S}q?Z%t?PkSB#{_{WfYu!=r>Fc%tIvC*at1m4*hn59wK%%7hj~~~3 zcFtr^3z&<_RD#6;QiAgfQ1WGL9W^7xYvZ@o8#4iLaAZSknZl8NxLkOj=!sT0pvunw znVnX#y4CBR&jI7oN}{1{Mx-6*X)? z1Sjd^O$wwieMM7mm^B9=28+v0tv`{$$u=&ot(r5)Lp*yI5Kr&_HwXBZ08m2$nmmvv z%?8K?4ZkEm-z2xkWQIncVGIQpBrw37p+Kb2VE{Hu)WL;g!>e0GN&x&D@w>HUX4 z425NfbFlyhtOAN3o8(P00QjB1bnXQwzR|2mppFAby$a~gQpBMF{y0l`K?gC=U53kq zTmg|250DR#9pzX}i2-yx5Oo~Di8C<-#eSyPA?c5 zjPY1X@FUo8_z$Hhtz}Ab4R}YEPGVwjL!T%Rwtm@kfFJT;P&L8OmCwurQd9@y-$@KB zR097A(b;|l48SPwbjWPw@DvhR{1!(_PmX7hzxd*$P9oK376wwWctG2yY^WCjU4djD zeoe6gn4|jfTntwg=U~h48Qu58lJ1#;&kePChizfv`+|E(9-h~<>QpEL*nFRhoQGC{ zt$V`>8>=+~k0ISX0j|-6RKQAjkS@UYOU8dOTsZ}j!*RV`*6UGDx0o(-eECdT9#({* zxeIK)qTa;o0}8VGdpAZ;KXo))1V}v8O7r?-3h=c!oy0el*weq%Yr3aN2LJG(Ot=pB zBKaTX8K}YR=HrvQ0H4(E0a_2}&C8@Wb`SI*19oeOZF&rkp_QGA@ZY8c>&Mr7nOFUQ z5<&Njv1PE!_ReArqOet+0~0h&P#Q`exCQ9q-F>ag`Ed8ehueIT!@y-Tq`u`N>6?w> zV?g0Ew_pXp6v;)*(@yf^geJ!=8=whyk7*L{IZOYQ-(C78LQH{~wlrQSG0kZD^NNeu zGMGM^OpJDsT-3k?{atdBX{H-|3?4jM?b7v48A`C;;G<{NPFeyS0b zD7kqSAdg&OLj_q5N}owjH4aOk-S(}CU!$GSyMjG$+t8<}Qoa z2;5_PPw6G8Fg*GymEZt5LsL5i0pH=wyIXM@dOMPtP2^3gUB=JoBhE zbID@LQb)Ia8VFz=k3HP}-RpW);@+9LE*2J7D_R=PaDNiHQB`g3VIwohYJb1RxvU+2 zcW?+nTV$WTlnag9mMG2eT6qZTIXpL6Ld^Gi5BcVz4w>1W>N+JqSs6Nlo+dq6kgscWb;8P?`3OM!h<|2U_2DD_QS0=0s)l2^LG#JPopFi&$%llDkIGPO9tu zJ$V>)21za8!`dNjbMyLJEFZ0-WG7wpPoh2~I?4*FKl+2Sk8ZcXx9pJGSv>`vCMA}Y zA%rkAvozS9da~GFbcBj6nTG*C$w*rgPeFgceZF-r{psz>u+xhmiJEW{@;r5fXHkUd zkkg9?fL-`kQ5#AOy`vZUg6cKd!|o3TbCu%sIIO+CUS5BO2e8a#nJjj?*KFmrn;FQX zOqM$RHS5@Z`!fGN&we4DV~79a5-n8HlX5bFkoh5{w{jx-0iA_=Zc+g`LWX`}JmYMK z=41?clMe#_f0~yjQZh`sot1$#S#mFIec@a&ZK-FlE42-n^qYK@zQLQclOaTq)0A%` ziwKIi=GUNKpLh|vDQozm4`EL$v7P?t+x5n!kf2n}d}nOt&kNbmJW~4u_|Q=aL-uu9 zd~W{*ENQ>j{kM)q{I~41CBzZr6rFs~B=E<}taCg5PK^xkOV5nRSI+zx9V{lZkxF+ulCi~Z^(~p2@;gWLFA9OXXnQJns|I8sVmG)$c zc>B2ta5e;U`U5!OwG;h;i(mfagO3pQ_M~@fE?Qm`Nyz_Ow%bAKIM}D5;g5&?e!AZu z^b*{+$$jCtegKt-Sm{3&Eq%H!QelX@e6rGm2`ZaHtfAisqw4PAUB&Nz-jug;&o@`V z*V20O=|Ph{7W&WAP|7DvIq_O>_}5i*=$dEB#o+(XnY5haqwWn?+#8O+cip1-4J%ob zagw+tJ&2*5)c)r86Jz+ag{;vWv4TrxkdJTQ&+ffI01Z#vlB;a6@2V|&AN4txmqzye{@@5b(>{)6yX`yr(nLOE3 z+7I(gPX+&Y{VaRl={Ep>RJK&Sb(e`};;IyLxt96d&-^Q5Fzv}okifpS;L3wVW9Jpz z!2=7+`a^%~AQ`OYH$NoCX#xg#(E{C!p23j7X{r5WRWJ~{J3uo-1F!Mt@8%$z>K>ei z|F8^CV|m~#+#gq#_X@k zwI@ww{BzZdA@^g()8}1&E$`1>twVljf5T#*mDi_Y+f%0qYSMQn1f@z4mTjsNsI~e^ zpmw3Y^b2+Kx8Z)hu|&b-;Z@)N;=sjz?|CnZ$k0P7k4Xo)5Xf3%*6Y-mg-+i!L0&ZH zA`?!sEw^#-#I36#ggp%D57#wCUi6%tmbkwt_>xJ!EzP#~N3OZoMbAO{c?$Z7XTrC# zEz|UW1`*PkAKYCW+}^i<5fO z%@HT8lg2e>L+*Eh=r}w02RiPggVTN3HMMr;U)5h`$y0eI;7+T^dM4?1lg0Xkv%8b8 z9~`27K4Rue-F~vZw%`2OW!dl5^63-&0dpKu@1I@ZKJTquiPr3|G*p@mmDvR0xg}Mg zWvM-W(F(`daC**F8Rrcpt{Q+%=3n}dxO_>h!Yd2*ROs3N!Y-O?M3px?!_$)>;loZ zRGorLpP4&{>ma@1d(%I$^fl>?;f?Wr(NVqU9uqcUS#_i}pH1T6I#=^mjbb@=T&x-5J+Sd;WQ?h zM9V>uOAhi#WGN6e3?c_X850P5UJF}U39M>x>kIq)0NMFQ;efSGX_r^e(}&7>2X~NV z_75ZuZ_VMiU2ikK#1dK9!2qeT_taqNY#bqeTFD03tv&*0((`(!_us}ca~U%ggTSyA zKQhM zh;zSKQ=h*5_IQT>SXP_AlDt(4Bm3`f7T5yq6MC}!MiN_88v z&D)+T?3hf` zSznU|^c}};hHWNq#%hx6v$nx5(Fb{*!Q|HhX6XcoU4-?z?)pPC1)3VoiPl0}qkZfj zsaSM3M21tks4&NmsC&oh#4$5Ui@UqqK3&E1y-8^1T>2nKb)mWHl7N#jl_~UMoGAJ6 zTRR87}fw!W64y(%&`)n@RS)|=JU)*s2I;d{m zwte(GP1_vQ1=;#X{P;a zuyGI9-07T@?Xd>s@-$6q#MLsX%de$*TluLi!)v-iu z!MG7O6d5FN`gljZs;42&Eoa4j#W|LVsa|UD<$Qkw+lp`x1F3v$Od_}3)bFf7o6~H>u;OBmU=GoJmY(*{7-0a+YsyS^y>7V>FtMq&j;Lk z4zpH1+ti_P^kf zd`&U)JbQfe$fhNq;GL`aH#)a9W3_a25L(Qc55=qG+^qk;iSmltzMH2L2tAzWzUFSr z@#Q~kk6K8?@R}wS@eJwsLpPJYrFh~W)*cmb%WmSL_#_NAdmbJl7dI5KQuNavUT9vS8?6d|RWc$hL5`Fjj*hNgPrrYNs}^ zlRS{D3eh%9EuK%P7G6t8W`XDx@96sR*trZ`C5J-Pfp;feK-bKo6CE|)UkBDJz~jtmgxU_>u9{LB+G!w&{9)A!;z2!{H#{_zuB(0WTKpeEELg?Jyq1heiGhA zM5+)raZ$;SGPI7|Y#jwgk7o&@45vR<2|n&rP_d!FbVNU#whCtve)tj0XFw6J(;!Mn z(m<)%r+%x5{mxAdbun>Kf{X@+ne=e8CsyElZUo;%L1K{SJLxQaXfz%bLvW}Z&-Pe`3fM>|ls)+JWMn>gD$67ZO zm?+e;tcr*{KVwI7SQSKOW8uf_g5zMO`#%-QIdWOm#O2>TnSJNvPaXP7;Qh#hd4)N# zZ0}`Wf991<5}hx6Vi~!ePy88wZ!#dh}5n-FOw(H{-`PQXSPEqE{w~QjBNyYE`oa(zahn3ymTmr3(?z< z(;c}Ps22P={Hjy4DNBgYJN&En5J$_AUjcgT&_5wfSm1iT>g}p?U>J@JQ?Ixx}%IRS1t`{ zHjaDqhGV__)r7(*#mrHuQ%iUC^tFTY zDo)Spm-j=(TdEl!UZ;-}8eRQH?YdtF73y_y&Y+u(g})IQ4OJNKH#ad(8?y%;+nr2w zyoVfNN+Dyvzb;DYym3hOmluDYns)Ty2|)$ zC0L;CKl3EhAMB7jvG8asC;H*^kWb#Y@93|2^*pW6ptN6gpnIg?nN!-@qt|wg?kR%7 zSv-TRh1K)=9D`Z%)d=mhHF5#FS2_u6eK+0Xw4|=|@7OMQRWVkUJ#~ER_WRC#1UpUOgv{^JFkuBd*u|?#g(;UaQ-AXeNP(!=dOD8)P1Wz*TOqY~XTV?vh zE9(V=ckT@~KY0`(Ty0k7I5u7_{Ur6wGqo^SWr++co9117Tz(@k$?Lmcv$@bdFsA)p z@{Rk(;@4)abwnoBT#f5m&e-SYho=a}jHIsD<>y#=K3xOjZS$4aZ%s?8=c_DIwI{Yi z&icldlyFTY-+eZltkpgU3QQbL8nFd8T3Nq&%_iuvUv@+1LojmOrDk&CznHy6?%uYe zKa9(lO?-~Po0{a7W`}_n96zsUcJ|$7+udI{RJ`N*A}sKF`{cX%uHTEW7VIL6<%HQQ zw8;Itmy-<71LAP zhTTzM!rGr+Ug75I{R{NaQuP^!-s|AYE4!_&=^XLalQ;t7G++-*oW_!}mAFNZzD zsJoq4g@13wNniZ1Q|OeO(XDvpHTagp!tc#Vxdl<6F?U5g^+Y`eX7c0Dwg0Y*$Cz^U z-Xn7P%G>7_Z_qaTUEiS<$!}a*ufFz{+jRu}wn#jjKgEc%>+kmG$uBycNI$#W6SoQ+ zO)wfdISVTOI}HoGth>aNV!iWCP92O7iwECQZqb{oS_17dMlSjN)~m;`mE)?0#I9WC zT&`cD-^tJ|t>YSRKQs}ju+-mf`?i%gP%zuxTV8=XYj|UT`mFn*r^a!f_2B&^$Z!IR zqE)XoJzFX-2=Few868vb{Iz`XkM*ahH6pK690odydmXZ|n`(sAHa@wymMh->76i9@ zcKOssr@Yl0F7JrMxycsTk58btU4oCnGr9wjKkF3KPcTOxx~@lwzt2YGWvWeph%?8F zlZ!}<7wYqeuCO+LEl;lQ7XR79OpfhK1hoWLG&i1JWADD3m%!&AF{f=XVe3#>690l06fQ!)G>Yai#f+9Us1B_6006)nWAiOuq0;>N+ zOqlwa4m`1OGsJG=PKeBLc!ztpg4InT1uLExiYDPE;6B(lp~vI`gbzB%I@P8*DoJ!H z7?;C3LR}T}Nu0rQVcZm~YE*=*s<#M#aNPmLa}j{_*hxWpT*M$*F?vvQ0&xgRj2e_@ z^N{eFC@3t6{ziz1C{>t<7!{~k^j27f=q-??&28|K)osGIUk@pCZI}t$x(F%SdI`bV zKe;J}Ur-SWzqkcX6TJya(`RolDFA^fXx_1Zr2y@gfI%#Mq)K2v4hk&^_TWSr0+96+ z(7PzD)s_)K3WOvnh*FX%Oe6D7NZS)KCD^?Ph1)|nKf#8=JO2AdXwCjqsY4ab{!xKc z!Sy!?%ss|Qk@%95km$D%1-(-JyN~MiO3OsgZgoC>b}PjC2|=gpw~&qxPe?n#-@-e% zpOJ<1*Mh%t_UZJr3XqKS!c`Y#r>dyk=dnp3W8?yihs=2&9kZ$FWNLozJ&~zC~s8)tGN83d9Z62|~l(nllspPOD4^hmU&ZX)(Rv&oxuL`yrh#N3)NWK^Pu!?Lul{ONjQvUzf8K43 zpD5D#+d|$NXA@aBJrtU1K4%5h$JDTwQ8;jn@WYc6=uo!v-6HN!qnChhHVR*Tv$q}E zfAzpEg2GD6`3-_oW$c`_V9t;vB{&ur{#avK`x8$mupqf-i*mmnNt&ItCm{Lr2o`y+L!C zg(nJ3oXkz-Yplu98K8BGg!#!>q^pJ|S4fu@5#^$>xW;m1qG}jto4+O^0? z0>9qo?NlGIqIy`gO8KWDQ5{sA@`?K(M?9Ob%~@W|GS< zIv@27c>DPwh8oLsg$g}d5bltr6i%vh<-^3c780R^AW0r>s}Tfeh{BK)gV>|S*mV2y z*z}@A!dc)|(+#g^`+y1fc202nJ+B4g9WtN-PmNRjEGyv%Y0G>@wNY%U8%96mPPI{C zsu*@7lZIRLaZNP1D)T8fMR8*&I8@T-<6G65_h6$TMh3qjbCtfac^<9`Q}r!>W26&Z#pxuLm-`pB42ue?|wy`iP72*8LCMqL0uNl+lvp z2?`(9DKFsULtNaCOZ}1wwV&`fGyce3%(HL%vNz?NteEii53V-qb83~ z_~jx;=e6Yi$Bw*AAI)WvAw{Z&uhKf8G)eVUomcS#rXz5Tu+v#`O?HETSi+szcujWw z?WBZJxE4scHZMsb_mrE`c?4)o!mLQt#|oT5j~14OBz6+63^J@O*34JI%99|*a(R?W zP6bI2lUEv`lvxjzuVcAfA%(M+8dim;QdA~|xKMCuUGT?_oLN%tN>eN!iQLy#4G{Tj zk)#gdXH+2dx@h)G<5F-4dEGO`bB$6OP;afj;=}R@(vbK%BE@r+*AyV=cYO-ommicm zK9SObbj<@4Bd6-N!|v;)Djo(?rkVTI2kd zBE2w%22{UEL=sXcQ%do5awfckMW&dd!4O3ic3&oq!lcxLw9{Osp5p5i4@swtc_Kx_ z^E6KIwiiTcS>HU9-D#qbB;-xgZN=&GK?;zpr|G+cbYX3m6p|WjKSHzhbTb7n_6KVJPU#xRwGDSkXle91$Yce z1Nw>4;uukc>4C#G=)fU+7*!>9t>75;lOytux>?6kjK#ZYkxo z=?^DgaY(CU)WbUbaRo}tthfgpmHO?uve; z`;G3WN_-Ah1n*3>a}zF3wsV5_H!KrIC-)PiD}v3z%v1X*z=AS3d!!vg2%Xrc0|{Y# z!a6i@&F;d-#Ni!!n67uOy1^!3vvD>`P}+%lxPnzjgu5^(uD=m{ZUU|)J-{zK0f!L< z3KQzw;0ssq?MPF~H|{W2%J=DzQtF^`eFmNewb6kfHypwho^`}4S-tF#Zd3!o6JB#K z)8QAAfCZ^W8PL_oQm*B9>^gTE*a>feuBZw1I`Z6ULBh8>Yv0D;7Y0qi?BSwPZ>d9~ z|5N37Lc|}f;Puvs@+--RK$zVPe^o2xw~Cb0)K*Uj8A0!8La?A&0Z{v)6?3}sb`*lM@3;zpV1(Qd5DH{K9g;^dMty@`oce~iErigz&wx;d-kmyxqRT3r z!jZlV>?yGd0qMX06s-_t-JsMkBWgz3Fd?c$*)Sw(K$Fj3G3+4jD9QT8m$DrmHin^NKDHg<{fe~S^?Xk6h6XH`FK&PQUgCxZqRQ$1zU$$ zsCH04ROSdH9)3)S2;sTK!C9$78F@RqkV59>Fwi36VI#0P$Uy*PPB2KLRNvvQnjgGu zrn>yTLrc}lZCOq=AL<&yS$W?zAv>+(IdzD6Nc}yK2(j=}LW?_P2{m_31XdUt%qY*f zT*EnCIb2gXUBwQ_!d)Q;WU9;V%Z%awtuQhuub7L!?`gIrjNPVw*VCZe^RvN%VzroCy0lnQZoHZ=1FJAG(S*QzBB(>2tZ+qlyj&@W?(kiC z5H9+&;SuElqYpnwA~s$CWY@z-6C&MHOcQc%MOW3T&Ml2|#CHBW*h#D{pMv#%8+-5T zK+U+R^JwM@w|M4?LoaEI6${m6t`#P6hgg?;AQ;1$K$u4l&V9VcXVv$^{yk7rerU9U z!icphJO4%`=LpxvS56AH<4>F)?|SM`(lZ}dak|=k)T3;GY#6FqIgOyIuN8qNyvrA% z3F*wQSml4$rcankm}5jZNgPxGcDfB<=7XC#Dcb@Y>fx@IBk}I{Z{{S~kYH3PedRW! zGQ1Rn`9XO%%^wpE6XPIg>GRK=qCDr-O6iP&ri%jUl7xDdmp5#!D?fASkQzyXRDV9Z z^XZ{UDdhpt_WiKUpZ*^@eprRRuauw8H;r|;V`}p5nnA0QFqpyKnr7_7(XTItkAws7 z#P@%w5F1JktI&7S`Z<;EtkpG@{%W9Os$quPQs|kz`yVERSmWWHZ#tUiMCeD5b0X45 zug-~L9xtUGgBwo4A{0H(d^!z|FTT#iEb(meutc^(G{(N|i z2D#6QV09PqilBR!p)0L&=Wkb9sW8+Z!S~^VbPw8S_BxTivhyGE+}H#8;XMI=Am!BIlfe58*#cTrb4C zO6=)|yKKBy8NTq2Vo${Y7Wn(vYxOr(X?I+5rsBZ`NtKPHoo7qkGuKJ)S4ry&D0_yCC$`c0$Zq=gat}3>{1H9d}$ZP#Q2OwjEu2Kg7YTh zGo}QhI}_?UZ{P35eXJ;)qxkbK^?_=`r$S=R9z|_(wJ!?Vk0@V_jCE1o%`X(9G#wc| z1=D#Rn}jjR_;bFX@;rW`a?A5rM}^Vz*r}U(W1|s#vDHAwWj6AAkkWZ$vo2C|#Lz?K z*70#(q~yjcOJI}D@8FBEnqv*Rcl;7#AJ_-t1QjUO)QZ?6->VdHL^?+bs!-gCNv@D; zhKz+<4a9qLrT3`OvZwc`7qO@Fsugjh^QzNwrWdK%e-imCdh55V#Jx)T!AeG-6{uWn z?}4C2Pq6EMY|D28L-r3OYHfK*c^=E$`IPk%^r#@KMhU0$uO)KW}F7j9HVyv+e~>IazmY z#FF@m-WMv`lm3$V%`xNa*nLJl`fG8un+p_@_g=a`e){;)$a?@wLO7K|tTI7eX@f(+?%-l}Y%;oW!sF@qyM zqJ{OyyD#Bmh4OI3@$I2ICt+=r{&L4LBO7sdWsJP%=k%E#h?GH{Y^H6jUEZ_YlBJs* z9Y@+q2#Mi(kINws<>Xq}1L5am2Os{-by?oJ=9lY|>k5pQ=KPx#na3%t5~eoG<)AJc zWyX48W$RnKmqR6UXM1&1vf#H+&fcD$1*o18EWkFdG|w#b2|n-Wmlo_r z`HOCujYqPU`nODQl{t7JjRUSGCGw5aEHqnFk7Y)-LoN6M)>PtS;2aq2%j3P-=9aU9 z*5Iv$0^~9@2D`KEQHd-Bsw%>J&*#n(+VqZFr|Zqz$;1X<9ATFxmgKFb-%1mBll#8u zxVphccJAJBaYM3lp+k&7id;_gV56ggXzN9R4cex-H?>zd_Is2$%NzyPb%(Ud!A3P( z$k}`TvSH2b^~!^uHy;9I_I@=W6m5uUZa2A%2!xW|#(00692*{6cu{X_s%GMCVo}9h z6Zm@5w(dEfXS6MGDondeGSLy$ltozO+>GMT}ZAWlNU=&OI8d>4Sl}s>BxV?xNYf`&>296KGG)A zW=wJk@eXCK>keuJEZKIPdCuE*m^z9v9B>7_7GGxe0!u9$SBEhjF!2C0;t%9f%W^}v zrIv#w`R3oYzqD?+kyauz{~wh=*PX}Rr>+FGe=}SOj+G2|uUm)PuO9`{QyBOAy^<=@ za%7jsrMA>M%U(9+8?|57PCre&VLXwOeCpnF0*5`(+A2S6md6dqm(RA(obVh1=lpruZR`xZACa zbKt{xRGTWYKiu=mc16~9CBSy&w!15>^!bg(bIQhZlE#M-W|y%)&u@kv3cO#Iy8UyX z{vAw|5S0)?+ShgMN^sSE=bTh(nbWz0=k_nV+c#g*N4{4a5#^V>&B#b5bSEOtIV{&% z<-ZSaL)6$ms&!~^QK(4u=dkVav+Y9ISYCpZfX^$C%ot%<0v+$sx255%5)h>)Dd#4w z=jZzyI@=@R4+;e2!<=gg7B(R&qq@iWX`@M}`E;%CU-}cYZazBxgxk>G&u425rI(4k z@}wZxpdcFK=pMsH7I4w4s9SyFsvM)7-Uyh6Bs1G`@@4Sn>a23!<@p#jZ~IwAT%Rg~ zKSL*3iC2r~Zec>ySicH`?PncvecBBEES+R!-T|JwcX;pK;kdh&5arB%_YUwU*WEjO zcNa8x85Mb(qI4t^b&~aX8C7{1AMr9O^D=7lGOF`3>hWeO@Mdc8W~%UJ>hNYN@n&lA z-ebS(*QXM0`}vjaXHIc_v5Y=#-b`KIOhw*IP2PJPcNc&SReA66+#TY*o01SU(68dr zr{dACLSy?`OI)8OV?3NH9Ko8n^7t$72_ZF+W*6aYu)asenkGF3r-vWa1D|9Jq%5x! z1DT%J*Kg$0nI)aJ=r{%aptI6TH>100VwDIF-!bm{znO{E*f+#BH!fznJ@WrQ1N-Hy zW(q2Oxcuep+nAfS?f3uA^na^X9P!n`d3s;5qGh??r0oVpd^t3@3ORvE1gqMUzq+O= zMrmBgVe=fWud9}LirCxDKQ5J9n|4*l*|G3^NW)EyW>aomsa#<~R@*Jvx0OxVddxI8 zlX%#yPN|X3NwwhMX4*gP@sG}N>MqE|<>SHF6A{gh|=3j=oE2UFQL`%d4 zGy2Us$2uQy2i}xU^W;))(x!dfCmX`bggMcQDW*0Jtb#Mb|%Y~ z#qX8pCcJFYb3TmkLz)Vpe^pUJ&6ac$Ag+|=`_>+>|V1Dv+kB~KY=9&NX>iGL9=1A zpJu;WLL1-v(Rr|V$fGS%Iu6*cW#$;h3qxuQg zx!0-fZ`3@tBOJ)^$VhQo>T|MMM9w3eY@yvJx%PXudoQqT2vP(Lut!>k?a|b05i?AG`-o1{$lZ`S=Y&)gR}jEY8=fv>-sjL6(N=G z;@%2aJEM0RdT$;nhHyiWAT$wx5k+I9vrLCi3lWU=LLSl{H$7;k=Ki(ebU(u(1163X z3%0)MIiaq}dsbvoNMvu%T_aH=P$OGIJjK*qE57^xdDvIL{V~Gp&g-Ozw1!3ax-7z{ zB9`l2@6Vm#ou50ucD{>)yo-`HUj&eOh)TpuCrf8bzuSHU_!Q|3{G(sGUzlI=1qYgb z3LAZK2Q4{wZEIm`VQ=sy@0MYyIEW>Kx5$)d*>2b>Rn$SuL1GYZH-sNTJw0B$Ud%_7 z%>mj!PqP`kH#-y<)tGic#x>L?OpkT7`6VvuiIMcTCG_CgX zG84U#E8k9ePW^A z5_p=RB#xIM<9eEKAq_ZUAzRj?mUux5ncgr}4ivUeI0LSwid)9f{@HN6&tfDAJQ9*C z@cq7IlkWmoNdu4Q;cC{bO}_nomLB|Ui(J8~<$cx__sfK;lg>60oPX%!C+y!BL|~fp z@6=o%{h5kPb4fgla1??;GA_0#V#0gfiTc7vct#*GfUUyv&0I3+_2Z}T2MII%#c%pE z5w8&_fy=#Wk<+5eUA zapgF%*J7kZ&)8$M>fbCi(tfe@-2SkoJzS4?EsA zNNvS*l)p-i-zSEp;}wbD*%XPpD_2baRXp~7l|Tz*JxZBD<1PsGI)78Nbv$P*+O&{B zYZ=M^04G3|vGv=Cnkj$ZT^Q5QQ#4%u*IPhpaPW%oTZW-L; z{&$6|z&9XsT9kXjCZFsGpoVpqC1x(}ZUE0>oUp0=kkXS?gz{4Mv^@}TbZ0Jp_tqb9 z)C(77b8&!*xVuQo^hVzSAR;^92=TQx)S987W9k5~?R=Z12hUn$D0o&)nGPrzv-$e- z03czTEX1)+b}{wwF-tz;&xGYb;L**l_}!pufiJjyI>5bs+L+`LkP$Y{|FOLn<1uSG z^v{IE|0E19@Gta+|C#7N2RKZKH%+mZr4k> z3;6cmF5pd|-{v0^JdXgblf$L@MV5|0KAoOGUKjp3rN$C(oU2V{aL5!7Wv>(jC-ew{ z4K&>jE)<87s#D6~hQI`fLNK@u+j{B>?(bVw0E zTTKR_by2R0yw9qp2|UW74Lq7tDCl6Dv&qLh0l3W80@td#8Bcs`pj#QuI{r*2^;boqJ&ZhP!G-_T?bYYiEfD&z{cGdD_8z5g#s!5EwF$DKr542s zDcg}hrG-ARg1L1uhrPIwDPYoKo8SFNqWwt1{Yc{dI&mI&5g$D8PVMMUnJFnqP(H?m zM-2azw%jdpyPom!u|*W+#i7VX{s;_@DEld`wOfSfGm7+)pkI1^r-;U<;uFO!9PSno zU(cxcR3JZi08Z@)Pb={scu60$!~Iy~#U>@iCS}ER3iDYs)O0G!eUIhtX5$D8N!ex8 z3}gR!V7$!Z-k1BO&I+f_ZZeg`U0dL7G^3e{qI0(P$2eVEh(K2mI4MGi5(aTGD}}}~9TLbe*U>1ghA`DHDz(C08S}1jLBM}IJFIq z;CAndQ@!CRg6WW|I8}Ou1KYC8hjM)g$B~HAtV=NZVy}0FLmXZ%PI0Pbq-H#=((@+` z7-OVnK%}NBmC`f5225{-w7xh> zrXF;4FDBkLx5XH~vuV&s22Q^eqQKD@{ac!N=qLEH-4r5@TbdxK0xjp~Ckt4~j&_q#Z`39mb@+lOj&Xq|ZjBy;CBV zQX(#k{5msE?T{Ad<*Leo+G<$ZYL6OXn`#gD?-u`j`!6L|$I$c$7$*m=>MH!HY1Tp| zR0sJ{^`pMoszJbWjljfMLqo?mEcN0>Q$wArs;a)~2UAILv1<~9%WszKsv>!43qJzo zrZ#rs+%aR)zCj4g-q8_Hrd%eroMb&d*|-L1%FQBP#WrfCpHJIq28ra}tfHZ<^2dm+ zpH7U9x(c!@sIs##jdE1xY8kdORX5i+&e=29Qwdp4k+~S$00|D=p#(ZHJkin6w6sHl z4*IRRJy@)+bW|IUr<|l@(WxlRNJmGoX2HL7E-nln+C{n6+1bapp{=>5O4+e$S)$9r zMeEE~>&#J$bh~WnaJi~!W|*#-0Kat2=Nrb<$SPemUF;80~?hP<8URw}0G;*ub3+dKb-Lks_ z)lXMcMJG2BH)`}gcfM<_jDweK-@T}VlnykiOZc^2+uA$A4{^OIK2I*LO1^FEkCv^< zshMnF|0hN)KY2r#rozcgV$xt+-*$Rsbvv&7SI32=AVo*KPK7UlrNFeMu)aUXNZ(Mn zrrvf5*C^-Fqq))(UP@bw>gxloON+t9t-Yh7wPKpEwttRv%iACII^yl**0c5Vx5t~c zI#?|+3JL(KBaog^`3D9J)6vJ4jOVx9wZ*F5Uqji= zs3Y~yF@zuTIfgblhI$SXWXozU>oslh^S&2?c~0?7z?vFhO$o533Fs8C-s9v&-80EC z_?8z7e@e2W`SI% z>-=-5G1#hVjq|l0+G?!&c4e|o-o>*8Q*x1AJiay9YILA+mG&KbKqn@AC_30KaAX^u z@LF*Pvt8yISJRJJyRYMd^Mq7wK&rZ4&glTwfjm3URUTv2>P7RhMi9u**uRzg#OU=-R5Bt z$5u!G_|3F4m>1&#NT?=D$e#AihoiMW$$Z?^nxP#JY;s8^A3tz1r7}9vtlyYR7_pgb z-U1TY{QZRK`R!Kf^aG9KPZFeWXF+l5BdU_%&2f!`T9Dt(@gX@3`gMa+SQ;z;#UbTRM{3NrO z>4r7H2EUP%lxpyNe%9e=v$eIMNk;FcnMYACNwjec>ex#&ygR@^aAC1?dKW zp!0dG>z|yQUae+_QDayW9F|wL)85dIgmbEHDZjy$#TNJAg9R8Ofm1-)b#qbMYs}VK z>zcKL(N%L>O@jAdWs#e=y{i|Zw{ItQEmx0(qA z&s+U_IM$^xa+Az#DGU!MYu^&)G_MFMTV@@a2Y#8(lND$7s<|E!U{EgLrt3Sr7txGdQ~?6uuqa8th&7V+-y#ESYt1plOg2t z;^8vX9-Yjhqbq&)aWn0mURw1Wlgr{F&P|MOlg*zH?jQAFhUx>f{RSxt{+cZa>0#QP z-XS|hO9iCLmD<6`(p$D?u41jan!%yn+sijhd*WJ`;OP-`qMN9$Z>v_{9*x1abuxt* zF}cUC;3{H_QX4AORXtRdtyE?15kBaC@w_sgmVQ)hiFE zW73qenrLjS&*$P*)%3YWk4~b%&+#9MV@UR9$*)P~2R}!>rL3CCH0uHNE1EZ9X12<9 z4hN^FKhmzG!_{Ygj!r)cuzbG>T&pEGu+^aX#_0m7aX1EYvUKS`DoX2)cNCRQ&h$=hKMopS0MfF9mLnO{OkdI{#w3rpl`R z5_|uzN-1t$-<((Lrr7`VPL*#;xO=^MtonAI|2t`{MrbWD>mes>8+TS7RU8G2n5+u% zN3i#$n|X_ncia#!<>rQN80*QkvWhS++mAOGvJu}9#w@1o)VG84R)q{Y_#--h?2~`G zXvd`-dAKppdGLexvXNi^>udj)b~1>^S0n9Tf84(D-V^?tZNdaFX0pqgYy;C=4ILc~9w*Z@ftL24SDu!> z24{|6TqaprS@QJGC)w@BeGXT5{FGb0{dS>qEPMe@3n}Z5#jEo$U)#)st9-Y?^@* zKZy*p#9;08vBAR(^muRkKXzAv9?BKOv@ZP~zg>3x%J=_Yl9NT8|A{iMAU`V`4htxh z5^Q(|vOGXOJ|3cCV!ZqSF=0_55n&-AVLmZ#0SR8A-6Mk(R%z)J+LVs-+tDXiFGpW6 zU6*JNPhK7@0Sg#^7%?);z-VR#wgP#1I0jgN!2AF{@eJauNldj#EH$;*IBYc)psG#T zaQNH+EmNhk`{0>`#ctvbrRLOc(Zyhv+hm^k6KQvKQj3AH2U}0*w5b9 z2kiGXAon*QYd325-W6owY|8-<=HV3+5duW*09930;?;oDs-UBYqoeYXBL>EX_|@6n zh9o#QV#B=^$l1c4PE1%>oEsp)&Bwzl%AZS2Q9z>nF=0ABZc1eWsG^!Mm5`8NHT-Ls zK`wlxoZ+Z4zTT;lHiMXuP+EQtIb~KB zc}{-zqzYDCLhK|qRstM-{HSJ0p0#^Fcx@{z#t#q{78U2?6A%*uh!>1ae4H+y2V_-F zEL`;15e!7$6~#L6JwEUo2d9#1LU4$1Nc#A)c2{DhmOsO^%)67K=bK#>ki}OfG5(}1a#?-Gk%&3uk>l&ZgO&5Ar-i5c>xG5;gPDo@ znS;MGKJVQC5&$tiaX=oilG^k{EVU^E!>?f*0PR4Nmh=GaT^2FK(oPE8jK(RCXJ@&^ z8x3|Xi%J)qm!Y!B{;;Ql*OO~UlL!Fv{!+o4gy(YE+}=hs>{hy2+Omo8@QG)Wj}yo> z3Rwzfu*b&M9JtFlJ63V^{jGk@+I9A~@L9{$pHj2AD+#MS*Z=AvB*K?9Z9P(sF|IXl zsbz_$mExpEub-Xj0XQioP2xWSmN21is_;-SAi;lsd^X_NQ5keHs+rq`L{=y}=N4P% zEB%+uPtzn}8(=R-A4g%4Zc(v(6XpXw^yj4Aje71OD!&+2O!kD2y9luBV_SGb^`+0r z;T>)qPwBWhoK3oE7%MtyC64djX%)>MS5;Gqv%14e8D||R=@@I6 zJ!uufr_rVfSc|!~BhoD2g~chEH(42nv9?tWb#-Jn)c0uYU5J2#g-X&{(U!~Xy5nW8$E>Z66X_6A@ zVFhzvD-WT95}xHaV}v9v_AU0KpZ9}i`K9)?`d|=P)|^APJ>}AL>^N=++E(>GxQbSQ zg&>vUbCtHh>EV+Dhu(OQDoMM}23Mjjb4W|1<}qw>){$^5&p+SjcVmyaWieNfFoPvN zw@9{$X+Ft#e0&XltevI3pbJ~cSCmGbeTB5R@n=h9Jh$0B%Wa7QYOs_wwNve9j`-68ebzX%G_j^X?JBOBdVKIq0Q&6Mf0 zB-VdD{MGPMcIzftxqN~wlj+S~Nw#;giD8HeiZTnOvA>n& z;)7gA(pb_&I{5x75w$N=m)$6p28if!XfMTl;YEj(@uHi71YVPW6nfNX8%(9iIOBao zW{5LZM1JzXM84W1|7}=nkH7QV@KBEXOAY3fF1lQ2E~`_PEH_=n+aNXD{?(je3)scn znqO~w|UqOZo_!scQbGkFFvIA4%!E zqOeC=1Q9gT=%VP&Ag({^?(95mSuxfYt>CIqntn&nryG92a^I2r$3X<>770(S$whN{H!3h zyrd1hxY4NrsQR}GVzqA4-IVdwjBcLmpXyCqC!Cfjcj7)Mu-O`23qsu|^ePIixU=<&`vtTS#OGBsX|H>8rP-)e>Uz$mCiyFAbIxH60U!%$4 zGC^FrC!GHwM_{~1$i*?;esr{Do|fu{pkd&y*xW%{Eg2TC3=o7a&ohF2q|4Qe*=p0H zmOMU`UKCHW-OHtftIB_AnY3SJoGoT5@X&cKx2f+pTg}M-J4b(KT=jJ5YAE9$p# zstg6r9SEWjI3=SRxlk*sV?<{bagt@4_dz=(*Fut~vwz^I9en=tqt|dzX3w`2J46R~ zZeiV&EieOPG;_AqLUBlhju z1;*ty&2%!rCHmM$dRc!+bq46PY~$1;e&;M8Y%3w&t-N5H_yWGLzt_(>k{TXqsGbtdph_in2})Fpd_dC-<8k=w&*lFKuyjXYa|@?-$@w}r0`YExyxG{gmRI8jU({$TtN z&w#L6;w+d{Zd-|9)2fkKlghvBN$exU{Tj;5eptgvsss4kKgU0C+ezGxHupnm-Q!zN z)j;@u>}Nn}+}}I~-{VHAxo37^)9d}0uVb8~MxEgfYlq%FnkLhWf1mSOR#kFhLYXD9 zrZV~*cBX&9+OV{8hGq6edIQ^T1i*_Ta*G1X%Cdo_J|ZfN6S|(SgDE7FTcd%QC0V(T zd~u0U5DJ7xqz@a6CPh};WGF~&ZNN~iKtfrr1snYh<1ZuYF{bex@esr%uWdF6h>6F~ z^KOTO!o*h7Sto`dPze+$O?0vIy5%8!@tGLSv~oeem5EBoHMfQF^MY$CYAG3z&`g_@ zW##+9Lv-X=Dh5@X-Q6NgIQ&^gDqMH)Dmbflmy`943GWi;WFl1j_?^AOK;d`#5Rp`= zRGHkI-d6rZjTGma;i2P~Se?y_4Y8M=kZ^J z54B5_ps*RYeJ7cKfc1T6UPfA!I=}q~Z)&|X8^!17Cu1(z9lu(r-*hzla_p%Rhs#eV zOQaU6)*Cvu%=W`>1A04YIc9`#n8obgj(Y9$)Dud|$E@>}M%K-AJF^Du)LE$^MFh$R zM)P&fIG5L#(&+jxjo`q^6zz89-sRXeC(EG<5?lr7FWrgxYo0GnDf3~72 z3v6B#&;}m)PHfqtPq>Yt$pYT^Tfg|U6!wW}{Nv%udL=~nBh-W$c0s)O-#g&JWE?tq z@dxzg=Jmbv6>OrkD@c1|kw|3grTfn~n2BYjMZ~YF4hw_09fphRVht^@9{-IzCMx$< z2KX?TJWNGt&={aFACapuSdYFSyvbgOK+zJBlJFu&8N~2aAl49D?V7o~Kr)Y3i@!WB zzfQa6ZykYyL%d8*2-C)VuqGN$c@vLc0yZ*&PqFVm2i(EXr5`g3>eJ*E&Ni5OXR##| z6{xryX!4jD?!#ybxk*qi`*_X@%|LX$S~Waq$EpzO;+B%Qb9Gv7v44$~PJ}K-mP@gF z=BwE?kg;gMT}2vbU);eBM1=FzAN-8Ib(+MkeKu`K|I&ovlx}N{yfF!x*Av%cx#_?t zkrj(FHx{(84M6%B>|-0!6sdtVmHJV&6{v44uM$liBNDd0Sndl7{z~{X`Hr+Eo`hUp zuL_yk!d5%=juo~SxzkFf(N0G+Ze#i5QO`t4sLM#tZSS=gqbZK#cmkkkjUreU|SlA1TZxj6xhDVpgs+r79#Z? z;Y|7o%W7E$m&LNgY_GyzcqBv69glwAVyZ&wy)ubBZ93HhmFrJLOg?AUu-R#ar&k$y zeRt4Ht|jKvYfzdrn8S|g_X_2DXPwD{iM)BoIO|-S+~)ExWXb?RcCBPzss80W+8bq) z0_*dy@pm4kn4hnD-+AO?gkF!&l3C7u>CoA)45C!66M)L>i>|%Kj#|c0F;JVhS^4kWOt^} z4Mp5phZ}-i4@#myLlZi;WzbQV!>UxpUMpEHHIHE&qkE9i%plI6h1B1JhLA;KlP0C4 z8E1^=R@doI#(Y(PMVMH>sJspOkl_&d$%d%gM%-TDS%~4UnY8Kw17r3RckQpqcL;`I z%NaL45?9fX8eFTTj5w*jdT%Q#fkUbO)L*n)m`m-IijZm8#x~b&d@6%pw^<*i>bI8I z?2$B@ZCB0}fmSN_g1T=ytY?{AMjZ~??$t`~&(3tTDJpR=!w$eH=<9&~TZEunCLKrBKO?&k|{u z`fuDh57~{s?xtujYY=2F<}Y?oP5!a%&!NkYptT{;bWsN z#d~k*8YY~bbI4a*b$a?*Cr*K_nx@RP&blUJ%{7gDrr4eL-I>gT?+av5^L#(Eae)?t z%$=fG*TNM~ah!t3b|;yQWAqkn4RZxp1f4?PCokG45%!?lY-edDLpF=iZ(tVALlsV& zTWrK-JRfI-g8utvoAeMrP*dgBX1jrxYcj?KXQIh=#^$_KO$9%mC@X&9wJ*bSEAzH> z(P0gn3;+roks2-Ton|LD`;@l_$!Heqd`38Uq3PF@sw^i+RfjjHN@dj-gY$*5v@E6#V9CuFn;i5=xS8Umc|-R65lq*P=Z};7kfCZH}^} zQ`=qedT2{D@4Hv5CxFxoHid=ClI6${|Kq9Q1pn2jpC1xWp&bP2v;j2%PYe|~bbMQD z?<k$!l>>WIT+HDleZw(w|qT@EKbyBwlBn6=Zc9KW)U=$5IXkjNX5 zP>YNx3(e1y_Q1oZCVXI)K8QS|iD#l-7Le_rAEd(hv$rjfA^fQ)3zBp*iIW-m!3F(0 z(#FmdvR6N)5BrRvS=uO@$z^KWv}qqu{igPVQPGZao9nuM-Asa=_LZS>qCU||#wSyk zHUX4WqHmz;Wxbg^9N^K=r1gBo=HQ1ztum*bTGcW`ga!X3f`Lf_-&7UG1(_*)eO@J2 zu)L`O4ynZ7{uXIx#^+me_`qZ<9fZ{x6Z&ew7y9R~S|n*EO)V*R$q*Xtd9_((uwh~p zH)v&Pw(CRo)Q;w%`x*WEI4PUXVE8OK`zI(zwE$Yj`!7%{u`u6SGa~h;+xr)sFoavTO;81iy949=&6lu;QPrjCbbrmuK+owJ@rM5 z>g@bn=QXWMX^(emG?+FNU=rK$yBJc)`-Zt3Grnl->F{5iNvz}f6^EWLr-gCRggQT;n3n2sCh^IB6r#Iq1NcjD(+j4A%`x^r+>nt!tTHU z-*Ch5i-7&2g}A$fi=*pC^&*XS`Zt`pBV8d0*1v^#4jv-%Z|o0j^0C6wy0JPpV%uxxU=a*CTI(Mfx{Bv8<@9mR%>Z)Y-Gj|?ii z>DnRL*q?p(VNxSB?h}#b0)JRi7Vfm4v|95Fx&1!5u5J9`Pn1Z35(YzJW}9F!!!1ON zuW0#aY)hjjh7ZF=v=t8Xq-uysO`bN@BnRuWESGs%M9|qua{O-R>tD89Oau&{>cIQb-N-;3N;9RW_OsiJ-;n7`_4HiF+zbmu+816((|4%iM}Cb zkh5F;g@t3Ug>Q~M;X8l^IiFDogVM3dQo>p(^ zT2cThHGN}mppydD#ZPbh%@i%He?ku`R2S4ov@oB+-;VKuraS`D!^I<3R! z56(I&!qmm1&CEe1+Yq~;h$S0amI2DTbSyXMn7L6|N#O}5Pt@Hbkl%_HbKSb8y1G>q zw0y=NJfE|C6MEY*2xD93Oqz-oYxNRMj~9W6=1(!%>-z7fkuEYaUxj{{cl%enx=Gk( z+0n+6m_gZ0LMTM=?LtphpQ#cOKS2ywoZRD#M!+|9`=?xDSVXFz%c2}NCua^HHLY7L z8IU|dB@bMF?@jHsx-`s(-70?ygN5)VFz74tyrzg=Gua8Q47eb@pP&8KLeX| z4fHG>S+n$5?4gL|T~a_^vGEc)d=ni5zin^_P4Q6j0Fm&ZCWu_1aobA8=Kyn&@XOcDpO&-uGB;^uhC`GOAsg!bynN^WhY%{Q{)r!hi{K|t z6&(5lqDtvv7yt0_PvSF4XasZWyNJPGNR6HbK)ODkFJ}e~iRMfMf~_EbI1*zs*f;x} z7L>wP$uo8c2v$@6An$?};}?f!i6{#CYhdrIf3T2#E>1Nts1zDvUi#Oc*F_56Gv-Y1 zeb=NjTeV#X8shuWuUX1dP;HoTG($1?FqU+NvOY5 zU9X!No^orTq4&c$Q%5A30H(C{Aqz$((3Ee((m1fXSbYb;%NlFAS)B;*3*Z58^9kh; zQ&8rRl4eg$sFwXI16aD#?$cUo?YpOBMcH}Oist5%=2&+qq6%+ zoKH-gQ$R*RNs>1{5nEb5?~a!O_v$~x-OdvWGjQ+y_2A6kgQty&iHY-#r@`ZynI{#H zpCEt-Aj~bopT9-mN{EFpd4Ij~cj9SqVf1+P;CS@@32wTway>Ju4g!4z`T6hy03zAM zK()ew$Bx~OvF8a#zpr2)u!RR*3NcB3OOb|(EP&Q2OL#^ZaRk}zv(x4ZWMdi&pp z;5~@IP7s|h*KvIMKaU#!&RfFwRRL zCL{OJknxM*0o+dVB+atNgL|3V;n-~-L{U+kf(;2@7h021r%^w4Mk?(h^8SG#mJK$W zszr40^kkJmr4(RN;b$1*wN^SWbIE)GFY}&8CGS|>Vk57^fZomAkHP|uGDYrgzcS`a z#9>%B@ijS>iQHzz@vU#TREA>AHz%hah2_KF1I-+D6y5o`KlWYalhtLDQ|o2wcC2u7 zR37qL-U&>Fz~W3Ca6DnkK_Nn;6s_;W*hih4%S*$=Y~|&Z{AXe#a32e?*r~5%zXbg1 zqu@Ofi2Gg0{L1FoL0yWV2TDt4f2}cojXBg2ahCM$n2nd+qZ5`T57!LxCp)M6E&30} za4B%E-xj?FciiGn77d_BlMJ%`RMQds$cf+jaTSWIUo78>BSHs@KD`l?QSVJt6(PMn z=FMR}6d=apNy{WYDN9v2%Qcd-y1V=|l2x4sT*I+zYIKb;6_(+QPHUWKqWW#usIbUo zI-WMe9s}5|MJe)YjQlfg#22G(Drv!V1SHlq&P79_6>;qBhZ=ZgC}b$vYcF{vy7Wu+ z(S2dg)gm)xBKj+2dd3B;=<{GEDQD)H^J^Gn(!T_@%w$6Xg-$mk@^c98r;i ztO3S;9!qyiOFYYCWB;#?Ag~ww@Uq}_>VC4+VI?okq*~(blo{5V#Xs5@x@Bnu;5GJt ziyUJSA3$ytQgd=eZPd#<-SY+ zruPvzB|71;nzPETasmbcOXPCbwX!>_2A$ghGiK7d-S`|S5Sw0VaCMXoPd-x=jy&Ydj+krqK24&grg*b?k4t^!YqdCHSO|iA>KOvgRWANbJl~KE z_X_bIALisG^C+bx2K3Q&tr%o{mVQh@;s2P@h(AP2rPHW@NAYYp{S0ty_y|c!px9i5W%Q) zm^&7aaoHYtVe}B^=lRvb_N#4xhohgf2iVWY+13vX_VMuY3a|kC`S}Tls41x0%#{qB z+}z(EJi%Xk!eRh%ZXp1VC_qd=7{H&eV_cJ%A~9cKnpdEZFb&@k3Y^BYE@LoFX=-2l(@FxvI2 zG=Ruk&AizU>rT{B&Q`pY9S1bNucob~DjTLmwZ-Y|_h4!d)&-&{-E!Z=dC!1JEyhg8 zbxNOrZHOkztWhJQ(?*LHtLWFvNsAgw&M{RiDA3P+VPTtDk^k*6v97wjF*MQ$hhTOq|>d;Zv3`5rG{Q`gQE}UmM&IcF1hD{*HkH6g)0V8Ebu`iJ)3K) zwH6Z-VL7>lZ-iGeL0t4rK)i=yU2jn_~qE_H|$h0%o_YV%m_8CHI;1GCzz(0 z$&~2RzcRttK~4v;UwUZ7Efch9tQP9~1@C}1HJapflTn@-k#%IdydxLc9}M)83i~D6 zW+7wk&X9$|&~JhPq*r}No8b}9nr@b5k#|33Vru-u4UBm0KWVLw7B^OA{<@k7yy^a2 zjFsJ3B8b);S`ltg{KiAa*|7zIWjEc`%Mv2#N>jB+$=>r)|uQf*wP}qKzlT~XJiBiO?6e#;eA@U05^y_busoRaVcMl z)>(G-LDWFXeJR7SaY41Xc^=xHf#GQC6=8K6PvH0vQissL=0a$lPSr)OT2yuQMGL+U zPYWwMa-QOa5>ijOMwh1lj?lv8&+U6;d%bKsuiwqAamWS2D7;1BT@#uv!m|Hvvc(GI zx|Wn?wAWhX0?OxWTmBO)^{S%;mM_-wEjrp?7)tleS1STe@wJ*vJA{*EOX$$+*QNMv zSfcq7C8EFZ9)}EZy&RPKX<6-arY5W$NAvEJRT|Ile83GObKW3hNAZ9D;kYOr{p2 z&&YzX&meR~g1CJ1+hIIg$^T{0yC!&Ke02w55s;tU5>~DM;_vy4XMmv zV~Wiz|L9p#M+ggaiwR!EnoHPgEpIdkvGBFTL$z+P-J@q*18r?U%;|)SzYVp!Xy9Lp zzfL4F$6dW{5Y4s4HeCMIjP*lXfmv+6>7Wa3e`qk__lxcXz-ZkZM%dUiYiQ01hMmW1 zzf`7)kKLo=nB_DRj#uwl;RFT{)Aw^krIJ+w0$e#1c*@)9%{GM+Z@_Fb{%9jB$>o=N<(j8V)!^ED^`)8&SWP2=?;hz9QBkXgy(KbNI2tI8p^~t zkBbvM`MtMK$iqsGVoK+#NtHbwVlafZhrsqT7si*%4I9yJ@y4vRwQ1_l)N4e0C=pPJUSyGG`;CZzi=M6L)&cL#fss~#>{VH;cFwrEr*(iXD&Rw|yx3^m%J7|{CB>J7gL2pWjG3{#j6dN1Kq zDz^?BoVIV^Fbyyz1=+i!i56T`T2w?T=In(`*I6-8k9NUny7e8(e*s}fEgg%bcs$}) z=yerNNBrPt3*b8~K1^0AWA282-{+uH>DlD_BZiO)Yc^6InZ5WI60+@M&%ydC9HA18 z>$1_2y+Mue4=Xm$N}W$;1f*T{p;*tm#cXyLLF7#ON@jJl1Z`*0$-|63idf9ZRBh<= z6+d|n3X!J=?&{xJR|FyGRQ!C8DfPbRT~4Y!ocQ87#_ThNy(_}|!Ve6XpNU!2LlC&k z@%YV4&2YNI z3$gyM@SmCp5z6&i@n~KI-=|R76`Bv2`Vo?uA!L30O#eNgYZqd+p=htz(ehw-lJ75+5OQ`?6 zN>W9Jzr!(fU^i3Gg1lA6AdTsXqMN#6y%I{GPyL;9ZiYtqKZNA9%vid2jem2vkX?4) z^`3{9$Y@cbB4~`1F6vrgdBy3PRJTQ8y8MSc%JQDwLPpqGUSih?KXy zz83LE5yKIYR1jAmsfZn_fle0HFN(=|s$KX#$@JRa=L~7G$t2(NnmzLD`@HHwRuc-% zE1k?Q*8SGsQ`(S8Wiu1C--$%xV&dY`5-e<2^L@QB+GN9z{SME=fSR4^@EKS~qa5Up|jo``zw7fzNgGGYz% zp{SkV5=YfTh+l&eO?9JNvwFW{;SVYFu8p5s^ZDQ=?0ix2kvD({6nI-sz|;4sy^S<9 ze1+w+#zOyXM1C{d+#je)2D|0v_=2IqeS}eUKT8q?9(GfGqb(kF%jyrvDtp}u9^oIT zltaYZ-~$grN28=#-%Bzw_Vh88Ib@?GNfHfB)nZc_F-9=R$0S&Vah-Ug{%pNGTwxAgC%L#ee)hh{D0^3L<9Vz59d< zRQvMT?%Ma$_2`eOG~d2tvx^Qdl$}-IV#-w8`T{}w9>68rcnOPMA}zJuCxbXrqL6nZ zdHUbyk(O3I{={%4I5++B-iMewj+c{2-aj6E@sv|)5Lm!Py`G^?TQcH@`tI~i4mULg zVGQ9m=O2H^=$H=S;D`yQCP$!Ms-277N<==+2_qmTX*4{h9^D~S_-{+-AG=&D)ULYMWPP zcrEUxH43A#D?IANyXE|lUmxOP1*d{|Zt2_Yv2sLovAf`FEr$Z z9|3gM-j*K=N&Vl+qgIq7UDL=|WSD*8koqA%EE~_m5A-1~Jj424ON4OROCpN;P@x@x zvagR^DGVq12yz?0_#Gf;mtsTSD)ugk!r>;Xhd>Z1&z-Ps6@1j61ycDyQVx$nvjZY> zAd)kendw%1x36j+b}W7Y_~^I=F&cQdor&Q(9zXPYDt+`2;Ipq$kLn zUr~J4466zl%mcap`!ap;&QcGuwesJp6iX`Ny%RBKFp;zvz&X{V;}M&@6c+LmTCRvF84aeeXl!wF6c?XjPwNJ1>$R@u6UaDIecK|*W35Nl+ZFw(M>^q^ zsQtx-Cao}B5Vg8lYgiw-A9LfiWU2Fg-3N=Wab@zvQ%1-h2=V5m;kP5|{bbml02i^9a>#$9o*t;*moU?Ninztx@Wp%C1l#3bNh)D2B zlU^#HLkZ8uqCtkN;X^`L)eru{|LoOQ+jbN_vq*)g%(M3Q&NLo8cVcL{AomaKA30sQ z4c0cv8SyVG@7X2fUwMAa-}>H1-V}GYWUOOTS*z#b3Hi=GGAsUwLD=zXJ|DAJG)f-5 zQ8ZdJm7;!tEqgg)bntbkrR-b<`Kn3wS{?r@S!fG9ZI<1Q$eKzBvCVD5#;fcDD}#*fn+Cm$^ZtQFULZ9X#;R?~7`> zSfaz;izigq{JG>wONjnXnU55jp*1jGx!W>)4L;p>9crJC4f3Mv`F6-D5&i33k(;^? z`hTX0ViiE?cEvS0q(eW_p&w~C ze+rN4o9iX@CU5Z7lbx%f+^@H4c0=@SEIkG5#MGxqi9i^{v}tD+U{awt59 zuMvi?5r*EM!wX-n3(u`xtcI`r;Nbi;?2VyADAa~RZTMQ@UJ13KzXrWwM5qrAAJ`5b z*bX1qe!QMZKiCUfEsC&T%|BiuY$(8n0&FP2?v}yAM@3*&bJiX!KLWBhe&z~G@N{4e zO9Gd&9*N1_w%ql?8Jb@pJ+?MFUP|{HA6(~)lmpjorbC>+C8F@<&<;7YLk`959f;fZ zIvDhb_G;%CT`aX=(JTMm6MG@H-y4AEVZgEL3T=T0n*0k59-3k3qh<>n#4sKQQQB+H zpcAei7p-q7Ui*opxZds6W})3pf=!X3c)j;}(9k1qsLg&@ZMJu{41PG{KXs0v{pfh! z4MqCUh8aawWbgwp^q6@LIS7&dqinrw*A_m+?I#?E+VoJ|mz&zh&Fmo*_aBE*JQ(8@ z?@GF(hd16<{1S&^IFd&m*1~u5S`-w+tAvl67{2%BD`yG50`+cHY~$gnMQS}y2kp%6 zR5;<9pJ49RgX%@}6|rD|>8~mGTV!$`yzWeM4o7WB;5ohd!7`P=S|wBh-ul07#rgoV zZxT`^Xc7r~)2ZewXKQi}+z8GuwM3j>;vwfZxb;7Ml#$~VAK;l*1OTW%IDg=pJ2>uS z25@b6mImm411bG-+LwtXPdK5s6Ut?bmU-@K*J7Hb}0Hv z&W5zQx+60sF@YLSBAFuTA`M`Ln0<|C1ofar!YGbu3GUINfVrF)+$oU{zyjfeTSPqz zf`wf}Knns97{^lz7bR0!0I)u!{Ci4AF!#NdkYv}x2;#Ob!7}wK2X{oiL5Q5a< zAulKut*@&9TT%yfmxOADf0^>i!!(RRycBlGfUpd+ArrAGsH_D(fd(nZgUkZNi9uFNZ99fTQ z?iDc{h}stbj?3Xebe;#Of1{JS_7dd4gf&z0=o}dF5F^ z{^7Hop(^#hN9;5$MkL@saQ!&C{?Iw6n|C@f(fi4Qxjj=lgq9Scp>n5LQus-%!AB~s zi}|}_nbKTc)*5uJZ7L21bDj2vug+fJS05k4+&m2eN^iSqmiu^}1#@3plQrb7Nh&i; zdfm1F7j^|FEZOtID|eCpV%_W7mvx)B#Cr&o?r<#6TP@##yRcy`_4Yndwph^Y4TE~U zlioYWf*~1%m%$*x=>Qld+ z&%qtNuI{Y}BcyXpux39aMrqqS)W z&nNuHF1!~I-U|rt1%z9KPPv@BqbDZ&{IcJn z`B3Zc+~DZ9=9eXWtta#`6Z)8GRgBf$1cR@9xpR$XcH?FB=B6>Bml;)H3cbucZYD0_ zHCa6l9%rp`D0cdfR48_at_{6Z5gJ(Rmw|;|4871?HyVnA{sKd2+z>h)d^n5eP&{l6 zCy$S~=ruWz{SB`}Yr)43YUy|U=t3sJb0YlER~RMp3Y zy3xl~HyYaQHpj-}ECBm!P(zV86p2HTID8)FV4sKaQ&9Mc$Zv%r^6qGhLm}C{q=!B- zK5Ea@?2P$*)GxbzRD|f=^?wnH+d<94zRCw#bj0&>b}0KYC^r!Pt0wglexgSSMzOR>-<>3k7XAPC`LD6tqJ@ zyL~}B_-T&|og;ETIuDkI!hI{EQYhR%o*MPmSWGD9KmJ2z&OK`t! zPUwpyv;^NypOgT4 z=ZO;e453K6a>(5x!TT^Yl!KsfhSa;hk%i0y@kU6Cl|#8aQa7r7o5{X(ie^y zbK1a({dq61HsC(Bf>VIU9w#P_$Qn&g%)=N#xfOr!2x=Y>{aVACU%)u52e$w=3$mwh zAp6VUR^_*b2<*76{WSm-8@jn;|iN2LSryiiD$un7o6`sYlW= z^swAB0Pr$?=@%u5pH|z2Cy|9!tezrXu`ZlJaMZq;0M7<7NqbYZfRu6!k1^H+jBw1R z*b4v`Buz8alBF_N;auE1c#$`GHa4&}#(+A4+)9q-N>PzU;3@CYIct@B9$-{gzdatd z9El1Xbl8Q@KO$z{sIT7OaOUZo7h zb6fUl=aLwf#PVDZF8)YSuqd(SUMN|m>g0O!cO3z;p*ANjpU#o9TE5iW!P60$}(mz~`lmrq+YC%W^lYsg9H5 zjJD=4!IvD1FtNNI6jDgkc)@Q=(Gp=^axTeR!5B$ODH+)^=7#z=oO~K*C011x>#mA6 zyD&;0>WoHx5{D(~m|$6Yv&n}Ku@_;J$OOUTPG#Ue0gZ<#yt1E%Pb0s4VbIko*3X^6 zqjQ`Nrl+<+c$!gr&QF!+tP@^WSF~O3LDJm=xU{1_)PJSf0I@hDXUfSsugMB=>6v50rQ8;S6t3&-koH7XY4ZMd^vF z_j!P4FSM-wgtN_+nsCa`oEXL*#^@JL>03fBGArl`sgG&Wrea%bqn^H_fEcWu+o{}w z3}6u%sRR#Mv#p_!Q|EF@pU`M()K~kM8H>Mnx8b{u)NhL%PzUUyR73#Qs6zH$7l>A7p3WES=n=9X- z{x#VFzswA<)bjCqY*TdAp_TIkNUceZn6o_r@>A)+X^q-8l@Xi=IBJXBaFl771mHD!{m$feE_yC-k{c5neHOWBL8lxv*b*&A>*lIUn_ieAU;IcXo%I`k9uFL#3R zJk4HD^yQ z0~q_ER)+c?>JG_wF8|uweklXGqJq_Yl{5jqcfXefa>u1-Xc*ec@qTQTmJw{qGR8 zR=e1m)!#Ks*@FndNt>b3KuWAyJHa<$3Er|gYabvu&y@IN74xg7BF^mu=0i5mDm^LL zOUmO}A?1HIe}R)avHlJY)>YZ)q1818{tkCO~bYZkr z>?w5dWI$hfu`g2VAmX0EI2%^mOxKZXQlASq7ijW;ecGb*sx{%M+%=|?X1({q>}vnm zn=k}#!J9CQCajrO@#3vl!PIf*o`h-IeM0PlBi(w-MXCk!lodVzNe?B@V!KPi+yZOR zagio?e)dhke~6@xcSN2s5xHG?X0z63*!w48ugHfiwyuod(hbjDf<` z51K=%l+*z;?o|&hbr4WCHFXeBHa&F!P&XagQv~Wy5Gbe3vMh|2%W{L{v?o@<_Lf(h z^shs2ZH%?;%r03pS*A zADYBTO@Ms^f#jFdSdIKAIS+h~rwAYt~ER7mVviMaMvGO>Sv4F5)EK1b{G>$!vp(PRwDNJS1x&}|5q@)e! zFt!zwieC*@cJ8G6*QtXD2dzw9o_^Cc<9^R%|>i^IFg1EP>wc6?bVXr&go)ItH++BF!7+MfXUIqWn{!R`ze0V!A*yx&74H90Q z-r_J%Sb-S*}5S=(b3FSc$` z6wOEYC> -/// Creates a new instance of representing an instance of . -/// -private GetUserResult(ErrorCode value) - -/// -/// Creates a new instance of representing an instance of . -/// -private GetUserResult(MultipleUsersError value) - -/// -/// Creates a new instance of representing an instance of . -/// -private GetUserResult(User value) - -/// -/// Creates a new instance of representing an instance of . -/// -/// -/// The value to be represented by the new instance of . -/// -/// -/// A new instance of representing . -/// -public static GetUserResult CreateFromErrorCode([RhoMicro.CodeAnalysis.UnionTypeFactory]ErrorCode value) - -/// -/// Creates a new instance of representing an instance of . -/// -/// -/// The value to be represented by the new instance of . -/// -/// -/// A new instance of representing . -/// -public static GetUserResult CreateFromMultipleUsersError([RhoMicro.CodeAnalysis.UnionTypeFactory]MultipleUsersError value) - -/// -/// Creates a new instance of representing an instance of . -/// -/// -/// The value to be represented by the new instance of . -/// -/// -/// A new instance of representing . -/// -public static GetUserResult CreateFromUser([RhoMicro.CodeAnalysis.UnionTypeFactory]User value) - -/// -/// Attempts to create an instance of from an instance of . -/// -/// -/// The value from which to attempt to create an instance of . -/// -/// -/// If an instance of could successfully be created, this parameter will contain the newly created instance; otherwise, . -/// -/// -/// if an instance of could successfully be created; otherwise, . -/// -public static System.Boolean TryCreate(TValue value, out GetUserResult result) - -/// -/// Creates an instance of from an instance of . -/// -/// -/// The value from which to create an instance of . -/// -/// -/// A new instance of representing . -/// -public static GetUserResult Create(TValue value) - -/// -/// Invokes a handler based on the type of value being represented. -/// -/// -/// The handler to invoke if the union is currently representing an instance of . -/// -/// -/// The handler to invoke if the union is currently representing an instance of . -/// -/// -/// The handler to invoke if the union is currently representing an instance of . -/// -public void Switch( - System.Action onErrorCode, - System.Action onMultipleUsersError, - System.Action onUser) - -/// -/// Invokes a projection based on the type of value being represented. -/// -/// -/// The projection to invoke if the union is currently representing an instance of . -/// -/// -/// The projection to invoke if the union is currently representing an instance of . -/// -/// -/// The projection to invoke if the union is currently representing an instance of . -/// -/// -/// The type of value produced by the projections passed. -/// -/// -/// The projected value. -/// -public TMatchResult Match( - System.Func onErrorCode, - System.Func onMultipleUsersError, - System.Func onUser) - -/// -/// Gets the types of value this union type can represent. -/// -public static System.Collections.Generic.IReadOnlyCollection RepresentableTypes { get; } - -/// -/// Gets the type of value represented by this instance. -/// -public System.Type RepresentedType - - -/// -/// Gets a value indicating whether this instance is representing a value of type . -/// -public System.Boolean IsErrorCode - -/// -/// Gets a value indicating whether this instance is representing a value of type . -/// -public System.Boolean IsMultipleUsersError - -/// -/// Gets a value indicating whether this instance is representing a value of type . -/// -public System.Boolean IsUser - -/// -/// Retrieves the value represented by this instance as a . -/// -public ErrorCode AsErrorCode - -/// -/// Retrieves the value represented by this instance as a . -/// -public MultipleUsersError AsMultipleUsersError - -/// -/// Retrieves the value represented by this instance as a . -/// -public User? AsUser - -/// -/// Determines whether this instance is representing a value of type . -/// -/// -/// if this instance is representing a value of type ; otherwise, . -/// -/// -/// If this instance is representing a value of type , this parameter will contain that value; otherwise, . -/// -public System.Boolean TryAsErrorCode( out ErrorCode value) - -/// -/// Determines whether this instance is representing a value of type . -/// -/// -/// if this instance is representing a value of type ; otherwise, . -/// -/// -/// If this instance is representing a value of type , this parameter will contain that value; otherwise, . -/// -public System.Boolean TryAsMultipleUsersError( out MultipleUsersError value) - -/// -/// Determines whether this instance is representing a value of type . -/// -/// -/// if this instance is representing a value of type ; otherwise, . -/// -/// -/// If this instance is representing a value of type , this parameter will contain that value; otherwise, . -/// -public System.Boolean TryAsUser([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out User value) - -/// -/// Determines whether this instance is representing a value of type . -/// -/// -/// The type whose representation in this instance to determine. -/// -/// -/// if this instance is representing a value of type ; otherwise, . -/// -public System.Boolean Is() - -/// -/// Determines whether this instance is representing a value of type . -/// -/// -/// If this instance is representing a value of type , this parameter will contain that value; otherwise, . -/// -/// -/// The type whose representation in this instance to determine. -/// -/// -/// if this instance is representing a value of type ; otherwise, . -/// -public System.Boolean Is(out TValue? value) - -/// -/// Determines whether this instance is representing an instance of . -/// -/// -/// The type whose representation in this instance to determine. -/// -/// -/// if this instance is representing an instance of ; otherwise, . -/// -public System.Boolean Is(System.Type type) - -/// -/// Retrieves the value represented by this instance as an instance of . -/// -/// -/// The type to retrieve the represented value as. -/// -/// -/// The currently represented value as an instance of . -/// -public TValue As() - -/// -public override System.String ToString() - -/// -public override System.Int32 GetHashCode() - -/// -public override System.Boolean Equals(System.Object? obj) - -/// -public System.Boolean Equals(GetUserResult other) - -public static System.Boolean operator ==(GetUserResult a, GetUserResult b) => a.Equals(b); -public static System.Boolean operator !=(GetUserResult a, GetUserResult b) => !a.Equals(b); - - /// -/// Converts an instance of the representable type to the union type . -/// -/// -/// The value to convert. -/// -/// -/// The union type instance. -/// -public static implicit operator GetUserResult(ErrorCode value) - -/// -/// Converts an instance of the union type to the representable type . -/// -/// -/// The union to convert. -/// -/// -/// The represented value. -/// -public static explicit operator ErrorCode(GetUserResult union) - -/// -/// Converts an instance of the representable type to the union type . -/// -/// -/// The value to convert. -/// -/// -/// The union type instance. -/// -public static implicit operator GetUserResult(MultipleUsersError value) - -/// -/// Converts an instance of the union type to the representable type . -/// -/// -/// The union to convert. -/// -/// -/// The represented value. -/// -public static explicit operator MultipleUsersError(GetUserResult union) - -/// -/// Converts an instance of the representable type to the union type . -/// -/// -/// The value to convert. -/// -/// -/// The union type instance. -/// -public static implicit operator GetUserResult(User value) - -/// -/// Converts an instance of the union type to the representable type . -/// -/// -/// The union to convert. -/// -/// -/// The represented value. -/// -public static explicit operator User(GetUserResult union) -``` From 9b887c044b82e1505b7ae9bf3133a75739b6c8d6 Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Sun, 21 Dec 2025 19:34:19 +0100 Subject: [PATCH 32/36] throw generic ArgumentException if validation does not --- Janus.Analyzers/Components/ConstructorComponent.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Janus.Analyzers/Components/ConstructorComponent.cs b/Janus.Analyzers/Components/ConstructorComponent.cs index 70c5475..80edcff 100644 --- a/Janus.Analyzers/Components/ConstructorComponent.cs +++ b/Janus.Analyzers/Components/ConstructorComponent.cs @@ -25,7 +25,7 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation {{Param("value", "The variant value to initialize the new instance with.")}} [{{typeof(OverloadResolutionPriorityAttribute)}}(1)] public {{model.Name}}({{variant.Type.NullableName}} value) : this(value, validate: true) { } - + {{Summary("Initializes a new instance.")}} {{Param("value", "The variant value to initialize the new instance with.")}} {{Param("validate", "Indicates whether to validate the value.")}} @@ -35,6 +35,10 @@ public void AppendTo(CSharpSourceBuilder builder, CancellationToken cancellation { var isValid = true; Validate(value, throwIfInvalid: true, ref isValid); + if(!isValid) + { + throw new {{typeof(ArgumentException)}}($"{nameof(value)} was invalid.", nameof(value)); + } } {{Create(variant, static (v, b, ct) => From 8d8805e783ec153639f495934f5a793272b7a412 Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Sun, 21 Dec 2025 19:34:54 +0100 Subject: [PATCH 33/36] add named factory methods for overload ambiguity resolution --- .../Components/FactoriesComponent.cs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/Janus.Analyzers/Components/FactoriesComponent.cs b/Janus.Analyzers/Components/FactoriesComponent.cs index 2ee34ac..e5b136f 100644 --- a/Janus.Analyzers/Components/FactoriesComponent.cs +++ b/Janus.Analyzers/Components/FactoriesComponent.cs @@ -197,6 +197,28 @@ public static bool TryCreate( {{variant.Type.NullableName}} value) => new {{new UnionTypeNameComponent(m)}}(value, validate: true); + {{Summary(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Creates an instance of {Cref(m.DocsCommentId)}."); + })}} + {{Param("value", m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The value to create an instance of {Cref(m.DocsCommentId)} from."); + })}} + {{Returns(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The new instance of {Cref(m.DocsCommentId)}."); + })}} + public static {{new UnionTypeNameComponent(m)}} CreateFrom{{variant.Name}}( + {{variant.Type.NullableName}} value) + => new {{new UnionTypeNameComponent(m)}}(value, validate: true); + {{Summary(m, static (m, b, ct) => { ct.ThrowIfCancellationRequested(); @@ -238,6 +260,48 @@ public static bool TryCreate( return isValid; } + + {{Summary(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"Attempts to create an instance of {Cref(m.DocsCommentId)}."); + })}} + {{Param("value", m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"The value to create an instance of {Cref(m.DocsCommentId)} from."); + })}} + {{Param("union", m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append( + $""" + The instance of {Cref(m.DocsCommentId)} if one could be created; otherwise, + {(m.TypeKind is UnionTypeKind.Class ? Langword("null") : Langword("default"))}. + """); + })}} + {{Returns(m, static (m, b, ct) => + { + ct.ThrowIfCancellationRequested(); + + b.Append($"{Langword("true")} if an instance of {Cref(m.DocsCommentId)} could be created; otherwise, {Langword("false")}."); + })}} + public static bool TryCreateFrom{{variant.Name}}( + {{variant.Type.NullableName}} value, + [{{typeof(NotNullWhenAttribute)}}(true)] + out {{new UnionTypeNameComponent(m, RenderNullable: true)}} union) + { + var isValid = true; + Validate(value, throwIfInvalid: false, ref isValid); + union = isValid + ? new {{new UnionTypeNameComponent(m)}}(value, validate: false) + : default; + + return isValid; + } """ ); } From b31ece3896589b62b5f068273791d3f5816bb1df Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Sun, 21 Dec 2025 19:35:27 +0100 Subject: [PATCH 34/36] make generic type parameter check netstandard2 compliant --- Janus.Analyzers/Components/ToStringComponent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Janus.Analyzers/Components/ToStringComponent.cs b/Janus.Analyzers/Components/ToStringComponent.cs index c8085a5..d0354e2 100644 --- a/Janus.Analyzers/Components/ToStringComponent.cs +++ b/Janus.Analyzers/Components/ToStringComponent.cs @@ -57,7 +57,7 @@ public override string ToString() b.Append( $$""" - {{p}}{(typeof({{p}}).IsGenericTypeParameter ? string.Empty : $":{typeof({{p}})}" )} + {{p}}{(typeof({{p}}) is { IsGenericParameter: true, DeclaringMethod: null } ? string.Empty : $":{typeof({{p}})}" )} """ ); }, separator: ", ")}>"); From 4ba1aec723f3c7e6533a7186dbaa11b709e5ee11 Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Sun, 21 Dec 2025 19:35:43 +0100 Subject: [PATCH 35/36] improve space formatting --- Janus.Analyzers/UnionTypeSettingsAttribute.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Janus.Analyzers/UnionTypeSettingsAttribute.cs b/Janus.Analyzers/UnionTypeSettingsAttribute.cs index ae3019e..b5bd821 100644 --- a/Janus.Analyzers/UnionTypeSettingsAttribute.cs +++ b/Janus.Analyzers/UnionTypeSettingsAttribute.cs @@ -105,14 +105,14 @@ sealed partial class UnionTypeSettingsAttribute : global::System.Attribute /// Defines how to generate an implementation for . ///

*|Ie4CbjOUZ9DItFFN5Cg?i|^3=reC;YFP?*{PllI<B zl{l$?RSQn}(F62)cf63>nb5KG-obIFNrGI4(Q@EP@bD^A;@b(K{;+(w;| zJlf?I%sv=bYj&M@UEUd7rV82FAkCP1{-re$SlDUHw;Xdqf#amR4SB-$?RM5?Kb6>? zZ1@{p2LtNUg9La#+isOm z|M~tRq)l=?)68K4Og^0J3AcBoEm(j8!0=9c2=(u@gV0v3dG=;C(OS% zC?vHxbuK@A+fe^s+UYdg7U34$JA3znjAI7{?0&V|Dye>Z{}9n8abDU+k@U8tpK%3-uq2=T6_U2UU=iUdt5qo%0_ovl;*@*1~3GHsgc3=~6 zwuKYf`tP-O1if?Z>u!+j$%6)ZzuRw>jDL6k5z{6?k3WrfH(b1JK>aWFa|GQwoq{Fs?ZwQ` zKN?-@OekBOEdQo6!Ec)jy!}%-Z%{*zYR5|&Uy(S6+!hIZWm&aEkTvMNO|s7(=`sMX zR7?Mo(kcPVVk{m4B3F>kGjN6v!V%E%tD<9Hg@{I_E2?MZ9YS{QT@?48Ri5>xgFo5t zgE^sRe&=>z=I){D-P6X5bVJAPGH?LA9^UYpc*z0xpa$DIjN0u;^Bp9~dteB2#EY)M z_qJ_2eei<-_pNMLfL1WJW@Bu5==XPeo7K4&z*kOs? zn%?<2!sn%yw_Ib5>qIk(Z%%Ko(Ug{LGrh@e)h){qJf~eLk;0#bv(zh@kLQwIBi^7_ z>wH?QcHEOzAap;fgFXP{H^M6&F0zC9AUM4*P$JHD0@)eJ2=8KB8b-F@ajD~6KWytp z{TxO(Wgdn_PXUqVc7}5})2#2%#dmx8DlPwmt{#9MDV0F|&-Qcu@LGxNh1{{&*)Fkg^zB7*SBGd5Z zGvOwb;T3X>iTGiEeX2tZ-PIS7nCYrjgTsNtlV^qb zshUTWtiQ82gRWi`ld)LB9+E{`LpOGbAzbdfZX7&Btxo(uI(SgqS1J6(!FNLE4yl88 z*}L7f&V=%SciHVDx!qOJ_6!LQ$kv-{^T=D1Te=Jd#DiZG|*`&H1xt0+RvtLZwxN$fdp z-cYS>KmP$evKP>m2k53!UCRI`^*-CM59nPDdM*e*Yxr=Y+og{3Ufff`-X`IhE3~@v zWio^t-;&F_TYXZWi`u_%#j@BA_T&j?n)&*K`nT-8J4WL91dAbCu@Is@8X})C zVZ8~5__;zRKIx85UO8k3dGaBvvUn;?=4EqxcW$Wv$^Onbr#jbpe|gFbKrBWsW~GrG*@%_D>vantIDOwG1FRc@S6P}I=!!}nnvsr`QP`K zYSjH3qD{T}x%>QO^-<|ur^4BxJBghjy4_9E|J|uw<99k(7O{O6w?AoqDW8o%c}hLy*`J3D-}w>+&J zzB-RR`IDhVfDHz-u z;?!Y(te8*5tt_uUTy-A7A}{$@2ggfxS9|?pCw8P0U}|+$d8acF*yd_+eA%%gz%lir zAM{COk0bUe=_LfG3_pea-Z_V!3EA^WDpJ=0QKXKg?APDqtJm%MEU+%d?b$=eZfl04fzSo() zhRqy*Stz(ve;qPTH(DmVmE;`Ej1nF-NXA_iP9(995i*0w7YAolThrC z{c@Xadu_X(?Klg8-RZ``Lv$b(0uPJ4Hl?c`G&{n}4UlyA296GbmD-Jto*?OV?F4=D zkWJNkLo7=?7<#he&3TN$b2K>ZHE~iWU(mF*CLc}FrjCW^;R_do>XoN^_^dZW3m2TO zd1Py{qyk;iUd?5Fnm(NMI#H4>SUqTT@Bcc3JkyThvi8hl)Ui^72bnzvE{TWenJd}| zxVkIs=MNu0G(7q}+Lz15ozd0y&kp|2gA*A2I_z{iVlr?@0S* z0N(A74l;FkO6SOezXRQ3Ax|Ii8h z!seaWjmiGt(d_D@0d=br|KB|t-D!XOu^{akHH<*F+M~_^*K*rYF-}-}O}FBY@%1J4 zDTIy;ZB~KW4)l%ct9R+cmt#nt5q=%^DF~Ev@Qs@(w+hZS0Ax*FIb?YE<2k-MzKY8kybDbYl0_wb@6Z zW1R?Nt8&+$cP4_{WH;CNUSr$!%*uM41I{-pTdd=(-Cy6$-i78kQBR!Ed;D#2%zo24 zX6KFD0qmezcxz>S*&WHqe6+gQ+0eGiDE_juyV3Sk@5Xycct3nmvw7Ra`5)M)2BeIP^4a0a!Htja0$ny1+Z5q zp}-~_yWj&%JaR17E0}qb;X!$WAwrphdmZWi1yag#&Af6u)xVN$Dz`UI%KErASrSoZlZ^C-0Nv@OjShBsVq~z<4 z_a~QVCfp^?rPePUEBTHf$*^`R{%$!qQwvleI302lIiIu;os!UaY9RityMHX_euf-N z;KdZVRab6U(X0;4VbQ6Y3peRL@K|)(V*X)${Q*51*0&H);;h|<^p&U2lMc>v`ZdiB zaf^>#@SiI#>agIEPeiJ`$_-CI9&5G)!ez~tz+9(om^3Y&wxQ9aUXW#*&Y26co~Ai3 zx$6v^>zCa12IjVCPUddPv|NQuAEtX1QLL5+I%r@te!bil?1BmSwP%u`zwqan{5#;~+o` zT^&6S(8J!$p9knB2SrKu)7^0VtU|Bn0gmFX>k@rOx(CSNk(#66xX%nn?T5Re{_E$V zL!aq>eed3{hf2vI%%k5(lWq>z$UQMSkmZ3zr?zU|BVr(rD2h7Hje~duOlAAZ@wk;5qbfFtP=b{ra{ENPH@$Xyo z_bNc2yz+dB4po@DM4r;c;x}`v$g{!em#Eh)0Y^^PTtwk$iCmE(Nx>P`EL;)}$>hwu zWF6Z?PE{3ha@2CxFXa+lfJQnnoHZ>1vN)tizlV$qb(Q2L8a7DuqnZ=$6YpZIxzKQ zD`VoIuG1w4%fS>|Vu~CuJLCzt)QnmkbKs=R?W`rzr>8G}N_D_A+8RC`kR;t@pid96 z&Lf8c}e(eg{Pcvdg2vy`)no)@Jt>Cf$x#1Dd&|pDcB9}MRd^HIBh>o(*9FC z{yr*qGMw_&A-kiPf~fPlvl`sd(<(cAg5wB8w*PWJkRzQRbq11Di`Jd^o4D*Mt8SGs z?Fi^@QSYSB-Uq617pcDYBCjrRN%lL#^DOW1X2TCKsIy4oQ=K!W>%|xM8#No`3X5G{ zx!baJ9Jj^O3#R2_qT(|1J1?MNH`vnCTS+sLL2sSAO$VK}#kx0k-h@c~AA6(aKW7Dl zo{sQAa4it1n?!7gDC{O8<^>`$zc$|lc*-vg4{$F7ob|>#pWAs0;2FQ+MKZFk`=7Oa zocns`KJIi+1odz8^%}H(l3uNG|AIT_fdDCFK!h#`6!5G^ioZwvA9q{L)J?2gT=c9h z>LnaT`+4N?3++oqPczER1AxB%4KJd3f)4~P#QOwZ_c2^P&$w9F?0pZIKi3c z#v8i4M8)(faT78*e98%#$1Me^+?(NF^{>fue(p(VW{8O zsU`*LIH-^}9}$sz-Nt})>4vk#?L|m+U838&w7N6+&E0A4;c96S$a@b9W4l}Pf3bVp zCjIO0=%kfZf7tBSZjEYVveN)RXn0y3@gE#CpzRawzig)qPT$D(qOhODd#zJD&?#`W zN(q15DFC-k)TjKQf%>=itCRKxn};RrCl4Of{fD4cV*R~?Ku()vIoyo!J^O)c4=2ia z>UqZ9GG2P}S=Z@sUwsm|O+T3K z#+!LlZp+-9^+?CPej9FoCL}HDZ66gLR@fdX25E8ef-1imQP@|8wfd-Zs8iwG zmv^uB7SV08vorn^MtVUFaOh_T>S}-9~1+`%-rc)@t#T zUy7U--)omA*ynHZ^ld+<#B67R-x*M~Iu-s^ry#N2=XIp{j3n;1bdYSvrUr+(#**>884#)kC5!QU6am zlVH3CdW0;F@Uk%y13S`X*?kBui_jG^!Ih5=4Jr>BaE@o;Su%Tdnh3AZo3sODB{-xt zi^a>R*p+jDmr&7gSxvE9d>xALhRB0@ zSE0_6TX)G{f^zaRzSuEt25-}1D|yFD&X|U={_jg+4);4)SVD#4WpR%fZ&%0}i{-pI zrEURJd3c=zf}Gk^{4}eB88lRga{Mca#?H|i`2@A=B%&ZY@K%UQh4`WdUX{e$P^pBb z8ajKiD19_IR~dg#2Yx-TzK$5ZXHHPzJKAq;r)JJO!h#hrQv@IKw< zMq2O$+>NVfzw#(|_)&trRe|y+k5Z@F7C^UuMaQFUKYPgMUhsLW-Cl=OUV5PgJ|K;u zot!}aLpg>YGA+|>B0V3J+&&1hffitYUFq!`)E;w0-HI4gQ?oTPxU9Nrs*-}gcISMP z8vRR3c|mgg)})ePEaFOGKib`Fdon+jrZ~RzW?#~?F4&z#Cm}4+$TmIRX-_+S2)a@y*+-p| z4Yg3K%Ok5Noox1;^=RIgPO=uvewqb6*wri#cJ!4T6gGJ5!}vTv>;m6uy`0hbIm7n| z*vlDym&rdfYveeM}s~G!Tizy9XkXWFLMex(A|>M1mq-pue9z+?Ogwk{9`B zh~n1q?`h5(J&ZB+FJtLwtS?pnwMZ`<#+1{E7?w%Zze~T#`VU3;T@ijygx?pxKN7z` z7QY{eeE$cBACx%!peBCri}Vjg{-5!01H|tkk?)tq?|&!i`8tA}s(-6%0HX1}RQ-=J z?$7}AB1S$H?@Ki@7}GNVl-P?8ccdC`iCVrXe*b4t+i!9B?RRBWza?uBDGx;dAADc@ z{*ZI>M%YW!a!3&tH2ahR<)a+1FKl8s9gbTs!4e<3YU<r3Dt?)8Q`>@Yw*^nzW&%!a3&PzNJZ)R>v~9uDwt4=h zwgpey{`caS=W}YC=W}XX@U-p!Py7nr_JC(|>Onl-mwIp_l6vq&tS|Loibvo9j+IRK z7=BDeQV%{QKRy$SbwyKeobEmZka~l7cy~8MK5;w}heMIm-SJPv55=O0cOEz*rk=kZ9qD;7E06^oqej&=1!F%&;`A`&^#50tAPx{gG; z4?*M;@gpZ9(G&e}q8~nS_(TLw_rswhxB~a3J`?GMLx)fILi8|J5RLZ2@e`4*zGVG3 zBK?>#9*o8zk)Wt#{V$UBcT)BDKRrYp`(C6UIjWsm z$<+_tM^Gy1=}XoBZ8TMXKYEJc#zOK3llme0@e>j3>v!hCA(?>Ip} zeFEo0{X3Cyb|!iOQ1BV$QC7m!SNvgz~%5ey){~f`n>=j4kyZ!CcN7 z2Lzm3-Xv~AK>9s~^m{HyKZ%fP%BJeyO$y|G7duabbd=ktZ|uomN0ascz`?$uNK~nx zQu-l&L=5kzVn^et`Ufne)PIz!|0r4iVKj9_K2f^c#;SkTB^p8jW&+Wc`O!AX)z*FRck`1^b<<|LE{Q zq!;IhsQbsF?jK9=ew>PR6TkwT4^nj@+#%?(NU~A?SnP@9gU^X>$?o;Ux{{50vQdwA z$GQ@UD01EUJJCd}OV;#ms{XEQ_Fd7eltt_BJ`p>N6(y35-;YLnAR0++Ka<>Eh|?@i zHhv#P;nTg)8#}C27f;sTi$*17z034Z@)pZJHvJO~zAMB={y-eRCsFpEK-qhTV~66= zc=QR1$Kn5zNa8T%PISd0i4(DI1u@7JM3o=(#w;`m#6+V>3tAcX@z~L*Y&#l_C+H^{ zJsvyc$oO$*Sd7h&lCii_8vl197IVhL0e1|^?azKDb~M#^SJILsKG*NNj`{GTXzIal z;Z%4VXGZrCEJ%p^;8Qe963*Q6_vjHj?o_P1FIoRjkyQQf1#kF!{IAg+Ju(0?CN4iv zM#bDH42V(}V!hboRHG-^IF@Q8`6H2P^x^-b)<-moa)H?L^dMJl9HZ~PGqIj{s{Rw~ zG&l1TWkO;n6;OU6LHP-b%ioLiM&yY8=u z_UCaVdI06r&$GOPQH^(x(T5xfC6uiH(2@48DDvR94$}wDi)gC;tyKM6u>o8rd*Nd2 z2|01CDW0tVFrI3hPSt;$NO1QWr=w9hmOnmxgmPI(Xq*;EXq-M8i^Z`R64DWxkUz%H zCr(6=`SFZmP~r|if1D5@0l~*&F-eptUh!lldU_AXsQ%M#fYkOIhX*2w)b<+}V+iKo zVQL%!$Jr>sPBnT`jXwSl`|w`e%4*Hnfhfu`IBeg;umhp*F}R5*Vuw-AtG|c;KQs{O z!WF!$t9u}th{s6^{FF6j?;gh0{Pd?z#(EN|2LAuM31p0oWb(lWsrrv?%tf9!5wUp< zcJRRmF*t;k$=Vva2VoFmElI}#CJm-ziIek8t*!UkjA_G&0;0(;M15XwS7JHpyX_z-+{gQ-zk|3 zm>ESBPwXiIK$8%?Ad%%x;c*ZVw^9#EX98AlxayXND1#-q& z*!s9ymXU+emt!AKHNI{|Vzt{JrnU>r;J3#T38bCIKcn>_k%%U@m*TtuM9I%lVb|ea z=sMgR={npS($k^nCls;1afrlvY`g(a zJH%wCKrbfzF)7tI4y76(DHEKO3H;5BV?7Z&98ERek0$FM;%_q6gG3#TV_Y=V_#G+x zHr^NVU*mn2{~GVdV%_mXG#5iGSqqOhYhVUr{h!h&R-hvKy6p6HDb z$NQP=3oFM&MJU|b8y3-C1MsFhp1ZShCe%j*sK549$XYH9(EB^$Mb+C%=^4+(@T zaw*mq#r@Vhh=s33x&$)fU9{Qaok^GVad;qdq8H*l1Cgh;|2cj!mQe)}kJH9!gIZ2D zHV)%&e!{XnMeZXBL=ah-@r2R42!+?QbA zKN^cgu|K$dN@BBi*OHBI(r|v0M&Rpl%vet~zWzy^?e}H2`)Yn`MtBSJE7Po zWNlwXEEDN4z({H2Vfgtv;R*^Y_LUjjQ3m7y*QB}AwnDEKiEltn3$2Kk$` z89IfFOoF)QH*r7S4amrG2)mNBQzHr%^xYMUbfse5yv0eR8tNJJ5%-$mYqq>@BgJ!=0i)%Y&*?(HAMV^L;oIO7up-0%NftVism8t*3? z?u*h)ech1mz|^ zz<@iEl5Bk6!9~`S$o1Y!Hc(rTZ2WGrfjhEf<9AQu&hMwGMv@VT!%u5Et^qbm6G!c; zQEqPhh!0vwqfF;e+lTnfW*{1&IN+2chpnWT*bj?eN)q+NhoVtpjUPJ_wGOwekAYYW zw5g zPn`1~AlO_F#1D&z69g+BxrrazdVT8?Cve4ZDWl$!>k(z7bpTK5khi^Mp@a`9@e~kO zHbelzoof7TYWq`%2O>xpNuUiH<`F+f21xaVpHJ{79~sheZpGV2i9OcWp~I=h-^Nk> zfz2F>Auf9(-2;)RwSwbmNpB=dEah+exNR!U>h&6sf{hlL|>;2Pb?WF_kbCRf4PJ3a-K_T*X#-Q*z8EP=ymd;E?*A zbMJevr51yonazi|1^53w_uO;uyYJp}&ONW2hXsxP3Ui||8)e{OV<7Go;<>Lvocm4g zg_^YiaWFU7vr&HXz9%F3S_Sh)bViX7Q-?ZLRlIeER+7!svRDvcSUIya@4bc+YPI@~k74In2@e*Q|zMA0b ztM&DaiiepW`ydnigi4wvQUS`}L`G%zczhU|9KwdK1B6k8d9If5{pKW+nF8w}@iiR8`>ndjE)uSuoyXi!juRWPoy*`hI>%% z$K4$&aFDVNfuk1HiSag$4Yr#jhSyMAG;F%#I9i+TcxN<@a&i~!=3<7MGu>IkJ!rTS zas%$>cmwX{ykeW9>q3BFxP_49b;Lq+nhC%$+yb3O67SdqGA8&|6}40fI}i=GzmS4kdDG3%pFzU}TbymW`&LC;DDfK8;V}*gF7hD%i<<7Dthz`^rn|`8!bQnvLO%FR?xGou zneIK)J!`s8sjRMdN83#I3jLcm-7}_p!mNeOT3BWipey7OGuk~vE!Jg=b=hK_3)WSO zb=hJ)8I4ejvbhwZcTM+>>E7Y0!05K=J~iD_!6t8$3znDZ-lu;LDVvh-69KMLLID2- z(|usN)#dmvkoy3&YQ(0|6VWin+@s(V*`Ft}KTo*g6V;z5vOiC#KbK7xS^y-ajhgOt zS^2uGe4Q&_SCy~J%Gas0H%<4dLPPfdlIdPF-E%UDnz&`U7dTF^Zc*X|)2##&FQ}3- zkzmaeITwQ9Pnhm0)2+&+)zOIILMZTK&2S4bn)gz8?iRoRaC1q+J*bc$lE@F4%S7fe zDF96N$TG&w#z@DrUAj9z3eML%}mDJ=9bvON#_QpA%{=nZQEz z6OC90?jaWFb%qnV-q{mPn$AxR=g$mx0&)=NXW&aC5=+KHDh#kVx%`w?`k#^eXEBwF zEcXwfet^}$d4%Rs;!)VrVxdGdtWsM+#0d@)L9x2&3duv@=DA0BVKCf7ij^5^#X`sf z6Hs=u?eOdbiWphxhEaZm88#yutdDlrMD%I7gKH)l=vbDAv`a!;u0yuO|yw@Ou? zN)S+1K+THje%>$?bt-&T!M~ycO8V@cQ32VCStEgidrqa_QUONhJh>ND;suIcA|x(J zWG+hooJ8t`>W*Z7nl*X@xmU@(ra(QQN;kQ3jaVWf)4eKzoKiq;H1zK}rQM{W^Rmiq z=`YBtRaNzlYU;iUJf!ryDk@n|ru$R{nArDJ)GO(My4nhCdrWuMbZ1F6)I<2UNl8%~ z#X4oIlVTk*)1C}9-^+Dm0hbZ9q&cL_o2dWK?Qia zT$7YJFRPz1-OnlEhRnDuGtMrB`-B>&sZoouvi%oIqqB|F-oC*0_NTg8Yr`s?*22WG z`Ki8IV85m(R4!B#!DhvyL*h(+bO=&MN|x&22e^=wPjVrdU_C4fszTkQ?gzFN#XAi* zr}8>kf3AMQT+mE#j8Y&RS`)SK2`BK5n0&ZLnBqDEAq+X z74LN=Kcs#I8+!$)3kyd?rp5^dl9(q@6Ut(^!BtCk}SY(p-VA0tu?hB zDWZv#YSvA+t|XlGhMeoBdtK#RZ^&6R-9?qN*pRKgb8s&~wFG5MDJ~G zgH(1ja28TGy|H4Xe1Vght(gm-1B5BzizVljZ3q(4CFax4NY8HJ z$-6=_M?x)RS!2cw>nqd&kV3g(+5OM?&m_7;c?%(_5~W76^FcAW zNkb}6i0n78lD2v9@Xz*u%G8iF5i?ZMIK>q!Jds2wE71nV)F~t`QkOnMolk=fUtN?) z^Z+hzFoZbwRQZyQ^@}B?V^7YErFM2tqYs0ZT@jtBq3DDtd82z7T#F?Z3}{+VUY5^(djt`YKXP z;xUz#shstRa6r$f9rm=)qD39yy=ZO_zq5LQSVRG>M3lyv-NUwVEsP7nQU-B|;|Khf zd+Oaj>X3ha%ltXTwp^l>D(1D4H$oSaGI(nHN^I%MT%8W1G$7fWx82nVo!>qucih8p zB1%6|FXEKj7bs1yDu2xXFokcwv?R?Tp%!y16ypVx{+q!pP655nDHzi3o98$qEj|6A*gs|3+_8W2;^C%;;*fppL!)sjaqezuk|g^ z;a7RaSShoHMH(p+S5hi}35@P5a;2(O!qEM*&yi9Lc&Qe4skW^lG*%h0NS|Na4zi;;s@zQT2koEWz6-yK`fM~Zfsr>+@#$c zm?2B7GR}N<-70dAahVc9^yw1YnE54BYh0wEdcWjc?1%bTiIBwMP&A{h@nKFK{B zwQo)-^zTfHCCf~uGTw=n@$GT^1Q7K*>Kurd2c(Y8@onL&&4bv)cZQDlaePY5X{`GP zVyC$HEYlZmXhMM!QT!P`ep$N^-4oo5vZ{lX34YNltxyLA5r4%~xn0<7FXf}bvIp4% zloSP%5f(**!^Bm9^h>gD&K7RAm+DWQOEvG2W%*y3b=)a-#||=xWp05p=}|^?1c$Zo z82%_#97?P!WLF$pX8>c7C02>xACVioKY)FQ2xApZ9y8I>s7)dpC91 ze#uzS-?I}n-(21wf?uzvH(B5NZr^(C-+!*VF}`w42y5WK!_5Eq($ldK^W1doR)p3) zCyR5U7)g^VLR2(9_@H7%gfNFSo`lAPp8CTBrT2t=`dc;{&2iJnV|O!}jjxFc9+4$1(#iW-rD>TqBXJhYqj(CDEB+-9P+Xpa8W|lH*90zDe6M8qQa3SwSODshFk& zVcRJxK#75=YjLr1GER2L&%R1#C){q070t|vpRq)?XlR@TP zxIt|HdzkDP2hUv_|0dX3s%*Eti{N|U>}zOZim&UHwx2`VBmEwJ1AT%cBLJUca3OOk zb2)NB6aO5^gUh$@w*<0V${@NvaKefP9M%R7qkk%{20sM{<`E25EtT(OAn>o{3fQ2{ zHzL(R@ksI2G3KR3YEP=p=syCvD>w>9aeMrgXz9%8NQiPyQhj(c0Cg2MI41-BXw4TglgxpRt%6|CD}qUHp`!|TZ7-({+}GLN6@x7j4z(l@_pjT@Y{cX#0g5J!$w-U$)3nE=dKqr*;0V{t}`e;9{B4*QRT%7iT zJ{{;%8*s04+NG7=A3F7NsXn26Hs_?sQ2V*m8b52Zx|ZlpOaoX;Z0a5p^hBPrf_yAL z9<{rUVNyC0KaZmMHKJp;zeckULBhLD?FN1}GfV{Ch8&r^Eg^Qlg@ZeVtVH{gqXaKqN{L|~!hMav)8 zXrIvzb3;k=3d+0Vf)i04V5kHL%yU_5(|6UO)QV*psGK<3CI-MFvVs^?A z_d*~O?_vU-;AC&Yz?%#I9_=_~=ZYD89zq`420kXje=oQyp6ujS`->kKsf{sf!u7A5 z3HTT)Thsw&JRM##`i)K`kCZ;lVkAgKaul5_ztjt278*_nt3bzr#$hgq$8;FU$Nz*H zaq_pAFu}7xc9;W`7=y)SZlE(JLg;u;A=nu>GBIw(i#UIgN^nQx%PbXHZAslaKkI& z>g~sL?4h`|?5G#J>}DXtZE5rFwFv^V-K%rmBBbs!TzJe@>BX@tqu#b=G7G40sPO;v zs>Y!N;m9}=*tGUo80N|+jdX8C?xqsZuL=Ap?*`N2`tUsxvPOi;dxIH_;uws4d;lfq zjRYef=XFHT1gSWJbY#FnpvRW%#Cu>NQcH5;qrbv4p31$T5$ZICQ^$hX;@J39*!6WH zoMWj7+HusEa2SV&?is)9N+?So7P)=x~8nlXps+QdJJvI8?` z><~z`1S!>^G%Z1?k7LGF(Ba4oZeT#8WMDMdlFs)?X-4es~Bk+guD>jXr7 z&;tzMC~BM4=D~bIBlSdFmaG1>gHvoPs8WN2h))x zw9kBpjT_>TZlxj_a7R=+34&I1Nzh@G^L6ZHLxxz^@MTK1wY#O)^|Ps4({4SUn^X_M-Rt2E z9LbZB?pXV#vPbZL1a^N(T=}mb67ZI6S zDDkB=^A=;F?|AppE^Pr~ZaN9XQxvAL(XCNP(*|mO7@(cZD6neR zWBzT*J*L0xf$fVtt#i;9Q~*&?M5TN{RWGG+=7Xbz{hRQh?pX-d2+4LYrV`l4B(^XJ4d6)f%Umj_a>}) z$@ir^f8<3RBeHb!DUgY$9Esp0h_PD+ah|Yr3Ct4PKk#YdptL+22Z=P`PyPxu!twFGof`}-rx6SRi_wc&E#1{l>@Zbfo}B5JynQc=}!evy{P(u0@x3R;YNYsP#ZRCNRD4EY&Gx-yxC0IP}x-3 zT-oXyk1?Q-xU1z8?#;1VkhK%w%d zaSjx<*P_1c6w5D@y$#Z+-JM(Q_RY0H z{<;t^S)*<1jlmvM`Uj*fNPSpDTpjm|mK%5K>M?m8_wMQtw%wJX$`H@$f)LLvtAd?Y zF!i1qE!f9aA=qH25z#NPf5|lNs+ILFcW9Xk6@x4XZb*~3{0Ck*3D=MD*Lsm=_NoV7 zLG(}?1jcb!7eV?|(+6UUnW=KWqr|iq_gE=3)1?=&dB~EbH15~WU~M)1WML6~1;YV`m!G|g~NCR8;?&!30EEiy4HR39FGv3#zo z67UflT0)4r2UcOvMilSq9$BgR#tkmYmnn$)kYJ+{yH~?rWGjx+VR=a}xQ4(E2`Eel zZmZZd&|xVCNG!#*0C>|()o2-`6w>rD^4PgyO%#rm2Y2b>HpM;BvqN+U%WlN;P)?XL z{uD&w5zz`zYC{+AWi#dQ9A_%<&t9f0rm6lSaR-(wK&kI zss?RdLI>SV2e1&l+WU=oj!`dK>M>0jX9;$AOPzaTdY&{he-(F%XM^f(7v_kn=xK~- zj&!Cl+BFT}hQN&^M%S9tjg5IqtHevjCyRAWZ`((Qs#wxvU1M0|+@T{8@Sp1Ka=wc% zc8o(GV9%0gMyH`JbgDufkupn`FgH)(!HNfC+?jC^Ak@a#Sd1@5Iu_)phk{!=mB4fO z)Mm!+UDF!+h3OoB@{Zhi1fsQmEaY#P&}Ya{R1{d}cIb*6;c%P+sw8LMtj{vKC1SSw zduJT%5`g8_V&RSMXK1n9KZ1wg7x$8KoP%7?L!ISw6zUrXYGe0nRB_wXaNByeGn$aL za&8HhIxN)*J-n0}^M}YfBMuo%xwt?*D zpX2|{&Kmfa<-F)eREC>cYFYTFTWo&VR|7vhCrfI-KqrsoEX)E?j@kI{g1SeKh!94+ zITX^&k7C-eQfAgSQi@wJybUYkEvrr3^hP&`0qvtn3C1B)f2+?k`_<ll=Kq8e37>Z*^R~=h!fS5Ga$^ou8(fZ;@M@mTq2}Rm7UEC*n1qBg!34 zU+j6ZjoB-fJ}_RIm*v8QDc#>R4=jj?RM6f3dTcQhP{b^KEi21W-$Q&E6fcd?2yXo1 z>H*-XIM(^K+~OYlmvdVo@RLZme!@KjPDHtxz6SR1zMOWZrlA)(@H@?R`$(adrRBcx zhKH`1PYAH;0j}km0@Ru|@jdA_4>Q)H=N!YN$`Dy}QZx%OL3cGz*M~JZybDng>z#sl zznK(i&DR_wD$|v)C_{|Qd^}?ljJPO4_({MWuqt6LIHDc9MN6m-JWkc_JV`BSC=BTu zwPKhZ<&663zVJwyWT875RS-RiM-z?VjL0kv_L6DGGWCYJbP~E#6*&N9SY}vt!~c^| zOtHew8#W+lVILIXC_GbP>yI(bKeH6Kl2YRND#pJX=eV*EPCR5U5LGCEa)5{BVniFV z?3jbwE)I`Y=q9lN+7&`1!LRwCOA@&A>PEUnG>%^x529kuV&S(1?i`_AL0dr&XU=Zj z(T~YCJsjyq{qR0a<%WqQvT!sYoC=%FZT=%u4zScv6m;@qWg-UxgmrHiva6zNIH#rW zPoCPYgjD^N#Kv(ahTHA`2wujNWfxUnWMJPBi~FcPn-&B~KRX|;rN3(*uBE zVcuKPABI>yD<1NszC$1CWWP%v>SVqf9~h;-3m+JzzOx_frTz5Z+DiI}J|iC#rM{CN zsvh{T7&P_UArM)QZZ=PDQ(9qFQk&a|AxT!OLR`>O-4xw!h@$sDrLN%@J%DDD16En0 zt)jg^8S>;O0$8>k?REAMz}v{Lkj#+;V<@);7Dp`MiE$(ZIy>HZES?Vq6n@U?y-agI zEtB@|ScXbT4Dv$clq70`siEV9mZjbzlb2q^AEOoTE!ozcbZH_O!ift+7)VB{Pc*cw zw-vEBWikIr+(53;74n@vE+&j2Z@ZNmg2)U`hp(3TlWoZ3W*8hMM45Vo$Ptm#NwJ`A z0=aP|esR4+F#~UlN?Ymzw;~+`U4m2eGdMK0Q)qmWkQ-_cn{s)n)*lGLiSwn85BUb& z#m|CGX4<7fgCPc+5EOWY6MRP`?}X}AzzR@bZGr{`TBY01&d_B3X)Umg#+a6OP;}zs zMIL`X2Io|qT0_DEP_J3l#-G>_zq&Duo(u1YLJx_S>zW^ zP{rdGN9NgWEX%SB(W&U)0dbrFvX~r z{m@L)Iye|WfLk3gwV$kUw$mdxlNuWFn>>S8%&7Jnb@{_o&k6>Dyq4)tO~c-EPEoT5 z&tX$upBUAlK~)Ln;JX_QgT!oYL@o*I{Lfq-)7&K(3W#uRyNPkn)VNclzR`$%6ee^z zxz!Ai7!vtcWv<5`FVVTwD|RopC@{QV38E=rTZg*tz+>_7r!-gQuIy{;KHF=+h0);( zd{P`Nl?@AVElL^wo*LcN>?QdOWif!Het;OaR|vjJVVoyT?ippZL+m$FKtU`};}Vh1 z@ou6^F1ai>z+h#hLS0p#USc?w+ISIkMBTfcgYV4++cxsom)hbXYpqv`;T^Ju2O`yF zz}Yout@Dh_tvJ@t?ot87p*oG-FaKMLAfoZn2^0>Y_Q~I&z(HWiUOCQnkY?Av{hsvg z(|a1P;1^Bj%VY}`Bl~Zp9G)f%IeT2~S=8&<2S=p8j`ttLN)wRwR1rKnPw!44O&|hF zj>xC881nXXF>VGpknjiPo;fJ#+?if36Xy-;$xejH9zG&;~OWE`n~oI-hX(KU2bn<-e$F}+n8(e6bu58jQ74S7O{=~*l^ zEAs{DSPn+|leHeorj3V^6Mhq>Ux7kA5pl_4^svviIhXJh2fNy(2=lRRhrxjmjMCK` z(i`rWM3rFBS$2hR>N_=fO#2gLF@X85p*u=rIq#t;wu8~xD=FqVbWjH53fy|V6&I8> zUa9)J5ush2qB2RZk>N&VOypytll{@jwPxlZ5+(jC1BV?NuzhZ*GIm$2AY)uQ?xCok z=}(mNN_t5}k9>-8{-LR)T%l79e4GXU2V7+nP`QWEdFU9^Yc>9IWzm0NI|!D^InfSI zP5Fy-U?)VM^kJ^o=yVD$hN@-kas1yBd1WHl9)##g;f)MUoV2V=verN3YE;Gf`7l58 z2q#i$k!*N~L zpy2C*hTiUeDqZ`eF5)CE+ma`qsKK@GPLT`amEI`Y_C#(Kr+0DVUSheAU(epBRs8AnLI zo(m|Rv_r;0ZPAX z;a%T81bZdtI+RP2>3H6lhB(BX$ov3Og5*d!AEEN+9@W5zbgjtdNH%Ps3+~y`2C8f> z);VQm<}b{`_dVdta4BYRYJ1)^2A>~L`2-9uCV#EfJtRj8#wcU@%EGn`=aU!mVDCQ= zS`%0LU_T~zio%^CfW6Uey>W#wzKJU+B~0xRFqHs5fdF?%T-hRm55g6aEejr(BAR3x zZb9JC6-B+GG`q_|4?w;7(1_)QoeZS0d7j`lV&SHmco4^kK74whFKCaaa|Wa>q&?+A zOda#z0957zx#Hb;p)pY+^4+cwoDfxZOq|AgzB)zm$S`VGEz=|>W(xbRz&grn2PLVz z!?!?EvP*7|U2+BuN&8EP9TgLFN?71BAT}N;2-=OM+C>;<$e-b6`j|rO6Lg3%|3N^& zZABgA79}#n2ogP0bZGe8tWpP*R?3oI>`*=vceqWIV4vqc($cGdm_fNKI4~#3r%~kc z6%>rI7X}n5r^Bqws=mOKtAXA*Aooh{d7+Ie^~daqM=*8xhbHR>VwbtVXb_ID1x%fa zxcti;4eotE$TyS(8{v%piA!DY^ZZ6V`KHYGqe`FjS9JpQuf~d!lSr zn{d4Bh|Fnm%5a*^q%*q|kPpYwTS6AWn;Z5rpccqP(vrth*KngP`Iv4X^!`vYm_RSo zUL3Al&`gd}O&LZ0%j@X0BH-ZkX3;Qb&CdAmAJY;i(~Gf`4FMNVi^pc_o}BCbO?;0> z6cQx6WBzL~Zc7=9bI7D*#$fiPo*V(Ma)bHF+ub?uPM($aLCwY9kOA(l5%E_n#h0sd zztiZ37fX^mk-C463U$QuDzvX=cs2z^R?uJGq`CvvkdL?%rzbC0a*gTQxl!k+N#74| z92|`OG`3r#`_P@KX$7<~*%7U-Us@5mGgIRN-g^0AAum#>e{~}Gl0rJFdngSwEnPWi ziJ8B6`Vj=LjRvQ#HI7)CcWZ#!EE5=gWWMcz5Mh~XBMfX1+y`@W=>asfz5E&xhPjV#WnMOw|@cES4QKR4t&& z4vwH(Guh-(joH&+ubawf=m`(v4rxTH|21ne2&xXKQ>ie_rcl@t^Kg?6+(N&LjnI+~ zzh{P5$9GgFa|>wGp|OG62Fzy_91retN6HG;aONYm~ZcV>cWmg-6; z8yZwx1y#-|#COW}P0yQELVU@HQH8|XJ8XUk=p~7omSc!l2+1N8DZYH0$1GH zthpK00#~Fywb{=2B_npD+G6@!e|cEzGDHcu`B3cTI>D@B*?nkUQV^sYN%BQ3AleDcYnmrrZ_pzIO_^;(r8 zgFT@M{l$19lXg>!c8IgS>VEDCxrB!+^DuJkQt?8i zjx0-~D{<-<4$wsowZ?^9>*}Ni(?&qym~#e<;rRZEs|Sm@;v%tIDcoM$5{JR<_+7@uW9(N+uz^I|wYf|=evO+DQ*wnP# zhR>QW#w5RF)Ou1D2Vyt=pZN(b-1<)YXNwalf=gq0qev;gT zNTAiY=VE3rG%p2`SCu)AqG#Q2Hr3YW!>JVgX>Z>Zs0>b&hz~T(Gg02d_32{% zTxzvRtR6-BjlO72do9azR%?vYXx~3jI1g6k>R#4rQ`mE4V{KE%b@h+bYGb-bCF;6)`f5HGj1*KcapLh3ljp`5K}%~Q6V6q zxTCYK4!EUI1%l(`PzM^uIA}QML|yo9@c94 zyIK)#2sT^1?$?fThCojrX6FOuYrIBXueX|h8=l`B4Ky@7^j}frXw8W|{{6<|X=1!D zN|FyXU%gpdDWLBfd%D0@Kz;xM&4*GgZcGZxxr zPq5u#Yk1sRz<>JiG{fs=hmE({+b#KbX?QBH*F?khPm0WwoGIa@PX_5~(sg!Z0=GlO1Zre`tg1x4PHKd@A4_nXQ`n$TmUh3P5w$MFb zcm}n)#~zMGYT`Vu%9o&zu^$084^jxoKSbjR$%=mDll=3Tngy5V@{7xR+1?q9F;`~5 zi-|bYr|q!>s2Yz+{R!8#k>GfYzR1pQo;v(c-d~Tk0a1CC8#u!@!s`1`m`})rwt%EG zC^QL<*QqM)k#v}t9D>(r9N&FMSW za?J)DeTackbsVrqociv^1<9X*(!$A9e#Xyymx?r-Op)ytc->g-Gw69f)Bm0#5&YiW z+{I6db)9gy*&-AtG%R7b!g3;*Zzfo3n=4oy6OtNxov2G-k!##cPmf=;ZPZ~dJrF84 z%@W@*F{~SCTqn-eE09l7TF9qrcu+GOsR*P}`OL2T$taLRD@=C)@V=kf9ab(*j@z~7 z?LKapSh4}JJACaZD8TPvJ^fkrhK{xY9Va{xOhr=w8*BY}*Eau^adVOC!y<=+&x(ph zy@-n@^H?>fULR(&L9%7tqY~M0`&;O*4mhyu@i>#-f(VY0Qvd^${yr8-UH(o#4hlh* z{{ngzl@i&ww2*rWMIixJo@v2;Bf6QeID5fy({y&F(9P~K>Wv1-r%pf@NPvz6N+z&R zp!C(V4rG3MD6G#enk9+ji^D|j#0Gg+@jkk=#+mfNjZrluT{*9JdMi)mE*eM6(gKfs zwSc!fO4pP~0D>|ZH}XDcD(5BrKMQtCd|I?PGZ<3RKYp8m>?JQruNfvjUgnKwHtHB$ z9c6r9Z1n#|5|L6VdK|K1o_i+I`N{kyLV zM#ALT-yi%2>{S@!)?kQqQv|Q|Q7}AxGYTRKrhg-R32-ra?k@R#rS5z`8+ab)#e7VS z82tA81TlQ@(`=PJ{92#*`h1F*1Qc+kT?#rlnXzcl%pUjw%-@@yD3K&^d)&QTnZDDUHZoo@*3xE?P0dOqC# zzSOWYUCi|P#W1Dg%lm$_DJg^xbw!oTi|qBt`AW#8QAUw(UQJ=Z(7dm=g^;WG$ez5a z`+s5KdLjf9nGxku@|j>kvw?~J6FFb%N<0_Yw&qo@;T&wR_bvJ?VXso1PurZ9vX&+3 z6=laUIe%;ai`d=$ckX)qai2CZy>M{)LlmuU|)iy^7SR_Jpw;Tlg!( zwO_W|GoP*EcmFQqi!+n;J22e<{uC?ACCTN((BnHF9g~Tn!nbFSn0Q!n-=B+40L5e< z)KTaH{W|J%zkZGODT;sJhvwOS@cW-1D;~S(+BZ1%y#e_b;?WoTZNhkX;xjtDw~mPn z8nqwka%TQD-R?VhNI$3GiyON)2N>2z4i?8wFP=*5WN}X)u_GEAQVlRiu7_hi$Y>(xru0D{x!iBqxtRac2D)g2`P>p7MYsn-T=QkBA8- zZ1Z=*$3x8DEB_Nz%WJ_KdDs|>fxjuct)Ke7{O(T?)l^V)mOh2z7RH1=cMx+xKI++I zk@%?G$_0*6QNj%&H-e%sy^QzFh2kem!Kgg6?<1w#V@0)hhKuei{@cFq?dhXMk+#sdO^{wY-v7Z#HjXRrlaYU$W-wxjrR_>ELI zA-X0|b@8V`@=7O|ZQA+U;gSiIA<7#qF036we1m|xVr0a$B z`dd_sr$5ve0+4tetoWpB$f>P^)8!~QJ1`*4S$r6e-D6o@EL4J3lg?O%hOS-*zY0{$ zlTfqvn49h5<71UT=cb^*L;@vQGkFT%@y>d2TiUMJ`$GR4E=nl6V(bU z*)?!fxDkxBX>#BPq*OGx;9`YDSxe>-H8|8<4vKFhsS%%6Un7D(<^RR5**uLfC#Wta zBIQmcSL?tqdwym_ZVQ%8RV#N{6yB#&<)YtV?il2JpmJ-ALqZA?Z>>8#~21A3|?u zJi1t+QCBSI5zp4i(#T|ziSTtLcoaDH6G-(iu}B(>qwU2VR=vF|g)cPiMwL)bhb`CD zL@=T4+fE@~2eSmvO2Jp8?hH9wkBlK56=qwd1X4=Vgs6&syTd7#oImc}Kz+TsQwfBW z^Lg6YCca4$v#T;bJ?{yn0-mkfOeMQ9tjM^|v^iP=u;tPQ^fr@H*=U5&{U30FmSnk{SK6Psf#z?~!-8-35MnlQc1ZRmE4-)v z)wLNdo=+k*-;_M(Pu8*SR9dO6*5u4^V_2iPBZ++~tW^8tySP53n8@7`tkVS2&V75t@Dd5(%K80TnpY zmy$3NdCu|o(n)efB(Uc&YTLWn{DZe*f_ho~tm47bPE?mGhYa*rAT@L<&}(FUpMhOL*s2>p3FNVZQO;8p` zlVBHA**z-QW=5W9xIt2*qSbF~itcz3wh+FSQacXU2J%(aE4M4D1?nAzTozlPPsnmD z@_*lfgNqyy=ZZJ(YJ{=Vz430E>4GM97sXtj2I;Q>lv|*~1ldJ<0qc5jp724wLqPY` zX5KoXxv+T28PCA>Fk!ipz`4gxTX9mNG}_4w$+K3duTO0nUbl9ih&Z>B!hBj(FMQ!2 zl^*1Ez7#?i7edQ7?q3a{m<977ye+e#(L_=P^yKMPrygx4c%iZhIR%IiF-Z z`3klKJOVnM^xTO?c1cXc?dOd=y4P9+?WUH=Z(SN4bs7;BN&&y&wKlikROEA9rkHH4 zUb(p0wOC!SeI4&XLFZp3JbwGOzLgzes2i=0OQgrP)!QejMn$9tGDH}7@&y`D&x>0c*L6}kV3|aBP}i5 z42cws9#hSte=l_I5el2R1M1}@AB^Zp9KUK?Eo*AM{&OoDk;`=c7$)Mv!#=#Ui)|Q6 z25E6*BjA4=GzC}3a2(0dR{8sN-Kz!=oa=peaoaSNlA4Bm>G{3$S;EMZvvl{_I+=Uh zp5XhUSI!HF(tgs=s^8DUdyf*y++pmxsT=cA2;#+!V&eIU)Ho%Q$cQw%e`5VFrs!^0 zX0hADszJ|3C=Ms<- zDLSQH-wJF0nOT4U0ipinI->S=E~a)a2ERNUOr7-@+-+@&=Ydhr`rP1x;HX20=iPpZq|L#AJ)c~9WX(Alz!&;jln3UwW5M7rm zJnF|BSNnQd!`EdburCTH<7TBb^W7~YfZGx&D@UrDf0>U7biZP6>8);e36=YAoOley z@|wJvr+;KFciX_Ym(T(lj!rZAor4~yxxwoq=5m;P12PqhYvLKPwNR#fY1eU{a|Tt6 zcl)>lXxMZk3akS2iqr4mLjwbMEYe=$SZW86eA(x^rkegi{(R>5Hz?5mYzP`nNrC`i zAfUjXhEV>2a4<5lHZnJ5bT)NzwKOqxX0$bRF#;I57%`gIJDJiuIN3XxI=NW>RB*7F zva+(6nKHARF|%k+uK6-Ue!qA%^Tw`Y@>*+WFLCdw?Zyy)yuO@>uTp z(U0#emKJTAw_D-1HV(Qux{vsy0~10Slh$LbY;crp%a`4nS^e2s_PUo38j%b~X9mog#6lo9wO*^dr{kV%i=EPVmTXr7SC8$oB91DL_l`ZMwjs8#9XS5c3Vfv9c`j5n6e_;OWDh~M1lYx_|jWZ*|e^Mpc|A|P9$bS$1 zgH#L(1jO=RSU-7jfYuK=6$^WLOA{x122p!}sj!`qjfbOwbE@{Pv-aK( zr@mTyeW>p!$bdt90{{SD0Z|y=v>2cY9T>p?0Q4^a0Nm%Qo}-Di6C=Zab3*KpTFMp>e#7#@5Ml>E_*}*mp z%wPiuIS-q)=^dksLz#AsDOIl&V~&l2nc(0_SI3e8qsi@n*?jS878Jtn%=7tIa}&A# zp#Z3ojeS4@Yh%v!V!#q>@J0QRJjIqTu7H%GlzCfjziwI&q>l(NI?C&Pt(^ZlL6w?2 ztFT4J$CRsXr>Q5FpPmdkj(Dgz)jUaS&$M|h5q???1Of*r{9kqjtNjpy__S$0H~_%% zc|ytD4rpQIXvZL8XKW&DYhdl+WZ}f1;%s4U;p`z{Vr$}P;B4pU#2{+o%3$l_WN%_* ztvsfL%Z%K9i%ydnRIh7oO?9q>%-a&20QY^LF5jTydsUoiShC(&fcz+9{$g7rTKH~c ztHiqHME0iLj^!zxM?eDb3O%zr5cP`2d^bJ3@yWS0N9{f=REd8KYIsp2o3>RrGC9Mn z0gRH>NxUGY4~+#KkFa`>vz?B@tUbkH^WbZ^OjBysQ2i!GY%EAg4r zwEcO(dF{vhXUL)PZD7}1Hwki_ZP>}DaLc+iWr9(fJv;jp4j}!sjW8Gk zA9;;RlJ;#yQlSv)N+u4`_vf9q;)BoqE;rQq7jdwvzQx$tKh31gHwYMnPumDqPFPTPH(=$KCvU7y$;16D$syf!k|N1{up0hVwhuUgz&&;@o1QQ2*Re zlH{Bm|25c9qMRd46q8#}`(tTq+2Jxo*T=h$Z`V7S@=TQT@`j513x`(ZluTeBa}F#A)MinQUhy5LjdV_7nmMO|b4X9WaEqOSoFM z7n7y`mzh6=aIUT|%j*&EoX~q%h~coDa&Fm5!hane&DZ3no~RmpZ1mX*bpflrb9ZWI5D z4j_9e+sF3=@5fJke3yIzwk-8Z!1WNb`y0~V&M#rd(Lh5?1T_LWaC?ggP>3*Q3*JrX zIr>9w6&@l4Z=VoCly-L9qTuwpv5fLobB~tE<_l`|1oRgH-6<&WkB%;vpPcyJ0iEJg zsK+P;x8h)t)MB@3DB#^EtJ07u2%GACp8fIEhR10rb%?Q@edYB4IB)<3utDDoK$i@+ z!2xh}7`Hz=DcqU>*t6)PD*iPd%?@K$7TccqVlQh4_ZFi`2DzvIv@kCK%| zGa=zi0n`)onMUT8;6~LL5ZeH*dMyz;a&|Ux9`?fX1(aWDl>>|kcYwBGZsx&M7H@b> z^q8UuP6uBR{qWLC^8ZROzkL6eJ499^C+dK|oyc@=%nsS8frIDVJ*i5xz-h@a1_-sp z+)bt2!t@x53|Bl`mF^`kt{UI;G1+!8Jcd(7P$w+5Xzotsa%HD)XF(rC*G1``Jowqf zuF9T>IHn5aPc{g{!KoiwlffqyvTIYOFqZpECD0ai{k$3jnWP-$i-M+>5iYd-goy^- zXyL)ck)h?fr053n-ZjOS>XcrEA0Awrk#{)o)DdK5<4teW5r@5i#3KsBi#iP!Q z>*WE^x*Br5`}0%Dm-5RoU7{X^ox(dh-BPLPM7wYz%VtS_zFHD-GAB0UHU;vOal17+ zdB@*`mJ*wy!SQfKnq>x<=LoVr*U>D&c2a+3N!4yKVTsNbK#T~_&DDx1V^HrmD`=;h zs=h1N;fR{bGBAn))h#=}LnY^058|$h4MTEPRZ&6akp|VAIX&%gxt>uiSQ1j5JQ9=8V(U`A z6d<$W-yce?qS_oPl0UkE#W$evS77dx;fr9)7<0_$|uI6ui{g`V140V+AexEzZ)dKf1Lw%lzO96^FX_ zWyWp_jP_*?=U`{cy`$-}(P*>d-M-~(JhRk#r>Z}*JVyqyB~-1Cn+>&A_DTLlKf1$A z&Q%^hsQd3@i%9lwR0U$2Zl&ySMjNdDZcwZZqDnoGYx52WtO5)FCOgw}Zm-zRuEGl{ z+m^N&A>$hodcwBAHgj7KVc=N-_Q*kaBZ8EJf~I0JA0}iV7UmCTTFo0q+UMk`>)#qP zaemw*UD}a;E#7j%I8RHq`>TVkl|zy?wA06o3}QvoNp{M}*vFpeB}5)s@=}$QWJ_q3 z^ewh+QX}{I>2ccf%v0;%ocPZR+jya89O&b8?;IiX*PZ`#e6jT+yaCyQ=MX(-$G%PV zKTVZekATYe{5S^vOig4y4#_}H%pa_@S~p$5n=bnQS!|N|Xc~Ij%(7F95&vkSyumlp z;nXjW{@)fk8g`=!jdT&gl3COcptqVxYA|h+(y)@^B)6j9FEptrXqjANTT0vISx1AK zQEd3NA$qhA>#SY0rP}SHF{F7F3pP!C%0zq0gj*(38J*;k67P}{u|TXQF~(yZ@3M^X zKdZ|Or`7zbA=>!#r_~~h1`_3nEgYIle`rsqqZa<8yP@aTAg-Q!`<9=IM<(h8$oV-? z^ZfJ{s3Kxnj546pxb%=%^j2E*o&}Di$+uW&+*&NfvR~SFv>`5e4^B8&@rOp2dfZWv zRN;GSd}XmMR4De>ig}+t_>vzE6=I8y6oJ7w(8f*3ogwmkj(0$ViXC)KGboDRBu+lw!pAseLrz4u}8L>2DY^Q_Tj7BknBF|wcf z9Wx`!pNVYBLn|&nx>@x#vOUP4b?!?cK>9QW7kZl2z;$E$?qmoCylY!z({|fbCE<1j z;Z7pea)#_=&O)Y%d@k=4K{#r3A9Zy8WwlQPOM=B3m8`OJ6QQ?0^jC@oa4>EtWFW*5 zkw1YS1t5XSw=EXhR=-hT+Vw`C(@w`6soLQt`gk01uI8o4=OCX(YV6KzXs&+C1dt;1 z0FVgP#Pm;;NrsY`j-Mo>WEd|DH-egq4Car6t}gULE6OZj4muR>C7>p{a}1WlVm03h znwAUB3Um$M(=-w%KBg!$aZtjQGnBV1+C+SD0i(k(B)0sf-DfTNRpLl%{%e=&*Pc6i z%LJhcR*1Q#VuNtHDbl0FMx=-XUaJ}#z}Vq38m8%4Sh`!8H8-YQLh<*WDzd%$fvjeI zrgXaK)D~wKD@iFDiIxKWJ6GgC zo2TH_$$38>Y%tpy^pEyP3TthQ{e(TTCGVTyR;9`f3zd42Vu30ZN`*UL=T;f&tMxPR z!SqqDiIFxA74az}jKmpqfI(M{LokdGtg=|6aIohHVnpDD>vpaxk+XbwKTez)q(&>r z0&iupZd`jSdnze%5=w}qV$N32_PVh3T^`{t;kWK=TyHiTsw#r1CiLd*B6d>rZ)Kir zb{?&Xr6tDo=Ci*IkYbTS!8l|Y46~xheI>@Tl)#nXF%;nVJKGa6liEEg>N=vV? z1Piun2j>6sb(gTF5GM43K1qa16PtC!64&`LG_QkSamj?%BHi2gwYS{~Fin3u=r5^P zBd}ME8Yhc=vJGzFTR@5|JjEUaGg**PyUx$y0ZpwIi93ba=gi;Ygw+Uzcke1X77eC= z!q4cpyS*29AqDN#mg|WINSr#otHfM^;Fj4&__F*-8>z+~hM%rIJZ~M?eji*UxYP@(jUDui-h%Re1)5BV`Z!s9TRtW= zc`JNbT3I`MS@8mFpJL~w9%t%v7bY7yKb}HAE))C6`iGz^S^v>t0&{=*=(8}Iq|+}p z4g7%0?`#o9N`1z>L4IjWZ0(p2e7$^^82(Z<5J{mpz8GQ%g8Xnff#)T}L;+y8OgZB* zox0*&psUXFgx?|ukJKBLeebmdT&Czl@IM3)*-ZUaFMOyam6|Gb<&-Pw#ZK}5PU^>@ zCq>UHwxr$;L7d3kpZf&NnvglgD2r{_gj)gmpaxt|=jUY7l!VDEIA%+abAf%el^!@F zt!%nqy4mBZtyA@V=vI8y+2U8dzDVOkrQ|E);lYlp2aqx+YD~&{K~IZP>LiFXBHC9F zXqU!;#kf`ovM*AY!fO>&OVxyinK6{|@<@d!qlvuzFEERMkge8EH| zlIv|>4^IA4oD*!wGZ3}1Y@}qP7D-aH*n}L2jR7BqUIdP{VF)mL5~ADu{*{2_$%5D$ zrq^r#1ozIS$h#LWl?+ocp^c+zIhAy8hM7_Z-VxIh%tY7aIBDDD*ma3=niM>tEqXMZ zFXUHB!R_SM&*4PU(}f~o|Jvf%)#j9vJh|B7z34i?$`N&hW>sz!pNXQD9$0>niLp)W zT)e}`g4C)I6jhTvIRk%`lnR+?f$cu~-j4>`{lw0(T=9z%&5d_;Et=d0N8_AM9+)0O zca8a%0+~%9cE_K7A~mEG8t}rwTqvAD@f5Y|b`pQ)S_gjOZ|2PHbe6bMZoBH1%NuC+ z0bpDd4u96K5WkB^>O$DSVBxqxs4Kt7bmUopEvCFn0c>+izL=XtaI7rvLl(C2@WFG#T6QOSV3VW9P)-^_UY|Mt^#*}b;uATnpEoTL7svv zHeQL^GdIJ#wCRWYiX>vMSo6haNZ$uf1{Iqx-Jcj_j%E#NqX1Sqocr;k>BIg8F)-Wv zo>nk*BXZOj3~$0YH1%XR$W{lfTcZ;i^tMd{RtdXfc>7|&uWw=QsoQT+4)Fo2oGkjR zlNX`4NuxPUN3w*2I_Sh;-wxdXjpvMgVTAGdC_7}hFffghy$Z8}i-2?toz!X@V+_AW`)T0)_b!8K#W9Zg0 zgnagcK{BaeKr5IXK<#G(C;}C#v6NV{kaH?Z!jo{lZO1)J7_w;@#rl2mS5E74NDl7! zqt7NmKhxjYP3@}oZqzqgbSy0L-3Y_Ajx{;DLbE^-r9Bz`v@|B;1DWjy#=!|iNh53t zdlds`L*YHTbAdBVJW49QZEfdS!Lt#@{S{E=ft!I4uXTMK$JZR-s=ZgTX>6OwIg zQkVP@T^=8&%=86=Cp#1iIiny)v(h%Oo~(?qm#0c6)}x?TEu2O%cLWGW@8jM7^#GgZ zkQdiW$vAtAPHnWMO_~LL=6rF908O56xJWcPnMuo(afitUsaSp(xO zl$FQ?@Q@)Z@9oj5qnWR>Kk=FGXw&{cKe`kTidxpHAEkWouq9U@Ced?VdX~j}#OIsu zt$VXGzaJ=kX??r}pQZfy-6UB7080hPv^85uba@EGZBT_6HeL@D3`BRfU%n2~qC&uc zwH2OnAE$9{(Rt?_?$>ouA$OgZX-=)Ovc)gx`5v-5lTQrnXhltD6t`Cj_ZkGmCe zukkkE>zRJuo4d%~)^x)l!pE-XE>z#VLr_~I7N`5R+%A-S7N_mia<4(z7&PZJE=kr}Cy$62s2CgbprYQ*R$5KLhSzn=O$`3WP9D2fO zt?CTiY2>{~Xq*sJkh>7V1gfApy4@TTnGyz?h0s->s$x1DkHAG9! z@9sHTFnq4(xDLU;-j5}nR4Zz1_kLe=@l%?tE6)Q}ydn zixR+aA{L5KaPP*NxBhuV{yUmWu`Uwc8lnv=p}-Yk!4aHfq4Qcj&WGQya)IE^2URkw z&#_=UoMw;qZpO(>H=`+gI&SP`i@|(MVdkwun`^5ft%7J_s=D(XW0nDZ&LNYXrf{`9 z!?skE_B)VH5Sw$(Pa6OEr8>P(X4Bh!jkw!ZFiORqAe>YzAl-*#@}TE6%J#`aB!oTs zHWd{|!gtmi&tq18!F{JRJ6dLq^!IPvc97F28(!KRg7iH6*0xAN+_qQdvsjGHF@&5? zr4641vbb#Sv9n{dy)w{>#^tjsatO-)>$SG^H)0S$*E1hJ`Y*uE#?}sgD6ylp*<)$} zED#Lq;-l@3iEX*`^yW?-Srf+&Rj84m^tw(q0>(E-k%Yfqh}Mg4Yg%)}^h!Wglzf-h&?*g9KWAnn7#XMw`A9>x!G5 z^cR5^R8@Er(EU0JG~AIAC+g9_v*chG|h)q48yCWTxb ziJ!h#f_2g(G>DoMac*!&bt+zc5{2;9WMYkwEmkL-(#v$Vx%O&Vm{Q9Knz6Y5E9*D6 zox^K@f{CxL@fEYE?&PeJv{`7oY%L9q7aP;t0@2_iLdZ}a-?9$mm>5gO7(OE4y@sbC z#m;Da-};qydXUT189$M6&g$ef+ z87b>d#Zi(c0*@7B)$6DStU2hHdQoi+3l+_3l^s1qoP0daYNr%19~rDrVtC|`Ld!Nb zIf`|EvFX$rK4-3a3+&++IWzS;y4Mgx2eO@r82?J!bYHDS%wSsx1|s+);NGsn-bPI1 z)i^}D4lLDxLQj{?6n+|h&5EdaGTWA0KStWpFtrp(ZOwmFftPv7lumo&-<$Rj{Fy;B zN=<=e{}@~{0K|)Yg!m;#%8x}! znYP}!7@m>i=}k8(H#AmnIJe<3G=%+z!}2scjZ^#fB>ryyam~zbkKp?&>2b^Kgtb~e zbo)Ll=L<##lWmqnaHxm@tmEvrKYEczhx)8#<5gDLT~UXtY@Cs{$p^28zZ%;}cRT`g z(thLmh*A9Y^SKSKKr;g0Fl?+<&19puOsO2S7=rBm7&=w?lh$bycq{=$N${xKz@s(3 zhNd;r$7o1Qe-3@0+h$uK{5|OJ*Su-S26h_+>y~uy*{7$8w&!zsOLmG@rXL==1E3qJ zUFo#}N_OdICrWB+Z7buo;XWF58|9bH#kt{OkjeesF{n06KxfXg)o)KwQ$E|F{B0H| zI#KVX+-QGFa%jxzb7cFBGi(U}_|h?F4djmH9~|$wl@LBqu(4MtWIhUwAyn2>OrsSG z+S&3CC3Yq49pAh_t(Qh}wsFJkE|TwKyOu1inb1S7deGfzWf{{e@{nRXnto1Fc%D`8C+UTNfX3VG%~TYJH7L6&x~BEv{ysN?&{QXhq(w zw^Nd5cTqqVt2x!s9W7+xLOGSA;FgE=*tpLxl@^mRBYv&Y%|ZHe^gPMD8G6IfNDMz) zIMR8_hIQ(i{JUbS`sv34<*2FF>81yZK|kw1Rcf@n7eH-21eTMj_kL{Hc^i2L`1rvAUgp|fSvdCfb zvhuc<*`W{trQt_Vz3}it8rH`4FQe<%pPdvGL;!zaTEnUiPOwh$4c(Y8!@6A&TkL=n zJH9g=I4w#SlU-pYC|dR0Zc~J`qj?^J&urGS(;jqO@p6mQ8`DZYcRpo~z8}tt`+CEk zBFkmgAQ@BESMHk1_TI<(Qz{MoW=3HM5%N>JnTSWNE;K4uFQJK5x6d#toK3KpHdaJo^Fp6J!X@!wZ(23F%vJzV>qbRhyBw zlxrh;f~VW;H{4L&rV}q+Q`qz`f*b(KLO4>{*L5e&XbG2&WG+q$$W$jS>JEhyfWzfJ^WeM^OLI=+q!ex#u%Ji|mqp6l<*U59mW!JHc~ zhg+Qqy_8=DJkqi4z6<_|eOwqB|M*~79B*-Ek54+mzGq&5xfrpV&It*z*2^7enGkEnm0a6}uP(Yz`@=yq=Acy#tZ8eZwP`xrwkuZ7h=;2B@EcLA+Cl`1g!ctWZ|kHh!lY+Yj?_iC ze!i<$I@grqUNxhTXU=rA2?x)ma{dZip~g8ekl=8&K#C=OU(Gv0l_r2S?c};ALQ`t< z>BMto095313WnK~>%BqFT;VlOo?U~w(P^yR@^Tf&t~k>1qHw#JoI}YNRiPycc3@Jb zRycF!Is3G%rQ5hdVj`KX7;N6a5?f9Be^OyBi0n;2b>KYQ>#KyhzE61 zSmg%_#@E->zDlN$YTX{c*i1`BT1UvGv)AIcgvFkZG)TA-adOv^w$7sRUfumNE>C() zwyt1cB$C&dZ&U=_B#7B)LH+~E|A5y&;HvNsNPGeV%eW2El(5#7n}?d}`6Rb$$VMX% zr`V88cO{*dHUN#rM0WfKiBN(5_0SPS16qzKFW5&gFBn4(H}0<|@_t}wetLC#*EEeY z%{j$QP6J%pkUAGm@`460`Ghpk(741o&ZqXNpjby{V4*(O~ z*mTm?_j-+j7NR#e3%7`EpP!M&b2nQ$qNe~@`UYz)0BQ1I3opkJB2$HE>x89@yWhdk z*U$aPgX)muj}&aEtxRnOGUR<@Hqa`wj21;$8A_ucj83P5L-&u5U_WZp%(QFh_Rh&J z_$I1+nYyfjDidD770RH&(hXw~F2TC!y#lAOJ5^SJhIySE+6THbWnMabV^{Ci*i@-j z3k*n_2kWS?6t{(XP^#@%{}I_NH{B4AO=eGfs0&umzVA!`Rq)ibfz2)E$i3LEHQgwI zqX{VEh|f91b#Cj`FlTc}#=92nt0zu)I)x`D!}{?pp-;ritV(0w<|m0@+>wX`KVz zUI9rr3%)V)WpWn$Bj0vvsEMwa=(BWp`1v9TmfbZfnCD-^9diExZSIZL6<~KmOzje z&eJM{`1-M*1o)ggF1nX4F>aL~u*w51yp3y8%|qmeq`VECCtj-67+h z9+p&r56?#c7pv{ijyNQ1i~D9@4jeNTO~!~=JLc-<^=GFK18EPY)c|Wt%%;SXoaVDE z!@keg>{9gUJ;ROPS1_6|K!!tbcjF}5GP+-av6pyXxrnqEizAPdYx8K>1v%;tTAS|ZKNWy1FP=z=)_;#KjLA!8ft`pY!vmQTWO$B#sDIvQ#=rK>+Hw$OYr+`^B zQs3K~rV!N|Bpo#MAfwgqdxI#-`V_FC=u^s5I=73Th|O1!S24@eigX9S@m<4lv9ZwA zK0dX9o*To_e~j7=6AihK0NKw6y(i=pYrSP#tdEdFh$HNRGFs!w4{D>X8ZtQyHX;e6 z6DyMnd>t_t zEMk!#6ZUIZmpuS`gy8+Cs-C>r(rl()mNLnS1Dk7jGb`bZeP!!IE%_ijH8nF;tI#wV z`SCh2DJ?Sq zP3#jRoByLZ;zk{}dlj;5-Ve)c$%6dL74r;Fm?-6`f*!nCigK>6un_S6GRBhtzHl)Wq4>!%bn7(?~}(dx>bNDTuA z)AD*Qa3!i~w~%iHcv`vTIVU1BIW^?rU!v%1P7bFf9T92!TFT)clQb=(XO;G-BUZHk zdFlsMz|Az*4HaNHU}mRbZ}~x&<5Mz${mn2#%KRlhwV8T88m0#g3Bl%~EdL9LV;Zq2hV= z%BVYC7qkl%4eU7>X_K97sE6~ux+RO@J(N$?pg|SQRm}p{-NgZ+JBExW7;NZcaD8h z5s*1?@{rMdY)4>OUZQzJVRqxyG05Z-?-XX0hR+nwO1d7X*wTuXxs2pfNA6ec8H?}q z!hm)#+^Jomr+>~w6(W6W)X-j{JN8;CP5v1db>;y6GjPs7ZM{YJ=QHXlSi%xPJBC`O z7TexlNOb;lJxWb3ykt321=G7a0r1*SXb2AZRh`K7(=`DvlYD4Y3EsDSiG$*=clVnF zzD;tH`9%ZHifsj3H-XFd2Ddzg^D?(8GH8jR*QKeYFs$D(k1D%YJy=h0_xeeZJqKyV zzS=}37_kJMZSQ-M1oBK}eX?Q4egX+Y132^)%A$GqnQVr#6jISUq?WRU)P)W)c=2K3 zW0yp;cdBGvDd=P5lvBMUQnkU3EM#)iZqeoT8L;|MlQQ;+ihj)!T$JkFft`eKFCR+N zNHI=DI{adRV!GcLw>PZK;PE|QPTAiM(B^(hcsIJ29TGB~Jys=fRqTyb@cmea0 z9U`#CCJqLBvRkRkLU_byWz&_0vu#LAhS*a7^piGHcW9?ajM5}(z6@|Uq@o!%y&kLM zc0qHS+g$h6`l>mci4RRHniDWMt;WI9=tlL3t|)?PQMjjE0(*NMR{q?YR04eVISQk# z_mdT>vD?5G)u9Q)t{09MjsQirH$jg28SpgD^`F;;GB@sbmP6f=MaP##Q30gVz`{qd z4inN63N$(`SJASOZ(Vs!)?|yuaYppGC7;*Uee7WfADmQpjV|@8ZF&YXr@~|;F148u zLg-NU>sY7tAM4u)m2|z_!EWf&Tb_E82`GD+cc@V8W>Tf;a1{!Tv@Rn`0DcAv@Pjdi zK_JEp9gqPFpat8)1Mp-7_WBm}bAR#Y=wt+IPY~wl{ZX&VzRMdb$@;48IS71ex91{= zHXZtr>F67I&=tQ9!KnVXa@|kVgo^8)$7r|&9@0wZ_XTDU@30P@wI6tEP|xk&1v;Jw zX)7J ziE3elMk)bS;xTH8EK)f2+=Post0eXJ|I&n-f7b!}Gt~HuJ4FA{Bj}SJ>P>loE02{v zkr&qZa)KWci9s zuChWFBf6qPFWsLbXv2Q#j;C<(w>`)UO!6X;zX%?ApVkicm0|$<@_zC zJ*sR%j<^8TSy@-bU*|m|{RSi}9_I~}T$DLmuPvZ_CqPyQS4o=74|z@-L6kj6&NqZR z;M4JBf4mvU$QOaLh={3Sz{!fQ4+tp(K|xOz4?#ZeuC1~HvJ%S)RXz41IyDB zMl1gw+LtQy?usM11gIa{1A~+Uw6sOCB;Qp%vlTA$Hgpd;n~LiZ1tu3XY6fbku$^+7 zmKejU)COOQjT$QZTU6k<;2%3I>4^&@eMk&AHGFtS?z&;h2^uI%W zR2&)=IN{>X6wtj_q{48j`Q00yJ?ECoB!Os}>G}0^UZg;dUzVmLC~=nbQ#cX-C6wn1 z|2z}oXF|D^uF{WZu=|p6#ED`Oyiefq#_@80a8~ zi<}4+m8q5E>_>kx`X29-*bZ!1Ad`e0@%L?65MV@by$l{G(1?e06+bAH4REc8TBK`n zATPbC-7#r;L_c5M8)oOZc}h$clp8wb4m8dzH0@P;HZr2cC%fHWB+SkJ76?Z`BGK!UFhS)zas z_Efp&m}z&H7Ky!^h{#8x00MiYFuDmm(+Pc`5)Z8?mYaj7@3M!`y*MA)^rrmz$Be<3 zII;NJ$=8Xw3RDLl1Pd0U&N~;4wi=2*4meU3-+(i~=ArXWT!t?0pRT-I@3@X=A*++Gh<9EYYaAQ5kMp$LO%-X5tNFfCD^DBuM|u|VqH1lcyoeKl!z9ByZ%va1ZH?aUWR8xpo!&+J)gcQuU@0j5neI_i!S7K z)LHoEL;RY4MDt%x`Mod#KYDD_f_f2Fcs}+wUp5t-ADcdIzuDZLi-*gBT z=!+@8Tw_N*9*gEdp&b9d6jll%`P4*9SRsNI1{Qq4!I9M%1#T%LGM{n_Uj*+ILsR$U z-gVdoZJj=4N5~awhc@Ia@Q}%`t84)8U}j;;pfUadjCJ{~ZKZ#glK$|P*{fUr^x*dQ zDQleGR-|!3Ms-%P>5`-7_DnSa_pV(|A-Yzn1*9hxPxLL}oDvf^KKFjqEE9Ku05^LI zy$sWmA?jiE#GW;vy$l9Q|1NS(HZpU6`Y$fH9WK4^ z83_N05^x^i(Z#iKJ%eo0Wgdze4Af3ZdZg3&VW_#mdbI7a=cB~HE(#)NE0P^h3~%S# zLFc(poCqg!a&s4@N(uHA0KeC4_HhpS+Ed?tNHs*{Cx3>Q;3gj*%Da~5IqC7pCS}M5 zUkFEhGO43!4-Gi$px7TSJQ;HUvN~QwgvO`=n9lbvf36C@Z*IFld9d2;qiraALx${9 zu#i|Z!Cfwiy1hAMV${Dvb@K_J_0l+M0EqB#C)!4^f6pdREbn_w*fF!6+DS*w7bgN$ z=hV9tu!HT=4&5H3wn=2XgF*F ztp?iBMDqWj8t+$PLvZiwhsb6eQ8SYR8J)Gqr1Sdx79_N%5Ww**L0K zoE~Btpzb6vk3w?yiq-`McojuPc0Ksu{po5$_S1Er23rd6T4T(2{QLP5i07U>2w?6NZf>gBzpGpPGi)H!sL)7l zmCVkAZdNNKL}`)R)G>nSc?a76~`Ev1MREE z=m*U*NQm-7Yu!lnng}~4^Un2VysOH_y8MyOILy;UaE=0Eu*Hn7RMXWxlzz#MpV>Ci zdG{1k6Vxc>GJdUp;6Y@hmyH{0fe9%e5i6Z>p&0ShX+0Rzp;+-!x!dDEqHy9Z;y&SL zmX&64l=S|1@{p*nN_U^Z7a>47ax*m&Q~f1XQ7`UQy4v-SqWNVwGJL@WRu%m`N^25$ zoS?q?SKN+wOhx3sI=h^rzj8e@6GOuI(qMOAUA09@yOy_dy^9d9yH?J~pIRnH`-qHq z`8}I1k!e24!xhLg=o=|N+MvyTk*MpBF?yK?e2INn?!6ZPH$0~=13xl0PkeSV;ZbJJ=>Q-#?2?YITKK+-OBG1Uh z>ye2j0xd7z{c5A5OEUgLua7>psCGFM1Fg-*rG{5U3i~rda83IX13jj?Pmh=;Ft`qL zqt70FxFIxQN*N-&rCz?TeLAmN8-tpygAp8RybZ(Ojq0FbFB(L*_c^d~Q|U4^8R61Iug-dfwa9 zO&}s3sL~|AAV0vl1El|K1oz%+5G_Qr69lI~4B`cu>#@Eg(Fxq3BMBFctwkpv zl?_+f#2Fs`K>A2FHkP19Lb7DZ)n}B(uDK#Gqb8kQOP#*LYg-9ut(>?KP*gIcrZ!mD zg6O_oZJqFsl~Ra4CBS9a!RBFhB$w%5I8rBF%GK95PZhD-nbzXEvr@8VP(qjfjPTx| z^Q70FC1OD-gtVa6QVLZqq*K8=z0#1DxWiMkTu+5+dK8QmP2APeey20btd$c1>}vUE zxXMXW8f>R>xQ=U%w(80W!^r9h1_M)&f9iXVWt{}mlNtO+ODOGVgvlw18xhyhpU_Ku z!LTJapSqf(L6^sGpkVBGYF5w0ovvZjWe%7OmaugRLDU-e-YHVh zo`hgq*7M&y5V=9F0NZ!nJkH5e@Kg6F=I^?8L$aCu zvYC;xVpk0Cy1#omzUp?A`#x>%Y=-VFKxz7w=<`;Y%CakQ7S?J-mn6+!BQT3}o=?D^ zm_gYTKQ$5taT;AYe~FkKHmT`gO_pY_SQ|@#C7u#e@TQB~9byOJ{;q1-=zI$UDd}X; zKKoWvjKQjEMbTwzQH~|&(3z2Fh}*TxA(ob*MWQT@+>gKUr#?IV$XpxU5{6V9UH+4*a(HJAytE6B<$_;hv;#tSq z=~xFw+Rdy(L7#e~u#>^6Wn1%X%8U)FQfr|MSkoj|hL2*HUZodB{<#3QtzPvf?ea$) z(9X5V`%lz#9DeOJ4{>%gH}`=PJsd{oqv{N&7$Q|8L7P63f);$4gX-<4&J4D-_c_i{1^F{>sXH*hkNx1EqvyJw=D{VzH zS9s-o^lSrLE;9SsR0JkBx;X|2!B*|lvqQ8s*uk=}V@Q(D536%rci?Zh7=H5hgB|5B z80yaL;;aFG#Brk*@LIZs{ZL0K3yu=#m>mW4L6fK&47{f~KE}oRNCS9r3p;4tv0Lx8 zCM8o>#*ckls>^yFnOjk9))V)=OO>CF(hSLUDIB0lB>5-Yj5*fJA zanZnF&tH8-h+rmhzT_7cL1w3mF;b%(+almooxerIxgw=QcWnooUPq6Jj_k&L8{aB9 zj3VaG%SuM@SAxfG`57T@T3t%eBHtg|u?n%g82<&tr2_#I(y(!Utuh{N1r z(5Jz|yJcD0sX`uwCW5E%a~_0J?Mnl^Y9*J5>#hXEo}sSd-TNpv=tl{?D+b5dNv|Cp?ekJ)s(mi0aNPcIkTY0EA^j3QoVuFy(MAy%^Q1ySXcK+o+ED-7+y^4qTX*fIqVLao? z-dV`hM9eohu$bCvzeZmhA0z{k+}i@~?cLTNSiLa9(T2M%<@km%H2k5;J*8Ws1>uV_ zomXN+d?rZYhfIlK#3rtLZdHO({0#bi_6L;dwYK()*i&|3)kP-98{HDa45)uGknPQw z)JVK~fJ4`?`}PCMCWBG$CM2yvSmfC1U@c)PMXWCl%1`s`P?md{0tl;Gg$y@CLjv&b zaO>YsRtCu2UDmMaj}q>HG<^gDWB)-5doLaGi(O;dTXhUo#nzM0s218prK+e_T&aa_ zfe@CEO#~C2D}sjA=nAz0KzN6%HhNFK&$GvLD$U!AHg23~DpLkoO(p zp4Mbu83d3sLUK;;N{V?(AcPZfE7TNEMrGOLGK4Tp; zhUOdOMYy`#OzC&=_|NYJkmQHJ^dXw%=&hzzox|bDzVgO<=^dBtwf4#vqZD}MYX=MF z^?_fTXD?m%ZVD0P68{`jctgVR^?dgRQ?olq-k0!fpaXWGt7N@1*yTnYGs-P5D6Lvb z#c0!&VS1DV#WJA7X+noGLtIPvLTdFB+6ju9l?#if!T_*dLnJn7@;N;4(a(Nf^7I{b zKubBGBp1L)F*!Uy;C>0a;v1icpLP*!&Q%4wvC$c1Lsqsuvqj-REW_k+k%YUM;z=E} z0XI+}5IJ4HxTTXUwCtiSO%n(xhBt*J*Vc$Kh+Eu#MOqMMligWPffZA_!;%=$(TiI^ z`{MW(D1!llb18NDDPAH>G-Fjz)$PjUsw(p6K8C1aJO|~f%JivyUH2(e)$1f)BG51< zVfo8}9kf2;SjQNmuzjDV(6>Qp<3hX$+{kZJ;zdA);E6gJhaM7Q@{~!+s)@>_K}a`% z##jZ`aR~9?5LZsIoDMF|IzZ?1)`2ri;mbpVj}qpZSL_t-Q*4;aD^%{Se;xWIx&@cbymU;X`0`DWW_1hpakyC)sxk6)|BzJ$Y;cQ9m zkIU3w$cuH0kT^{&#{9s)j^HRxKHu21jNLs_0%^WWpyM_c`uoeOOONpGd%W%({+j3ltz$B zZB2!^6efZSwO?j(l%42;*pN}w73IFW*MQjAG0!bHYAuq2A;&HuJ^m@A&?b?TOG6^;Mtw zRp0qlANEymR<*K2^O10r0}Mp*Hx@7hdjEy_wRUW>K*rBQJw;3)j8ogW_mihN|rH<5ga(7E&`O9nTP7#Cn+<0;MI`khI6 z58f1VHxy1QD^JkE0j)LQSRPT8G#e8VMv)t~qu(>MPf%1EW0ZyhG;14e&=A4ts-oNj znbq;5|Az_k_r{kc{E|Xx2D}gx$6x>e^3~7mDnZ5V(mxaRcGe>v?-V2x?*+WR6pglpbw`l_OTWOlz#=AK0CC#z+MtjrdBOPM-4#{d zHJO-lJ(#z~0n|8&cE68&8U@|M?x?5%kQKYS2Ay$lM1C&{GE$7zZ04&%erU{=6 z6J^T1?%(rNF&;q@M)IZm>nz|?{(mYS+A_Wnx!0-^l3Y{*$1?48SnV&5!DH#*ILY+} z%1+d7)?Qqg#9yR8xbR+!5#Hed(J3EX%~ZY9${$=(w=^&8FU=578fGM{dLsnb;>-Ov zUD`XF9ML9pLOi7shfWzHe5D$PPMIQntv^zxsO}u;T_pKmHI^uE-Nb(Hus84Yz#^^N+r+f>&McKddi9=Hw>k|$|uXs`S6`J?eLw~jprX1 ze2ws(z^_F=S9bj*b2xh_@$QhkJ+mC9NZ&TwU!eDv&A5yxaceB^1#u0<#u0nf++4E9 z-(=Dv)>;#>Ul}yiqMG}YK`I+2HSc)#rIwykE#j6e{a)fmlZj6W2;x`TZrt}sE$rgu zqua^~K1mODf3=HB{+={t$y%%USWUKPM`rK?C8+&qT}k$vy3awI>C|}5{5#w9(Yf~& zRQF-VTDUi_dNg`GF2kz(1<9xT>QO4y;^cC z?D-KAii-b^Vq*?^amw|xI`FF}eIHz`FE2t@SMWJT^J_y{2*T&P#HW~Rh~9x`4r)yJ z)3s^}v4}TzW+w&v%mMJJC%BTSeX-Ixt#pI$ZybH)vpYp=*&0;d52m%s?GPp9e zem-N?9H<`^8sG4vg}3j>zP2NAkoPr_qvy}gj4!vIj63o-BA-4`U0?@eZ+L{TU!qPo zN>czs_m0=c#@^_f+~L9TMbx?~9(phSj-XeWpN*_z6LZ4y?PbEQ@sz}E4n7ls}*CuC~-u6sAA({zl%68?ZJ*JO?bF3%xoyT)k4b$p|W z+0!9hFDV}{=D23|;M};3&i?R1(!*PT_dg^mB1$u#f%H2kP$Np?Sp4dLhzCZL25J7Z zxBbm9`3$1Hk@wN%HYc?4hhT21N z>^s>xmyDeQc4ORbwilP7mGtsxh8peBsy!W4r{f8MJ+bPu^-p_G`KuSd;umu<7#)RW zub|Ne+`rrCvY)(AZMgI74i8}4jW=4Zy2dADuDYftXs)^@C#KNLU2Q`5oe2e(_ftd&Q&#qWJkrS*tL7vZZD%=qYlnpxW%m-`M2kes0N&_Iw>Q#y@r-Guu1C9mw4C;V zUbHQl;BQvvsDWO%E#sRr@dqBEW59@fF>`QDP;P|27Stz~FoN0XZ)dg?Ds+>`v0Y&J zZeIKou+Xo)pJqElb30BK-a*;yYaUVA{B`$i+wB=m%l-M} zx)H5v@RxfV4%s&p|IjB@LnkA`x{yL9W5T+yLN4(TJZPZPF%VvbQ5)oV4zDPUcijIG z6vi_Q32#RTfgcb7#E9+>i2Ndj(0|TE2|Y!HArhFuI30ql!(`p`1h%$5!14Gb@!@_6 zQ2E^Lrs*pIaqvHdlJv?MdaCUjfC>UiZbUQJl;`oUi&m0hRKYe=>|W9d*aR?(^hW5;7z=pOcQ=#K6(1igLh!T+kZBNJfTtz&u3~EyA=j>tDcgf92bD z!srnWe$LeqcxR9)O)GakAv*IJ1Tssd4PPukwfXwNy{i_ZBoz0<_V~QZfMmtw(u`c} zQiAA{Jmx60vOhA;UCCxsOWwOR0quIea?Ck{?ArQ9m+@S9U7op`eF6n}-iQdf`7W=J zy5q%0Vodv?0P%$|TDG!Z&_@RxNX&H)F2adEi#7Jp4zl5MAZ6QdK2>Y;xFZ75k8(Uz zDL%_K-gKi4WinrYpJj4ic#E_lvlrQZ)-xSS_ri9+s3$Pv>A^rfkx=6Qg7SV)jYW_p zbfaub+hhPoNa*Bv;cXA<*Zkfsje8s0qFPPfv$4VBUl5s>6R-<#FWTiFpVi1lZoaC^~Ff6WfzV9BlGvi6Cw2Tqe>`{tB34cv$>B1FmDbIz9Nb5;k`+nQenPX=gk7#T7(OTMs zk~Qs2hjvZ}qDD@F*1({FT40TH;?xc*%8&)os+G8_zF^p}kXs5d;1xCA_(bf975|k z4uLd$I7E9;0Z%N0)l54Xm4*FJip8a>JDK^Fs%II+rK%5^`T5OXX+?PwocHf?|6sfL z8{}d^>+GHVNYO*LDMj{Q*?Mu^Xl*v@o1|iLm9TSvI;Yit@SJL$pD1oa^fR^gCdWu( ztH8Ach6J?lCp7Lra!?c*m zUv?$e`3-bHb`nDv?IehRCG}hBv7qL(0m| zUHpHjdY`uqxwN>kc8Pnnq`zZpTMU_R4heLngi^FW@pXK37x-+UQ++0<;s@tHsXgpZ zzIFk0p(pl<{x6vZ?s$I2+dI;AEZw6EvAc_M%@++L#NX+aN#E!Ov4@<`z%U-AxV$d6 zr{z=d6ifkpj^i{?6hA>;=>Kdsm;c7{axu_hf9r+AA1{j!pI=!wWn%RhY|zyK#;^F3 zMQ3<{s}=1|D zl54hx?g8f|ztQZO$5p$LfV4|?=GLg(pi*nGYK`ZYj5TXogKnNi+xUy{LXtgf2mIn) z!m?Z2$IdJcZIA`ARb1OFIwEn!OS})W#s1*6WIp&Ai#YVj*iY;Eke!fMP_Yx;w|GdR zO$fM4DZknL{*0E)4y|+Lao}D>1FZy}$6ZS@Zm(y%CQ!#&-Uxk0*rhRm*d7Hqqn40c z*zKUjbfLP|i6V!ZM+-PfT-EXvrIlNw?~_J3gLnwrVlqo$R8`U?ari7L;x_)QXS~|~G+}Y)1Rp~5 zMER&>m6TnI$OoDR4@;ZOV9K;R>jdI5o$&{2(M|Et2oJ~cT!;)Kidv^7^B6f*lVUv2 zto-ea`f?1=gg|FG>*o6Mg@!KNKcb{alTdXc7OOgkaD|%SX54p= zw0)n6VA(00;VD&m&N@g7QGWK5Q{NWEPE^1^AM|3W5^+xy%RHK6R!~M@Tl1*RcO-S@ zM0one#PWgCQMo0XVWX-wa4(^JwOgkim65m`W_}ujgadd!v>BCKUw{H2H9Ff)}9rqm@XcMusQQ2xH z;tXLyrG}k)48YpiDqn8b4@+xGEyz3SB?#Io>cDbPipI;m3==`xd|MSyTG~`%drpw@ zF?$|3c4(RAF<+rlDPo^0cBC3PUY#~x;)Q;VK;q5TSu-ERewU3iCEdj!()4F!0_lyK zS3e(nua=;WnWUs9t2!zwsUdV1zn zF+3bO@ysKM0u_mh97O~r2I_wl0cXF-ecnjW?m~?&CIH#cULWTAq(Uy1o-76)5iQw5iuS0@z9lr850vf-g&SAEV~5m*nIK z(p3-crG<>Do?XoUvUjsZZNuEi+o=F7j#RbAFZ(L@F3DTMM75dBxy-)r+#|3(MXhm4 z=b(Ned~@AE?Mzy-tWlPdQA#TYtS+JmdwZx(`JtAMpjpXJ<OQJ! ziYgL6P*Ry6)+!-CJg9%|xf7dbPV}}$0g}cD+gWyCpicT2`Y9~0QZP9{dj5gjUR`7Q zv4Q7gym)?)W2T7R1ogc~ z3|(PkXh->mp_#(B+JOt&Z7J$efCZEX+~BqQzY)0Ga(vt-BnN%+8U#SAQ7o zf8Z9%sK5I{KE=J|{t6mwSlPxgJIaLBMpVU4C3Rvs13WSc!fr1X>%qciGD}in+qE|i zD&ARecE2|z(_Zv53wtPHzp$Ixb<+6JA#Ij#(=++*J4)o940 z($6@uV&rp6OZCu;KQQF!1Uvt280lcnG^^z;hes5o&>HIZ*oouKQHMvuC1u@>X@?RYMpYM=I*(OmN?^vI-1 zhw!=dxmn8bf*q_;1?HvuyooEcb>}B0M5=}n(fR{Qk{vRz;kVzKhr&kL{1MtxGO%+R ztVbQXvXj7Sfu#1-!$@_`>;-_BzAOf-fow7#ooZu`L_)!GJ00Bqt5QhZF;28}2#NW}40vQ>6m6u9VC%*UB2guoBC5r#1WLqf4B%2;sX%UI3`350^ zjLE;xWJH8-=HI13o+-bJI92z3`8Kp&_1dZIzQ`}DLsxu#LQ5nz#6;*+GA~4H{_ZE% zw4U^!*EO7M&#Smh3sUv@6E11uH)Lo6PehOj1O1M2I3zF7Ml_`dkWQ@NB})F3qZZtH z(F?arhK}0>y8XFZH(NG6gveE6#UBZ^rBAHKwdubB)^8{0=%3gcFGV1CH<&@3dMk*ly?;q3tcv4A2uUhA7EVcD^Zh5xZ7RlFzM|V|X zIcKRhS4`lA<=3vB6DD1#ssT5v*Vm9nj-uGwEG4AQAHVyeXG?UpLwGAM zRu_`{BYhAH#A$g_1JG+(qx6VQ5aNj??3$M){%YI6B@wSB3vCJ&twMM>fn1E)Ua55Y zR4wGEfY_a;+Fj`Q*t!G-F6j`RtaM&sN?(}^aQ|L}|7pBHJ5-##K$Kd2)sBMvwdg26 zkI<`f6{2=|9Mi*aj#aF1*G)f-NRKVpYD zDy9tQPgQcZf?{Uo>Bbu3>tkC-zTa_)zV*Q-PCmkcz$9udA8qi*xhC*#ZZ#DUe?}ks z7!P)ko~2-76Z>*68;P>D^}Ev4dYlcnmzALx3RS{h{gqa&<#1V>u)0k3U!cv&Vjru$ zykt0d&7n1G{aMQd9h-0tacVEvG9ol2;l@-*A@AX?F7bZN!fetY_nva{HT4f$`YVT_ zz}sKw*2))+{_};?pcU`V?4vLC@Mg?vA}>yc5;^D4?4=*W9xXms>$QF@K>D*DibUBI z5NFcP8Mr0)sO2l1O6lh7zG01?t-{l1l>XguY(w+MO+nSKtZ>b1dlu<+tx3dV&&1-l z83)huNLy}+DNig^2?yWEILgDC=P2HEtZ|(`@G2y&s|tXNcD>p!?$*1t_GZ%)wwK3M zr?UPWn+Sppuva%l!O$z6uiF!NO`aDk&t0q0z7^Sk+H4TLPSCnH(OqGzCYCdK(nyU@uoH{0Y9&w9@#Wl-{}zdkOf2o7 zDsME$QI}cX^>@Qv0W?vUC2DP*C!hY$8^7ZwU69r%mnGgDo<{vSq>mK@b%LK7TprYE z7s>Mwq|p3z<-a3pGudmBFjaXsPZ}2j_JMNeUbCY~TIU=$CkDETd8!U9&bPeUo;& z5@n53&^|O%QJa4oeq6X8<^&of3QPfU0I@+rqOSUGe z9OYD$!&x(+m9urn6%BbmOi&1MfFBlRU!-*>=kRGmLwE4FlQ$W|{Iq~V38eBSfjnZh z+;*o>AaPPj2!WH*)-*oq@pX;imQZbx@4;uK+jZ}pN#Lau!DZa+23_tf#9`d zk^j|ew@#OYM_im;>S2NZ#kG}&bnL_%uCU(3cbx1p2}pf>W7^!4W%QZ`KBHES)@`Vh z|6zSWL!`+%{MzH#(pg+ERH8Oyuh*pBXBqqDzFUKFO<%8@wiRYItD3&ULR^r(^mkd| z_d*QgnvU2jkG8z%Gabtzda+6A_*Qm`cPZ;m@iLd>I?UsCi$^@ao1aOr863TQz7RVP zn|}9OghjUyvI`uv^xvYGIm-#;aT#tKa?gcINsFP!-Kclzp#`rtO%DLiv#m&|CTd+I9zs;M^k8NEQ^JN$G6sl?I zW~uCTM%D#VLo<|ja>tqzC7pRr%yK%ZiXdJ1|3E<3ztzu`<=@XF)r{@g`{Ic2O-rsu zz!#pnu~%{F~wsi7)hmE!90$QR^Sto~iBF zMqBT3&ot@ExEeH@PBfW)`73N521^kf2G<)LYEZ{Q7T2@nT?Y8%A`dN$Da%owUSEJ3 z(rOBx6r86pUVd#%@QT0~eE(<=(}8d2Kr$I@`! z17Z3>J%rFM``IO4FidKv@H&sB?WRZC~IPnJcFpg)A8UcCRW<=JwK1k z4n}+iyCJ|1UQCI0BH0~@*cc3H=%5GUcckOf}o+ z0QVN7rB7ldQ5v*U^L&21RO!nlBKMOQhx%H4{}8Of(wD+@@ftT-MA*6s68jXUP+oz0 z=f_kRWYirU1v>!E{GPjkd_IEcXb%HEf@nXw2>x`^uevCYxbU=W6gCVd@-emSwrr+h zoT}Fm3J()@s``z_%M5gjQO3WK6T6BLh)GN! z?^PDE%Y!_sXDF5$jsVNxWP5spw?)t0pD!!!$>OP~F0^CtX7iDnpZ^r+^;PhXuYK=bA=>zi>90EudJk}@W;4yTRHJ?+ z&B&a84{cxkH>ckUv0U$}v0TEAEaH&2>P(#L1swVF1CD3Px*7O%bTU?CQWOs{wAppj z##Afc#^sfECgoOF##F5}CgqOnlTQ}+-pR|lHFP$n81tqrayZI>1+h7g@==%1T*J~2 z;rOzAE^fiah_ugi#IdF(fIzv3R&|}B!$~T*4CHE_IopKpT?ev{>M@ZCr&=J7S^!UQ z@E%Eh%7eyS>w`wU>Vrl@>{(DI6L~}F__2436O2!bQ$i1xk)uGWV^6tM`sw*kssYmb z%uXa!^MqHrO-N{#u@6%AKwz2Q{A)3J{w)&Lv@fx!oPN-Gr6EqNo`ThrcznvP>ogLA z0i&fIx_vdDlw>N`tN9H|$GAn`&@$a(9b9SD>l1r=*Dd)CPOGo0*G(*c-kYfn_K;ZK zYgGog0LVl)xEW1)0b-{FNo*qP3<~;XP2bV}pEZ?H{Wg?xFCA$sq1e=^sjxbeUZ%k3 zH}KMFP_=E68Mz^HLa;cd#$HpDz2)it=(7^gO$7pD_pbQ~C%k%NgLiIXH@d?Zm4UVc zm$lqX-i{fC>i=AWFk8^J^EvaLQ;T(zo1LZPX(#Toze-;0=)q%Vupp)@TPGT6z{6q@CMl zNm83qK*4Q;OvsxTn2c_`;3HdczvNdCs}ZdF18bQNAx8&^*xq+x!9jzj^#u@RgAkjJ zJ8Hs;sar=G*V4H`2ky9|F<|D3S!DJqFSbORf(n!xAEr^dgEMU=>9EPU6W8Y2@6TGP z+|WMR4s9{XxsN>Jmo`BLjVavNdaNL>MsVi*1iq)70fDZtXWOjE?jn|2mf21@#l>HW zYL=a?2T3ryKbb67A+!e0x2Z5!0@|`YX4fMjs;eLMn`7|1THH3&$wuy${!rmIl(0@l z2pPNFkPH5HW(Yx)W70+~mub36w6~qrHB7(dCRd!B zq&*QU2L!ZDBQJ+T9n7md!J!if+ig&qa3ZL`wV!iB?t=1^X=KNufLS#J8Rt-DLeO=> z*99lb4DF|rF@(SRDF%=tP`qSunPWO0P3BMStB73*+aq%eKgoK2fhzc!SV#j_iggr? ztZ+EuXp{vai*N+VMgjRCl#cxVpV{dy9=kPj@0vn;i{*%wkSBO-J>K@_U&h+GZnOm) zy~$brQ+b1G{fh1N*Q>(F@3|U6&V%y<{$VjBr3*&(Ft-_0pzW)IvWd>vy(x_P(z4KJp~ zgan9RR;JJJd%nCPkhioO9t#6sn*^7eb3*Uf2MO&bZQq6VgO(8>gcA8;O{1LDR!p-+ zQE>WGRk9Fujhzl!A(0xm;TZ>MG5s_;CSqsgCZ-M<$&n=$mqxS(!zfgh7q-}Bg^_0o zkuEw`P!~-!j6wJm_i3@G*Xp7CQYauJsP8)}?#HnJ+aqRksdgKdu=K^EOuNM2Mt&M- z96_PX6*Cvl?5?Qu4c}U1RmTcVcoIPlX}rwL7RYF8f5Z1IK#*>LW$%W@7pmT8N6*b= zyeIJz3@XznOXnH{*Yv!sP()AOQs-XTinctMOiUV-FUjcQ~b7bO4A-?YKliIQ_I&gQyI7QSYd?F;vjyuU5IhYvkx_EGh1Jy&Se|dYQG^(n;xkqWkw-A&;?%J zjQj%;78y)I95tt|Fjq0molMM;c9szUJ{|=PCA@aKG4uRu0$Le{gL}hK7$z+ALsY7Q}8t zlAhukITEc5YjImcv(@C^4{ZjUgD%u{Iao5BHk$WKKfYo{%ur&HGw@7M?R?RQmIJM- z6dT3aAyS%#yM%eqG>b@RI%xCe)XG0x4KGw1wp&s z{wh_Wqw|e{?A(T06%atb{pPdKf~vl9RbNBRjTJ9`hKo2xX#V`3@9GP)j$GFn*1ZxG z9?J;!#cdN81RQL{-)~qd=GW#(tJI>NUPnxnGY-IhLtU>W#_h%CgH+Y|+Ou@Xor#0d z?UU7XS_dwm{USS!(`^atxTbeFaz@9*xQdF_s1l~A$@&#(@9r6cYR1N|Rb&GP#HN>Y z_K7HVP?oAY@b0z3FS-OjJBXKUb;E8|F;koYUlb|zDT$8I!bj=+N$sStMabVqcF|MGIh|TM{3<=x1-g34Y$E6D zSU&I4L=BA{NDEw3G8tn#U)k)zwFOt3X-FpalVaeB-U4zYkGktEpsw@wqEpIdd$H#| z2oz`1FZ|Ko68RPNW^&^DPsmgWor{sd#TbDq@u8kQN(^}QfZ)s*nw|~(pUklRDk`ot z2+$Ih)nX!Q2;dRe6^*RFEnEGiuYJhCj@ZhZw9&G!zT!=YhLGeBJbp#0Nl&uh_JMU}~%43SdOLgWB3JI{j zcwo^1EP)7hyn&a@nhgE*@+tp(G>D%U*6Th&5M7LSx@Zi|;R z-wQ+X)HgI(NH-Q?{3Mp>)jQA~p@Ykh5%e|=74Do%4tq{6-V}$$^-CY~P{aQTA(GAk zag`At#=D(YrP+mve0*&#aYGnR8`bCm_0Wj|1Z$X*ck1ras1UQ%Y<2&-un@C6k7ou{ z*6;ZUD^H!g-4k89CZRa7l1Kr$`otVuxW#R*0q*Iq4jydAT!(tu<}J=Y+4pqp&9qGJ zH5jQSA0Vmk0Oym1kMs{_si>=8Z>fsBy(%4@8VQ^&u{N8P#W~?_aAM+C`!;j^OKVf4 z8kXm)`i+{)~L5^mySt)D8qmuQ)_A?OW*vaUD zFw_vlcwtOE`j-<$l#g_O0cOa+;?McgJa+n}`G1l_>3>SismJZoO~eKNIr?e|xWqkw zCBPdUB3#dg2#jY=z}eUvWs_{!3%i5AHTvwj%qx2BDGjZ3v`q5QUNHgIMB81`XWqy{ zUQ1Jp9jnB>KgT*RGw-?oiP#O-Qo+GEKLCQNEC+h|6d*&QaP1OoJ$b^@sDx>?K zS3)FCOtkEd5ltovTMU@T>+pmAWFO@cI6X!ZB9UE=X%jkDfVDF&*hIKZl6KAe;(#h; zOdXIu%(3`P+t%t*?rn81_q9W61}+}fPA}+1!M-|ya#Joot!FFJhLjx;sUapi0I`9Vg z#&Dso96nv#!U!}j1t}oVX~*gZ;*Y_k7CsCdo%RhJIXdk*26sZ|1KD!gll*VxEZ+X# z%DHe068Z+;UPNR!M(>+0kjsE!^m@oqwT}jbo`~T`tENHD>xvljpb@k$feSUU4`k{e zI)l2E?#4peX7F~%t}+l&;45(uCKGnDE^emZb`~SU8Eb{mcY0hrEng;a{f(u3HuIa`L&kNw6^4d%O#He7#YyOV> zslb?+kg=8JtP3&q5`|5WnVe2m_*9%yXSy_GRSnwC2KHRCgCg`%S?7D&D(^G@?HwfB zboOSds>M_>)#SE+-AJ8S3pZvRgA?kIUY4Ff$4ilu^tYC=iO_?K=Tvp(aQQs=<+&^h5o4g?nias#N)rZR2n%0agPQ4-A8*^ z_zzEx>Sl}C_L2upoHWW4ZKLX|g96M#c8}^Sl0rLLu|M8{SQwmMH#Yuj+=(yiu_1oaWav;q0$KV@J1%rp9H!t3_aWkW5 zNROCqBE>(GB*DdhlVqF|31#2s2PM(aiSr3rx%c%P4TZEeoDq@q;WhGvErW)ja}Etp zPIRcGp((8*R<}X5P3rr3vyhYGIPr81ix{hOe?+;lqcgdgd<6`>)M1SXy<|--&$ZhN z`*I4y+z^5b0cw=uI(s{7fq}A-2SeKC&JS1DN6ZDjw29N1?+2 z{AuG|#2sBbVIzR&2pYold%sY1`vyCiZD;+Jx`n%&swPE_htnEEAQgQ=~AbSj>vf> zUkSNBtmdRY>Sy+>`FD_*_F@I@hYQ~WdxVI(WaG>zxkJ}sV>o<0oekv5v+nK=1yY z_l;D7Vt-a}yCDz(%0Gp+Jd!kN2<_l(DMvc@aJ*^!FlX(L>pDmuQ+Wr5`B;fwP@xvG zxK7Q-WNUhp6;Af?MDCPp_XXQ`q+#I(L`e0=Kf5DC-n?!+>{-5oe&AD10xw}yR@e|N zHOVDD*=CdWJjd;)kz%pJvVCYisA%R~_5OFi_>j|I^Pd%vXQ7}HZW-iT7DV$t3<4c6 zUGM1=3)c$`P45Nqu6h{r*SVq=Zkq9~iw(6wO~QglAC3O5lof4(gjYRKbMP8Jm7TX( zMa|i1|8$CGxJC&zWuoV8&CnEWoi{Wo)dR0<{A@m2Nv=_WO@%4>b%NZe%G0f2O~MMp zXrLAtabOl`2_bY&Lt(-pDp7bai11tT;x?K1bYTsI4j>12OX))gFH#m z?f1iRpB~}RJ1AKUJxu4_r6(SLR;=ngXxw^$2u1z6UxAx{e27!8fs_IkPYjc<`$BF# zZ6%Z5g2`vMZ$3))mWIV67p$f&nuXpa!+$W<8$O;Vt*L5xqz-3=B5*IINI_$UfE*Ch zkm`5c5j5-mP$GjR!1Rz^SM_;%*0<;IP3X`m_34i+2k^NeL3(zS?C+Vh}r>9_`j{)P591(!8+D);8q(hR`mm_sqbZ&g2d5 znK{{Ug|!l%SfZVIymcMD{zCp5A>CV}hobsXY$rb>^~WXb^rP5*T*4kdBR0eM%bR`w z0}-bMs&3Cvt)`oKa`JE2*k2jA_O5YhOfym*19E9b$i_7hQs4dRLXbdT-AX9;egcj1;OtSz4l*hwpX3IE-4*i^(W@GUW&*n<^| z`MW?6eWkUYqkGln+fAB~d3WbPYneiBsS`CiG7~4Z09URnZo-_i^_& z#}`H~fZ!i+VtO8y0`|G}Y-G^Abmjfuk0-MoA|tZAM$9`tGS%Z*zI4nkelw(B#E_#Z zGU=&f;3Co-DN_Q{qR^eiV1RlOwY2Dw))P4zcZlveRjbAPGjJJIrpJ_|uXhRmD2i%w z?&MlP{yZ$Zh2gXv?t$fWb_bN|Yvy?NJC*8xoo!+(d_q_|1AliOjZA!U0hT`7gq#-I z;p|d~K3nZyafqn+y52IZjqdvzE>PTy6?b=cDDGO^i@QUBphXMC-6`(w z7Tl%9-Q6kfZ@ByWe|oO>LvoVI%+BQG?7h}rXU`n5KbdohV@+ioO7D@o>KdQ}>Z*i2 zwYj+!TmS1jBzO2N`^Lo~+pPdJncFiDd#7FY)2r&-GaIX1^Z2RwqM_SM6aU6wxvo=b z-bQ*S>g*L69I0<^9g5X?L2#$*VU-EyZ&NEEeeEBe(R401K7~exTF!4bX{o1VdEUk* zOzb1X4ph&zrVrWaipksumbO_fqLEGCH1b z05_xdmSF;iO7~D4gyw5dcoy)Qw++K-ZuM=|u5oKmnLY`#aqFzd^Q!PSOsv0PtR#)aIj44(sER?TocE(W;lPdpL==TBU>s#@9rP0ayu_F4CLX5 zh1n{zy8JV}v>i}u6D@^kbuUhed`{nbd++9WDst*YCM^aIoIcPtsQ-C$a&9`<3ZeVNucquTaDt;=e+9CcAN}NX+Ct=AFn(dRsRQ>iTtlO0a1SGiDqeC9OLLPRzL&^pwx4L(?jKU)0fDTE zm8ofuG(kVZgvn(Y_I*TKouw)V46NU;WXXu~No&z~NfZH?glrCbq*vGq?2mSd59u$Y zbyQ5{7|B7lL~(;8%oU-mb$n-0cHB=U_*k9nJdLz@Yy3c>bNB)rGZ!`iF`{`T-5RB9v91<+3LVj@e6EinaJ$9qB{H90sxAp}OM_osOoRCQP zC|H_ef@frK1MbHSk7_GRfiK_?CJnGapW=j(0fbVm5|U1x;*zZ1U@b)tCKs$?2iK>o zO1IU3`GnLvpP&_4`jz86fc+aq0!tGue>I78i4+n2V|`m0+-V4m6`-haHIsB{9PalS z0Bg#=thRo+Gsc}Cd+H1bwqWsYf$%>@;$8KBjKm$~KSoln+Yzy!(FRqP*)~2qp?_u_ z3YLicn(&i}~5*hgo`F8Ob+jv>Rs!9fc~@+_9VKp%-_|4|HA4~l_Th$ zF^q-*^m{DOq?h<`fCUWfe2Y1w44*sQ)G}A5oP``qCRp*SU zZBh4S=va_m*me=7vw-13SteS#6gqrBMcuFN$w(S-A2AXMmlPD&H8T;Z(+{#f)*H4M zqvfSKyhDL4L49)2BS$#fDKGyY)nz%3sxLor>$|;qx-x(Ydjcnaw>Ht3Z;>yy%v}N> zI%WP&i&6-e#q8{3D#{{?Q8bm@bagNGXq#8yJs}oOt|2+h^tpVF#&92n8<_CX1kp+0(Z}@ z*Ivce(u~$xy4wLNYqwZ;II9yv1A)J{z5IWzYoz*(txp(W1N84jUx5aqsK78;&B>ID;sgVkx?)gVVdKuF(n2y?+HZH_MjpOh1XqK~2bsPhbutnqeS@3_J0(;pJ==R-BJZ>ZCDvjj9hqG`9kb}EA)FNln zH`s57>6M~NE_=#1Sjh&|V*yFw{hMNwSpSnMSe49n{ZEup`X|a1{o}6e_I>Q6Tw_Sp zn`^Sq2*jK_&iG$9h`2uyuy3MD_Nbfp#DjlZuu^DqXL-yqbKmF_me8k95VvzrCJR;7 znG1-reCc>Llw5ZLyV~Kd*IMB)>X}ZG9Ka(%jB9!dD#+2lzQJ8zdGGPB=|2{RU(Wgl z3zdeW6GbP^E5cNUvxZoe+LZ^C8z)d-$c_(cuuh18dXl$xpAr6FnrZ^-k-AwrbLv0| zwTv+&6%Tku==QQ4Bc|h1VcIpgZcnm#kLLv;B}iAJjHDgsAMAk6(_XcSo-RwOy@kO& z^U(YCe0gifbk$Z^2No7BkJ=wpujiYrVolpWO-+7+rWx;?oS9rR{89BpU+6n`@}nv# zfJxW*i~ehk`}al-esJ%G#^g?x$(_LSMbOyQY*yA=VL)IhD7Lm!O&X z69WbRI_Ag(j>sY@?Y-#e#8J$NKQ;q;I7L9;_jz{ak5t=IW5f5*0I~xA?w?Rl7>8n% z^X#$W)mSy+BJ^=TzTfEAmU6Up9o)fiYUA~X5tF8mXc(_W7 zm|(ooW855JsM|rhR^f!RoAPab?!>1lU_{uS&cM(Fjs88Y8(Z+9k=s1BEQOzVf-mit z!$Emj(2nY30a^hng7V{L9R8D9*i@>hEGlFEbr)KJC*W1p(L^Z>1z8mjIx!H`9Vr-2 zS4q-bLrj#*QLAi{I`xJ4YGYf%^|m*V?A*XoISl$n$|&@KUc< zp;#ZM_4EXf{ufx*5OW%)`8G_>;&KS}t$hwDIF3IjzQ{|$n#eeBbCBIX3z_mMr&F^tU@sE~9EL?*8Q#8%8l~&|*dL*vHyf zdv|^Sqfqmc+nV4KH-yj64^@9F|8hPLLHwzlwho?s!MQuO^?pgj5w0@&nUZTtR5F1< z6s1yAfN6}jBhCA}z450?ULC8|TYx9%*-%>68 zKOjMxJyV>Uk^};G-}HX;YM%bq4QciryziUkzn+SBL5=aFk#>pRnt3x$)t;>hnf-)Q z5c$A=)#Tfdiz7m1E&jY`<7=+x%SF8P9Ft4xJ|U`hkmPxu5eDCI1KW&25Vt2FgCpsD z|E!ZQGLZELaOTOPUoO{en&Yt>5wvEk*nva07qQlJYczsZ)uT`>y8k3G<-C1{#39DJ zkYv4+jleCAkrc8!XScg?#%+l{oT(B$9NO%|8Qs@?3UvOmx864z@A{_VODXX@9_<21<*UP|jT=TA_|D$0jWk4n5eXRC zCE@r|Glx>ry1vbNe$K(yiqxZ58;xenGMV~$ygi_ismCE=D$wSJO^NcVcpz15)fy_Q zEaG>D%)HryCZ`H^n)qk6_%YCVr7`W7XFSRvOBzVsB~$;HnlbsOX7OsOA+-oJG7R*R z%5xLz>e5!g;G?QU#q1oBEJfR`8W}H%wQxnKE8aBs?S3W+gE}YpeQ$EBB=}kkix$kY zi=K3aNFm>~HXQprR;ILytNu)i)=(JU-Nj z@qR|J(u2qkoinq%B2BBg0I22gdh$|SN!{optz|B@rsYDRF&aC*&(%$^hY))4M|?;) zQ(uLN^y7R{qL8_$ozETU_!D0;xTD#!WfKDERZPyr=xS&7zb>*lu38WI$eLwURU@s~ zhwOFcvyIX*1cF7T9SxP#U96<;M5fdXya+aW@YZ9Vkljyed(d@b8U>>pDScrNAXiO2 z&EMh2Y~{@)*Nljb`{%OnZu*<^!I)-m z!xi+hfQhMgmNz9xO>DYv4IDi5EcW#@`|6K&L{@$gB?sECvVZ4Qz1@72;LCu-M|Z1F zTT1RwW?PrXH%8k$sK`Pc+VRQ9w@c1(3nd;9F z+rkGI8c2RH(hcFdyK&#*?0Bok%j1z%gIb_|WxzHsn(E{yHsBj{(6QY80z3Y-!#T%S1I2Yxa#lEROxe@avBETojJZhq-p%4acguoTX0Nl5;lf;X;;b7%F|GxDJ0_exe54z`-{{Ca zA^dCM$T0L*KynLjLinGN)YE~Nlr0v#2PbZ2Jvovy-0)>Ov^(1VZ}k`3Y}@E}_9XUf z{S~*{U^~6EC>GO+bzjk@J4~6BN~%-LPC(5W&&U!&6}*c%E=sX276YRbFbody3Fkf| zp=}`fsL7-j&IEt4@4tdbF=8BDXubhbm%o2v*|Qw87+s0Nyp#R5he7*If#m7|6V~tm zZD?J&$0`_pQU#J>;@b(X{Wno-b_#E`l&%FaAZCxN30(lGoXJwwvNih~ZPFi%mkS3j zK(F%D3;1!V75I4yyhQP5{8WrjfT|;>JXZt3XdZ6h@qVqR@TtVwY~tQ-P0t`;K}!aZ zib7kfh#|1VBV|+(Pl)RhrADTWF@crbe8odAK8DLA?mSjTUuI%glfsVlqsl}+3aL+5 zm|T2$A{WmE^5U502eG6WK<*XBVVIRe)|T%w0A1BsI1CjjVahXJxRFJ+)9iv=_Y*c> zvFqC2)ykTsv}p%;!i6Qmi`OD|#OX6d9|0Hr3?QtRakuk8c6tJNu7wV`w1mK+Wr zgip()J9*j$#sbP5*PJLX`!P>%3*Ss+dc7GlQJ)*iDVC)#|7t-??-T3AU0Y3rJ_m3H zxC;IC9+BPlcMEY9ntSQ}a)5ymI>j;gks{MigTiJo~1Q z)8xE9@h-He8C%XBZ>wMZzHe zTyHAo)@U$r(DL!PIu=J#e8->ScvPmXy=1J@tU(c{rHU9cvjwMcNRg}XyUJf_XrICCsCK0LMszM7qI6-RAV<%+|G?CU5E)&0v5Tvk zmVP>UE1fzgO-(IRg$8%VheZCnLbaZACDb3cs1k&aUjs`GD~g%vohYvta?k?<8BXxA zlBB_Re{}S%nE8%OR@Jzs2v>HD`N=k)=dC+^m}H>j;AruOmDQW{57P?AW10L4*Re%X zGKN^lzcnK29s;~o;%r!Mdm;%EI!mjv6oUdp_?U*;uz?keK_2wm#5hISz4y`4qMli< zcn4Wk^2aQzMUo*N3P|6FO(5j`G18{hk#9v+c^i?ZavislM8)OZr`u9)0?Ta?G4iQk zV7$pL=x#`C%m}aZsoWh|OrFidTEzmdgBi|gf)={^hM6TkSGH z5+)=P8u1z;erup$!Pm{DIs?_|QuC z5EnDDAXlH!gJfVNJYlxgP@f`A80srXGv1A5`m$`jWysy% zoSY_9$awvNmeD_aYq)W64RR?VC+UAx4Py{|brC{lIZSzB-(Dn`S!Xk!vHW^ujp1SM z`-P0~Pv!$nSUQ%)h{RWwBcxbIi;KrA8mCt~28Xs8iIZ&Ooytr){?kn9uP}tGb)sRB zkhN0R4!G>zu1xIe?sVqiK5pV#EqTEw^-ZvZJgr`@PNL!_6O&yvRTr|>E-{dX{*c1d z2qe&A+FT}YI1U8{VXs_u=C2vb$mD^X-iPxnQx(7dl>g$&3e(od3CLx5RTybWsSkWi zwuAcIt24B=90&U7Q%31WtMfY!gp{ZH>ikoB9A8@>AKfhdicf8Jr9w>Os`zuL@%Zr7 zWssJfn0-V^t&5W!RZVjND}W_9H2)oR3yr}~yHWWmo0t8Jxw@KN-y|<}uJ?<>w?$B+!pOa9%b-^o$U;Bp!D|7Rf&$Y-Cj^ z(LW`N!+a~qok&C&wlXse-rOPtp?Fo|H+b*P8}H&Z%@IF-`EiyseMHv6*p!s1vP!z# z)+vE%O?x1sb>(9SSK}1hk|3T+@VcO(sxC~Ewki#vlpEy=sS?$WdaMO@L!qC=EfuIkMMHPF#yqDGjOoxQtG34 zdn1P>*~FLd=mAu8ImQ}iaTQRv7b{%L&52J&2ARb0JG?laKhFcPKTiAkpDPn~NMP9{ zp_ST*8VxcaeGZq2ALs?xF?5S5Gl1)|KLrAw!a-hZdi8~U{5R=FFBYhZ2oVOt5oSxU z<`U&K`sT2Em1`RARCQ2?xap=`iuzQ;lhK~%n|eD-m`82P`C%jScb}hkG+vpe7FQL! z{m7eXq2sy5`Pv3mtI@li;J93)i$3GCV$kp)caNiGfcU;&T3j6bT<(ukS%Q>(-cAnL;ZF;7Ad-y>T{sT$znwo1g*vU&->)ixN9 zGlz1GT?%@!{;mm4PTbxC-2d@ZSSso3V=)hdk^WVk{9NcZaIa)pqMH5BpRZ-)qVkW7 zL}!(Fp3|vMk=d<~9@{p%ExLAsH&}Owi3%@zaIA-F6YV?khT~a06zZIRJ0@Jr>j9># z&9@3vr<1&U1a9jq?xCM%+LYF2I$@!wuM-DCBfyND2LcJ3M1cqEck14%0IMM>_&$T_Kj*GnO3P6IsGwL&6;0 zlSzWujI}chRwK1NV{HC)MoqCXB@pg{ zW-a|=Y`+bH8A{X;cu?#8kTdXU)}zP0ttzdRtt6@D5YeU=ERQ9c3LZr|?tV#Z<-orO2l%f3U?~Fl&(U?v6Ulmj?(DZ^L-fzSjc| z{+lj#|L@WvP|1GoB$#e_=#^t$)Uk7yp}&ZjeDw;b*x zsdc~J#){-Sa^9?9n9g}?3JbQ>T#AD7vX8m-_CWplSDT*CKGQm=y4Lw>G zyBWQSBG3UHCG6vb%dqM1f|T`#z%Cdf!kztRlE&v&BN70Rhg-@)N5ISW^1x~HU_B{&uH?cZz!W$%E%4htBLa`=DSmV&;Ov89OSe>R|N+mBd3PVtpL#d*xOLxRD2#b9^ zUP8D3$yO? z)OwFBx#9wPFr#sL#matBwIybR;wAWC9OmL}a1spVl!bT(5REpROkQN<`}{=H_I0{# z3*-eb8WG_gLKIFYBkZN!eE2Hf2~3Fvw;@JEdPL@W;)f?|GsYJyeX7-G#h>N?&^9t~ z8kF@Js~C)KA*w9dx~MG46|gCz^H5_2HV}1r)G36!-}sKdt*9J-LkbHq+9JHE`4~@U zdqKY=);rb35#F{U#bl9KALWf$ZOR&D#fDM4jyf)^MVXp8UXpKhg@hw&|O13rx zp4Ae1KhAswJVXvR7Wk1kD;?~ZF<*U zV}A)C=8VUzC+(W^ssub%;ZWBWk%2-E_w#zEkF^BC;VZM2LqVf>A?0Av#&SVxg)O0# ziD3S>4|Wq0^=0rCUY~`N97avd^Zb{=e#Y^YnEu-@2c#?z<8MD#EJA+TE7ro8^cSI{ zH{f(PIgw7a+6P++1wW^Bfw^c@V5-HPzgX%VM&y(iW~(kA8&!p3J1R(~wT+Wa(mXHK zRD+A+7a)N7EGCu zSt*WsLfn%_U9IS7qcyR7yxNepy>??(g?b7~6ZMtcRtpNYpjN6TZzaca@7c+F;!Wpy z>27K#tJGSkrv>#@f z?Wx#H_=1UR(DRApti(y^LU7yxuoca?4$X(S@Cl9RTl9#ONYAqg+9TP}9F>3?u8O?| z!&yunguv^x%2OWQJ(+e%smJ_wy&7%b%w5r%{?8-R>@ORD?`X!xL(z}$11G3Ki^RYm z+I-eh45*pZqd9q(i!!Dt-@Ij;``mU(PM=^32yKKi@D~LjwX^#}QI;6@X-@}lI4UmL zwnGcZFUJ_3a0Q#&X(XI?M#+Qy4>&9hU+{y`bTN;i9vOP=U1ze8({*in_rl=Qbthb^ ze>UoxvQbi-X-TqUgcLD0MraH!!3+%1SXYI`@I&oj4XDb^poW`KTN`?eo_@P@&EQwp zSxTZUPtYx~f~By^{xsZEw^T!2vGT;eqq3na$2f|tI`3qBiWV)sHAq?t zuk;>ix{coEtGeB#TH?TzV^R5!eg%N+fKSqA&(dN>M!EJ|Uws7DV7o2C@>jv2>rO(5 zbPv_#zW4&$t@!<3x#}*4&YM4BTnyXscfF$P+)+wdic!EUAMd_$LVq?%1lpixUAW5z zG{`lK;V`rdWC9}et+$0ZB~KHJUQKeYKb z`ZUSN`YO@+GN@6Eq^O*1#lslMMKf~Ce3jrXDK+gl=an*X(8&@lE3>`$GR`M@adM;k z=AXmR5K*qiALIYpi|0l0OhkUpB{y$D7jx@VwC`gftO>?v)sy<^PA+*6)@?5y9~IV$ zJ`PWKmFj2`$G4}zT&Oe1aAj=mIu>XkUlG;vCjvLegz3uo$aO5$VC>83q$8VO;q^Z2 zbLw1n-Mvf9>q@Zj@y)*4D?z#N^8NHp!sqVnPW=9F5-T+3HhIu}Eu5xL-*cKMXE)eh zk?MedEe;L}P$javGup6OeDK##vTVHxJFb5nu&0=L(>x-Xn+kR@Y>TNN6>iS6v}G8I zNiqA-%<-1931L!N@wQRxJi5O|89XXq({*wHZ!YEepM`Saq|0|} z--yFS{Q_jYA@85@CJ=uctlglcfaHJV-y*W~L`r&up%YmLh2m~*N_qsL+q3nI!(WkG z`KqN8bM8r1Jg{~>7qbh)q!H6zk=ywqu?ZiIM|baq$|RbD)dw1e&RAy7rrx) zu7AEBaE<(qGEJRt8@g*sqTBq8%@JMR}j4P1{N6 z6XoD33?J!W0*0BdLilmh*_s7b7pGslEJoxKIB$Y-#v|eh*9T+?o_IcSp!9yMHB)~26x3QU% zr#J`x=v>TFE9t|)`9lYqDVp=zFjduG>fCG)oH)Gc72QaZp!kRYSw?cqb)KV4>Raq+ zU7Ck(*^Wc1@Zz>M&J|LkSsZo@;(-spAE{Id|e089Wwsxb6syuPpS zPA2-QE?f=aaw_)a*#lCKeXN5;DQ;x?q>)89PD*O6ad)c3%#+1o1(&rI^0C>(f%d$w zhEnUTM90ke9qw3*4aD@kaOQ0MtCb4#&65vI-A`Z8q>fb>OBXj_>9ibDlDBJI_ z!G5Ssc`vl=ccE_p-#K|h?41n$k}28C(*qap(c>A;&b-ITW?h4#%FESiu=7`ZT#x+D z=eDi)dr@K%`XxX2if8$}#(sk8g_$Q~2G!A{nRw&EUbsu-L_17OF8^-7Z-U8Fv7Ajn z^87(K%T7NOy!@x|##9!+g_2)9uSp2;>#0EiyG_vnyA8P>KaLkwRQWH!#t zB~LAk;^q(@O)$Alm+Epq3*C71l+WMDJgI{#OIfe|KQS{m78%8#BTqih=qzh`AL#2K zKb~%_MW#F*Y)V_)DIK;RKtzMZ6FO5J6z=I3fHa z26U3TZl5OBW~t!>ixn9v>F4}d$t=^9g@HNy5oKOdDDnepPipDWvhu_jv{V<#ZTlT#&k~&L;Ux&oeHaB`qd4yj--X;LidB2l&L~-rHix1FF#oyC1ld?Gm)b?v) zn^ejUC74w)wSVFD{#sYe)cjwY&#<4+R>B+3u(WVvnKkd+d z$HVOca+kxR2YMXJly5vWZnhgS;jK2DS^2dD$jJI$Zf^0f>KyVvQQ^(yUxWBx-1{1GC^})>yma~=V>ZQZ zp>n1bHOB@#`TP;;ub8=edPG)Rkzd(9UOkV%rn9%W@Ll)fn6ATtKEIPd^k6bc@MJUL z@Z>U?@!&IY^-wgy^e{H5o)LDc$HJHwOy-JMO`d42gp=6@;)!uMd22yjaP3g9QZGqd#u2gZ}6gqV0wHt`HpX(m4xF2ghvB2xK)0(})ObTxgfTKU?&+a#c zcDwQ&2i|kOt<4)ERKIb7_JNBjBL>&$&oG`=&k5=0Tv;6)Y3Nt2z%v40Jio{I^CG86 zP3UJ1d$@b6XFeJz5SN(|b+iEkR0I^8Vk(;hrNRGj8T0&L zxX|O@7IWO}NTEPnZB&-gd)pPAwdk!VXJ5>w>nRJ9%wD?fcMHDIU<;a#|FcjFW^xZ+ z{w=PIM()=i>+%G~*o5$X=7_3)8Q}MUDtJ#E*FR0yVmiqE@D&ZmSZs|g8XmT0`o4VN~!qWD@N|;@5 zM`h|y$c{Pyq1M{k4OC*f&~!E7__eK)EhC?nZiM7Kycscqv;4f|PoO@z!m+K}R*~pX zBLuh*^uuniTOy&PMnAOCxuxAXq#X9AbtHvbpH8YL2s9m#%`@s7h^qh4}+I`w!jwAgm9DyE zM#C_|-Qh27Up{*h6%Tn2XoFVUZ);E<0{l@Pgp@)BbdO_*zgK=nt~Y%}41(1`2yMLP0LJhu8jD zoUjmB?ylR6V|yzf<@RZeXBijXy*!2ATiy}bUHm-RODE(FNRb^;0zRi?pkdu)R*K$c8k@n6TeqC;NKTc2P;0Bv z$bEcP0=i+dg>Ol{-G%^^IVG9kf8*_*2*_Q)gK46%cj40(J{S+Rj47T!#z& zukf81?_Oqyi?==wegWoDnG64Tl&6gDfL#A6)H{#T4H@7xoJzXP+-lALq_>|q0}WEs z0j&N@Z&tzHX$H<%KwvSg{gXEiIDvVmQ9b{oQOj%`ox0cAPEGBMV%z*B|7g@O<$8*D z$pU`7+K5muR_^TzEBni=gL@LO(5}D#Da0Lk(e<^m=H7s;)6L08@NKjX4f<)rycX-2 z+edDLHAQcO1;G!)mfPU|ET2v8Z%^MDgBI$#I{mTS@c1}6N~zYclUbkm_-1M+&UJ0$ z9yImYA)&?3PvGY7ETwpMzVt>p+6_fH23koqFV>T{TQ8w^x}L(pDGNu#rIbAkat7k$ZvjQGdd3A@f3W)Nh%yQO`#XXWo%raTL~K7k{yCfp=WV|UOrlJaPy z=7(eh!{rP390Vt$zL2DAs&Lhkdv7eFzoIkIUcEU!6rREbS-tv1JDjtuuW_BgAsKRq z+uWUn&A11lKJ$}}E`aURfe#M^KQc+?*DqVnmeFtj9`t+rad+J{y|j^WuD_ue1S%(u zy%G1E0|64){D2GAV?-fR2I0#4m2ObD<+JeYNN{vSCjWi@KKqC4+kD_#bMoV>(0|JJ z<72TBa7xN&Mb5`ByPF~J{G9z={I2`i3HmHA2K#2kOZYt4F>_btqGfjd@;~i>?=Tuz z^1kONI1AYY>jzT}{)>Mw#-jeRj2=8afwhA_ci`0J`~v*_dS)*c0uR0uBVPaI_~IN-uL!FmfgPcSREPXnHHb^{bB z?|rMlIlh5r)n(?K-$kU__j8RGSVL|2PTl00Sw924w^i=ur1@DJy==OYvbTRZzEbjx zo3DWm_wqj{n0z1g>fg`nz|OD>&Mox*Aq`Un=6aOSU{iag!FHR%dyap6#M!xg`(KXV zra$Zgn~fs~xX_l^N3kxdJZ%YthJB3Kw}O4xlz>eZ27Cz9xqHi&ogjRM-bZPax@8t< z>(aEvxo0!QS>QLtx&PgO=)JWF{i=Pk(k&Ex*ILo*0!#E`G>iYfexW6>N-q!s+RK9Z zzFi8~ZBH1|v0+FG?Dmrfc54=PY|u{|RF5>^kbHTN2SU`+G3A^>8x4zYrT^-9U79!W z%R7Y@#l8I=W?XSSFL4TOm|TlPA~NJz26g-0K@bS>C8py8L?h0Jf4O|t_A(+|vcRrm z4&asfI1}W43jJ@;-$H^EOquT;lwE#KUTHv|KtF+bS;r+eM91ZP*4jNt$Dtje)%cy3 zal%!B0U?*lk~wB1&qEEX28X00tRts%paF+MS;PmR>H_ls1fPSFHV^_l@|g?efg*{{ zr_ldVFmfOS*@8g~QgXsW{wTK{Gl~3FLg0PjMdq&&Js?EyeB{=WJ`e)$j4S;Vy0oyq zTD_IJ$|j7t4wR^&QZL*wgT~b{GeJ>p(vI#8E>T- z%o?1TobQ$Z#&18OM^2#KN`D)$pjB9H=^&gyfA}4EMzlM6bOzsHuz>hKvUOZ13q}NO ziEowf6zhHSf6AE&GY!o2?@gQ@cGr7}Z;3TH<>29e0{wr(|1|giXWA#J2IoI8U@>s-n6U6UZ_iT z@7y6s%5rZ}DDOu9aV>xs*aM!P{&~O|IO;Px;QYQnoB8Jf6aPG*EukLq{vO8l^c@D; z{0H2W6=>VK{*kQ;)GZwy6nR0%b?|tnyhk|$&!Uw5r;okMT;u<6z`^9-WXkR-B;LCI z6F8k-W6HT)t@wun=WX|m`zS{jIF;lmm%UPiMYBchJ(AXi0+x= zVPDU*26A~TWCZf2{A0=tvvH@Yl*<1+-pDA2s8~8HpuaqpL%#57bpOVwFd+tZ0E)Gw zgc=IVqXZVelwO?AelESJ2=J_hXm!B))9Ub%2BP)cw6LKAF+p3po;{%w*YJ?ndvZ8<{b56@?jO6scouW@<1|yL~Cv4yk znz^rCSN@H5!n=X2sAZJo1kdH&^f@Pt!UybV2Yi-wNjyILkShHChBiL?clvYO3}DRL zCOTCA06qeO@D4_#(qY48b>4~i;7`PZ5M9?@EYUjPkM}yWa(lKK64wtmkVe3k; ztZh-NE=$ z4MtNot8yf(<|%lWZr>1a9R=L=;q)U(m7V;tt^ZuQNBx@av=`Dr#DF#%h_>gHslp=I zQgE&4#=QS=;eN}fy!Y57z9m}Wj{vTr2r>TpS7I?3(tSJh0~3r6A<6%c?#Lv*Lx_t~ zPj#=TSzy}dC(F3Q=A|vGZMh6Xl(>1}~v{kvB7!VP>pI2miI1M|iD zPu@z!|AWXEx$QQGcZggDadbX`#W=7G-sU&VU+(Q4$zNWa;FC)&PE*$W2lZ4HX9bQ`zB6G3;XIo45Ou!(?1 zysf>~_tLG7Xhj%+q0xdX_7Bgk0rdB+<^5SO;lMLtU>)egQ{b2r$B=v7U*mpSGu}ww z0ldDw@n(%ajdi%}Ph@)9nFDq=POt*@c>E*VHm6JtJw@t>E0`6JOh9iqZxgTUddsit z-ezxAzYBuaS8Jc`-cSu84**S$Bz@=hjD4k%cI$Ss_u0Vjq;D6Q_-3EeWY=y^J6!_B zMAlnqsayCxD!R^?d(2PW(dV~-@Kgbv9%8VZ1v9>lkbIU&wnRBmim3rdPN5bVD$HokHevnus1<+W_*>K9Y)S|Xe7^EYwL5Hh& zs&v?%<7#)*G)Zc;LxT8|?hZ^6y`R3@^dAFw{1SIEA93dx@Vf ze~GBj#!uut-ZdWQLz^$D3DlwG{2U!{ShKovX72Tuvm#AOTESgMbEiVk6 z+~gGph`Hytl=lTHW{NE0R3(TWh&f|g!EGuD#QZZtBr(u9m9HYrv7loDXmdBjT3c|iA-Gow>?pp^fIfZe^61XfL6oK%OU$`atUn5l-eih(< z>m>Zf?u&|%^S!#1$(|Um-Hie{QL>`cuti`)N<_JgGQE%}?=rfMA8wB0;)3Z(BXsbZ z^v9z@19IHgV&}vi*GJt3fhQ1uNm4uVo7c<38{h=^3JTYU%w!`z-moGUj+0)Y{{+&c z%+f8T<8td?)o+NXt#i>l>C21k>&Sf*A-+O=)Z+J%P#2bG4U0#XEhBzIePw*hQMxa> z#hATaAE#VJp*)*?BX+i`!&jDN;Ckuu`;Gbt`?4|hXw3dO_qJe!-)GZWHUxp&R5LH= zbzu>uuBpNlCtg21{xx{~!tWb<&zdA5;wlR;54_wRk6PWL$Ew8!gVQl8_~Hj}Db)E$ zqn_iV{L=b~X|gHb$b^Mmx}E37mi%*UIc55dWv%3LYr$gppf{V6#x~7t(|~!#-^or1 zrXg>n)a^Wza=Yj? z?Wod8Ry0g}bi8d1lE}IF*@e%Vq-*hk2Q=Hv!>Lcpt!rb6)n?u@PXu4TOIhWV0`)Cc zV{sza=~v&hgQH^-$07G+wAdr#^H;@qU;MWbRHw#=kV#vp15b!Dt!<8+e$DUv_breX zfIYY5HDm(nU{=Rl_vx)Sjf0Hxx1`9wN8!Z#U$c-|K!3+YC(b?O3q9uCHLu;FSRjz` zl3^=jQ4iz&of!!NlRmJ-EJi}?MT-9X3!8}w19O3h5j#)u8- zv*}xBZvlsVpeEj~!r#_GyT$!s?ogRLYv{Rpr8>*8++XJHY@9ia=9xR%-*3G8Wo7$k zrnN@C_I<6?#9x0o9sIh#wThDA>OeWq7CuhzjmmgN)WN$r`I&kW4u`r;W6k=4v*J*Z z77NyMj?drvJQPx>VpBbGEgl%QU;|sb#X967E8Cl9aQL^KtXEt^0PVsa#yc)v&AXJp z6vPxj*H(fO$uB1$sIu00#0=U^#4YJue6a!uxFoDTdHmSF%71ecQqq{%A8x$TUa-jl z|DdrP(Gj0kMe8ZQy^V5t23jogcvV*U<8L7)GaY9C9{}b+8NV)0O)3J{rlF~q4}dqc z`vzY#$V{$cJ;K1bEKi;|S{hY*7625RK<><|n+CMFvSB}Y$aot*c)erXLp5?|UcEG+ zP5IRYw793~l_8G_)qVZW1j5;&#^f16X9g|`Y2Rvp;oxj{Y6uTK-x*c9kPeVXr|cNJ z(Xl)JV66Ia=hDU}^M8%c6lHv-*v3cLYv3>d46#4grBn>Z+LGCBR}Sq~005u#`#V?J z-_)xts_dqouB>-UaMZo!E>2Bcc=ERuV3f$N?A+1Xf)zVs!O=6KtfO7axYgYB0H5^u zoi-n-Z9XpECe8MN^lB_$V{1yOh_-7jK9LNiHL<2;lgEscM#*nZlJ%tvXt_0sap)c+i_JdKUbxl1^_S&&!=x)=>;-< zKioT$57uJlzYF-=C> z^TKwIZ2NU7wi#%9LD=qG~mTzmW5B zTCVcEw0z9QIdsdF+oLuG4(qbvVIl8v>c+^ZL&t)sBLEZaJjV*4T5mJzmBWLxJ_Y!{(zSz-IFZ0qvwJ-RHO?^V5A z?5>&E=fzl8(#ZMYm>-_5k<;YxPMVbVmSCOoqP=T!dv2w~wiInE2;1*vTlaTkb1i-? zT84Ehi8?>Xb#$fpUyin(!uChmwsI-9E6}!zu)QJMRxQPLCE8XKwl`(l>hH!~PK_IL zxeDvl5Or?J^XIl)wPtDaXEoNTCF6Z&6+wt8WESN8vtT%}HF{?}q1tr%ZN zd0?GNnEz|3Ax)z25C}`Ql@>9_us`b$*fSG%h8l8_?EU*glYLo0MW(gtkqE z?L*nN*}Jj06^~^j)@d&4{3_Qmyj!P&d>**tpJpk6uAt2rByY-!7nlaXt$rM|4d`gU zaulc>4ONc(m7@gZsGo9_s2mMcjsldU-O7=Xj_}j2gXLv|A^683eJK7Zr%4}1e;v}3 z#3nLY(F-gc)Ra<(*b3;Nrj$C=RzL?erPN`z0y?NEr6$=5=%D29yLL^LO8&m<5ZC|t zeOL6AE7ml5a)RQ8OJ1?Awx5UO$@pg*U_KE~M#dr3K{&tF2_KJacXK;tNy2${H zB9c||4^j?uwfh$C2ztT8_xBNEL0tUeBxaU=oU(SK(9pF}qI8I@G@@AX(2|RXy<5DK ziZS8VU094?{`Itd`4xEvdm_pu9$%fV5(!7eT? zM@QwA?E>R+cn1KSF^#RJFT1soN4)A zl3inQtJiU;vb6y{n!2;*v|2l9f74_C> z>K+ltdk1_ZJ~y;p3tDR)4zRXKS*`WDyx{E3bn+UeL%T@~a51cs>?rjL1C4bEHZIn? z)Y6_Dw6y5;ja_N-;-?Io+$D31vtu;(ih?%UHXUiV@`9ZzU|zQyf&4Q_Z;M2@i(dcz zyi2QNO?CvWsF+>6;{UFfsn*MRw;qGa|9gBI|G#I$!5;uN|HHU?NZh(l3tIec@nkJ% z<-E$dS2Y=0&_)!?b*<9c7iX^MbF-8Mcu`$c?4pYGqX1wFg9^M!UrtvwXRRlFnyhlO z%HHpBo%I@kb6Lk)&dut3gJUh%W;MNQtEH~3DsoQ(7pGdz%_MJ)r>nQ5b1jn`pC*k; zsX+HyPmgyGiroV_yaxbV>AEhZx@*AMx{c93yR)cJ*1L)>d2yOFN>)OfzbmEYki?{L-P+_+ha2gXsU zcn|UOdI_9OaL^ow;7tv5BLj{IRub zm{0k$eKnw6D3rzLxMnG9aNt+}{nkhuJ0;vo!=T$f&v%yt+6F2 zYG7h!6;BMshFgVFD()0oca%^MSM#moN>Jd^y>(v+3S6JZ$L_x=a53Jxy#z%~j4ii> zLBnNs>y#1_ifigpDlVy8BT8_1-1Z#ut^(J}tsj)2z=d(^=LK5L;K25Q3gA zZgnv9*(HRIE4|k25**^Pt~I&@1@2v2SC^o`MO}KacvpcdxYo=P6u4Y#olt@T*JiDA zO6UzPz|tp!G}ezwsE2E<)_En=!-Z8T71vd*MJ3d$i|LtDLVD_$DOSxDaZ!mY~4ZPP*N`+nf3{rVTK*f)ZlG9eryg)dT2e>er0{qctZN z+`n$!%+Rcrp;e3aSmFS~aqlHsg1VFN$Dkz8d<0O}G$wiI$iXRSp>gDJFHM5Fp(c1R z)qy@QQ@3|$-yQ()da47wUI9?I+i+{5Sl5yOSojaCyvyK+HveR;@PnY(`ayY59-eT3 z*5v?Blmn>F02*lMdl3-rA=^2~&rGlZ+m-;1NdRMTO^5-M$LB8q`clPedCvm(nD8@> zxDz-`?xhw^n;K2if@c`Y72zn|g9lHk2tQ0i?Xi$iFqcY;t3EeV%E9yul#^GYyi*P(lt(#ijAxD( zo{Z=lqlHz};IhZmM>%QpHH%12+S8raWgUq#?SCxa` z%6sPMVCY&b`FIV=V{5Sdr{&n9aU<61nmX=D>ndbm}qyO?2rwUAT}{p68l)li%#PGOjn)}62w7iz^V9K9_d%X5_N)>M9u zO3J#S+)Cv)f*VaM2faICK7Gcae?4XRo|W=BJbpzvcsU%8=Z=nFp@r{5&~t5?fmMg2 z44Q&+;~?j0T39|Bk3XatvWVo-c$61b;>=ky$az{-nC*)h$Xk!)rerK{m5ax9E$Dfc z1&`Y&qa2oukt~~q{W_F~wY1%^JfiELDO#w~4YgHhRwR|j(Wzd5#}Q-hC+XnHtSc$* zP&F4VyHCJT-biB|M91&D;hFL{3FXG+=(AE@k6az>&%ctQg-+DUUo=LwsHa;zQC{>w zxtWf?@wlH<6H>~}$U)iprK7YkX$+0;XpC^iD9;>s(67h2^a+&=^u;(U zjmG(Q(7#8nJ7f=Xp5_ie&s)<~2iIu+xX@gxOS9{E7o1%?sQmew&8xI9Xuu&H>3P2<{Kv{5NZm??I{iK0_S8JrBzDQcAMbv=E2^vENzRXU)pA-o? z@Fkj@8$KlxJfR1*kXL?A9?}S+pF&5)_(L^FBhs6(^%{^#)PiEE2~&wYsDHH~i)c6X zs2Q9#SA(z@aG{4cbNT?5aq{|XInGRCU>O(%!LTax8%#pK8mJMJoQM}L<5M@() z4Pc&}LDP$Dr1?ZaK{6G}^+@Lhi-~?Q%d~`OYKTlriJlTQg5^X`RL>jlLCg(46tsq@ z4N((VN3@lwDHIX;5H*KQMAZasma{};fUQJr!;o6QcA`H8?I7Ahv2=kwz?d7{%D9mo z30+_>>3oODbe*#MI3KAOyryw~HS|XIKhkNL&J6Dn z&5c0{gI+`rh9X7bZ?iHt2&4AmA(_0GSE82zFq2_qiz%`pu#_m8XgKb{GdE}+awFRx zM!;&uzzs%|Zaf?yFRdw-iExB;+ew!OU&*ag)M;>)$c0+ZfJa0{6xkGf1YvG4f?Ajc z6(r2Wo0+Ja4oyh+K{)EBLo3pCn}xc$(35ntf>1XX{75&E>J>l;>E2Mi0*EBtK&m$n z;z+kE7AJs#e zOLR5w=M6{&66S1Ez;E#b6;)Kzt+X=79(vK{6fyD9Z)_NS$VesS_ z=WX8|a{!J&N75Y{i@GDwiKCZ0zDTFwDACJYq%Yuej_uu~_AbJ0qCR7gF2Q|{?Omq! zZh=;V)OVUpjvDmRddRe#uh=8-&|u%|befjao;`+oWEnPdT23TrSv|6>JVd4j8f>Ba zkZCys!IgC-8nzP2jRlis8S?4QqDc4YVASbYh6a1op1gRnIb?aJ!v5SysKVAzd+o*} zRcBjCXDlz%HmWzX2&pF9OS-k?_UATZwb&s|2Drh33j1?wOSRZ((#4Wx9d?tbru+Wf z&a57LLUgD6{@gIufH^u~d#&F`%Lc5n1M2pa-=8~-HDFapcU0&Mq}xPsHefA?s=Dvb zoyZ!o4h|UE&lRzSMywafa>iphtz^CAj=_?iB@bSc{w~QeMTg-VIL7)5wwXc&2_(tM>c4HqOVDpz|zS}Ml5<6z-EvxdnD?Hur*qYvr{LGGl}gY%Rh3^ za-5(_YqC~FLMl5&bZ0fvBzBW%Bwab!7-m2pJcz82{NR+H#0>2g?8 zqQz7%pY67FMu*LJ^Pht6X}ZB8=^RB{Uhe=ME6wEZDw_e zrju?fGZ1wl+Ri!=T_>MASwEr<)c}N#nxiYA8CCdw}Jdrx_|It%ddcrJ>HM&wyeSDvh@D86!=w z^BE^d_Ss*`cUEH=BCU1C{=L#64V5+%B~(L7mUi3uOqCAV`J5_9_L(hxXXi6ldO=<~ zdLqr2bY+oNkX$&gYkcWS?J4Id(oTOUubiEXDb)w3+BA#d%ZOZRhhB>5!ezhk|6EPo;}?K3_<` zke6c==O5CaM2*R(#3dKiXIWm>MfK?-NcLHQ*LG2T>UbL$^sC6otjYf*) zTZujqwB4mFKFI~3msoz8>cx~pT^#?ObmxqyOXPQmzMG9SfdA@(v*%3)S`OqdT&Cca zC<|#22Um5L58?r?SZ_oM)*H-YNH?ki>V|VGQDtgxBu{olFK4D8jpFayc^Qqw0IC@= zFDZPy9gQnS6N}NLVl<@~Wfh|t#V8j^X?=Dvn#)t|Xda*BiaA@J3DA)j@=T&5L<@M1 zo#i54NV+<%NXz(QJKb`=l&CyqVkKW;r(4ZeyH;WVb38GFYxz2&C)MpK&CQ;0!B-}n zAu9uIgsnSM>+AS7SKBo*64vt_L?1Jx4SW|-IgV7s_Yif^AZ_IPi12wX5=S;O$c23F=id^wqfC6t zzmxk;u^i;z6I~+9L;MG#T*|~}{031l`8>>T5j~?=j_^A~2Pu}L{3oI&6w7gbpXhg@ z6Z{vV0BZdte@GNWt$)rR5iO)loZ?T29#AY_@MlErsr57bH=<~2{Y(CWs0_7!mj6NY z4bfNp6;U^8{XBm|^aFYMn*T+_$jb%J${`J)$iCs6D4!y`#2tt>Q)HL9BauXTxx&j3 zttR@GmnB+AEnMZUL=n{bHC~>`r3}*dyaLf^YW+H|NVJyP`+-*?I!5jN$UTWFQwukE z6{3AaH+eOpTGZQHyav(FM0a>CqLI}4U9Km3L6P0#b%}-&-RJd*CQ{#j<_(FiQtQ9) zMnui1y$9Tz=p4~Q-jt{fwf8G;PPCPLKH@EimQd?Yc&l;^plxq224}b@pj;Y#-;u%Lakm{AY*>e_*KYjGlfkDg zo_XrwiZ{w%n!ab$LqTIa-tUdtVJ&=&VHqd$pLvF5B;;ZF`|Ua#^-!rJdW&z>&B$PN zhi*natZC+B)Wdo&tktnST7Ez!58I->{juDZT(Y-=?6apaEaTE#Y^6&9%9C^~(eVK~ z{xTWmd^+B;4yA##t|a3VP@W%#-s(47WC!)tyS%qR>S1;3 z>jrFPxls?k)XLVDw+z7fzVg-y8MdIP#W(LH@X#Mm)=I8ujp^b#6yJRB3~^MUn`7R-xTF&nz7IU`Zl98g67`O{?9yh(4(8PpAH-gW&hZ2_sEuh zdbkyVmZ4+3j4hyf$J~xB$a4#D@j}_EHOi5VP!6Dy9c@saCE4&jl#V2`8ny9j0TUXa zd_}Sy$+})B->;9dV?&gmQ2A~uc~%c)YdU^Wzm1Rvizrsl-4skz_2%86?}0e6d(l+YiXS*`YZr20H$S1y*eD3RE_Im+-cqT^p*(_z7QSunI z!#Rv~8RXSoRYMQu#~-WF0v5IZp$3EHwexD?aUsb>KRq02jW)9X?^|ViF!0wW`Z1`c z|E0bj+}pRTtA}6T>t2_^_S$(hWuI90SPea_@Htvf50TAgdDvTGNwx=SC)8)KxGP>m zGCKTHpF#QBE$hmj)kt2}*F%99MviwVJ-qT6?5Bqs2D*;581B~sew{Yj&o+WnQU+t@ zV^GeYfR;UGO!U*kp()e+^bk;h`N@yX^V38BSoCnyg7du7{QKfQi1$jAhQ26c$Dnko zi?Z(r80YvR%!BMr_KZFm)TeNxG3|l<=9@=0z&iti* zEx^=ash=JmHNo<0t???W(&}$YzBbmei;|&%Yw@D(E2$&|6#bcBv*FSFDO2R+5Lf z>R8g7EI+S-$1~};Q_Tec`qTy{Aepo4?ezX{O6 z{2D0RRrn!52OBHC4A4Q#N-lvqFzadrVqLF59qgB|?sAu=fjWQ+odR``T(M7}4*v9r z4Zth;7=QMEe)25xk)J$m*RhP)=)kayb0mN3h4O80l$}WW^gwy9FUle+Nhh2A*(jq^ zq678dSlbe)hrrtSG<&TsUVEQR9T=#G@24gg)9T^9%!z?5ATn!mU<;_6^+BKxK1iBF zz1|Y2gU1|uz29YLpbn~6_$E*Xc@-Z8>OfbiYLE`v>0&9jzCmh!dIn*BLW3|rmLSZ} z#2_71*Tn|ddbc|=H|W1bI5$X*a6u4edu0%VGl6(^G+BnnC+RMBw9dL9EgY<~E{K8C z$Xf<2aC%bZu3Li`45(lUEV*&+|?c{V64~i)$_U?jR@2*hq2LGow0$Y!;*5C7RE%0WL7A^%JH8M7dIg7N9G%_kKaR4|E>}U6tEgZGyPC!a%j*VeR9ODs`(j_~pZcPQ zJGJqChieXu)*KkEIWTCipXQ;5!?p4K&Or0wo7z464z%M1wC!<&})tTmrx4O%st zK*!J8`Op)^)jB%3E1jr=$3=P_)G0GZAC}Q(#4}GU85*pELDI-zTR!V0?xuGHl$ZMN zR^B55S048Z%eXx7xKgrvz-zy-44?ielLx(4%J1|o>yP(hl>4TZ^;h3Ss`}$Q2+Do~ zsw(AyD|`E+XOw#qdn@I>ffoO;jP8LJrR2$qf&O6`&sPle$H*rH;~jQVFdomOP(*lpB7CM;b^pvuF zhCH7S*29$c*MqfCWy3TNEf}(I1*3c#tOaAVml0(vk}dsEPW4CmC)v#Hi1JOaO-{&i z2tloL2$pmqPqGDUKR+Pdv%v$v1R7+ozy?YkPG%|`ZI$*x~= zExFeiv&{pxWvk_?WX!(A=1m@vnpDzMZjmCK9B3O6`_{4DApBuy$g1p?iJ$k4EJlgN zC`qBH5C+o}`pbdAykfdF#p->kP<#l3FN^6eDTE(kx<^z1c^?c6VbDj9bQjWdE_re= z6JZ$FyLl99RjGMbM#2sP< z4Vk+#q#_KH$!Yws4Iv(ohQzYqoMlsp2War83}6lns<$UZ2la>w!K?575FJcY=tzhs z)N5d)(;-zr)6hnjLaIX#FB{zosSU>(+30CVeaQB<5jT0mtfn^7nOefR<`~Ny*d6U{ z>Htm#nI?8>Yw8RJLGO2JYx0E%LAwGwnYzI&B$xvS1NVgVf{%r6`=FktUhsf)Sxqp++lp9E{M2XijBaW>Gmm zXoJKGoto8)GxT}849g2#ljLn)7%%C;^2Tn%T4j{QlT{_3wYoc|6vZi zpRmZ30K<`30Yt?tG9|(ig_fK8!x^GNxVUVQX#m_-Xt`-1yjEz9X%JMxA6jRHaCBLb zX)x4QXpw0Mm=)S=8VWHAEjJB=*$VA2CBerEtuYOUb^~$4sNa zv7Jn*^Y@y@Kox}!n#MvCg^roVLm#37m_NU#DHSXV%{HaMctN@I_nOjSoyqJ(9VICitf2-4(t=U27X&>7QkUaPrDhk1#rsFO95OJy4^hu z+IetO(6K>`&4Zr>T^JMq^Wd3Xz4_qU$wod<2sLGL@|^wJv;Z0?ItB}%c`<6IkOM4$ zzM@|K#$fG22)46a2vNnTe=!FL`GUr-s>nWs1%f86 zV&)HFg`Jm0P*jYz7o+_O1;8RWExZIoGPW2lC^|P-47Y_YudYG61Re;wI~VD>pyzYl zU#_1~nC(8!Usi3i-ft=q)Tm^In@)K%k=2npeOmg)Xp5kVAkW!V%|)Q^qI%f~O^EKY-O;to8{s7W@CD3)4cQIMAHi)wTBE_d z3F_e&XJHO>^D~$?!)!qoKZAJ-Jm@LY;pqnRR_Ng;)9iT$^ET)hAXE2w2J?2<8!S_r zpTWEXB0^+Zon|oagqGu^tPy-`6M_GRmbQQcn}r9Cg^HD1=j@Ca>Xg&wmM#*&2Db#!(B9ditXQAc`@G*Tyx&XFo2bwR! zmDYg{4H9P9X(bYyJpDvd$^jf3f*T*d}PP|4Q?Z@QI+X=^M>A z;E154>ATD~;IyEO=^M>A;ewzA(|4I~!Zkr%^N*Tu!EHey`De|y;Gv*jr(84NhTjA^ zP5sGy8{P`C4EfD`2ekMfUP6{r%8*y)J5XMbwJT$H!Bf!WuA0!hP@AX_x(1XD{RwUp z&4GhC)k1#;zYLiUZR{2L80KZlRFpG1^miCFMSW`i6Aln%LRy|ntv_KF{*R$h=;XR_ zZs;pmDClX?+|W0$b*lPw_!o?vram411!>c4Pw7iSfkjWJIDx_HP{syKx4pd-g);V~ z(AAi?HB@3{vutB@Gqen=Ba_pswZU2!)>x3kx|g9Ytd*drd4GkvG9N)K-#N^c^$--5 zT{)~A^Aj{UyIxp%7LjGkQmZgGR__D3^~Z%>!z!}b*)pB7b`7hqyqlD52f zvcWTKdFdBcnWYGNTGTJBDodZC=A}CObcULj>g>u4TVBjzHCVHmw!TM))nt8Ss_d3I zCQQ%9&Q#}T12#q0IX%sr7S@2x5XAEH!y2#xK_T4?+JBYTtXa0dv&8_hz5Y zvGv_8ybn7i=xLE#ct3Vwj@oxWR&lP{cR$u-uC4D?!~NNmxwii4!vok7nJT+wdWQ$I z9dp(BVq%A6om0`^t6?T~QqYdUt-?+0D?vx%3|ce0BIs7!(l9f-Jy-2RC^HnOeF$a4 z3T%Do7#_yP3VK@9F+76JDp30n#m*F{eTZV$3T%Do6&}sn&a;i5e|QY*GS4=Crtlb+ zAaph6MTN(*6qzc!Wey0BXCKZ}``4eXm32;G>w>lY*(O0r>t2TTXS)R%)&*+^u!DlS zuX`CffSsJDK9vk)=LFU2wKQxXyE0E5=|SwrdFn_HV)y5%c^S-_%vbX=m>rsL%ggZa zA?&oEr$xiVhq2r9)w~R6v4v`0hO^;?w!DlDAHiM}+VYYfK9c=aXv@pg@R6+j0$W~Y zgj-pROqJa-3&T^`j0I|5#P#NbmMu_c@_4p>fqFHaz_u(;0?3AF>8x7i2c3#l+jZ4E)*;PSbCmXbB?50hZyfiG0Jy>8nJGO?Wvkw=l z8O&gxFH|#_!3KS3%izKAOqML@Y0<&(sVw_LHG|VxuzAd3shZFE%yp@n&-qLzX!Hce3Rz7-lP73G3t2-!QP~a= z3s`eOgR|Wt7O-|ib0BSf&4`8U*iu_|>qmUZeq5$zcM0pU+-B(;v4jm@uI73fb6#P~ zwJ~Bj(+PT7WQ(Gcb?ERItT*pMLWf?1NxlW8&%gzg3 zjd?>O*0DD-Rd&nFi1>(A$N#1bvYcYaK5M>()e~eLn-{T#H5F7ecy;Jj)<)2d!3!d` zvd)64#2K{PST8}|aZAItu|PpfM;vRuorT-#jaU(}ompgp3-uW6U}>w=EbU;QuTt}} zgY{W$%S%zjPG%PLw5TXzH?yo(^Rky6S*_+}FT1wdmX{q7`&hp{<)e3(61tFERUW*+O*dWYGab*kkN_E;e|IKsxRmwVeO)}TGg zCJ9;|%h*wtC1_`C032ocg7)_^XpgZ4f-d%QgJW!kpfkM<+T(14pqstj;5geRsBCY8 z_5|A}NZ;EHPOu|_SPz5tB>O^8wH|J8l3frqpzk*kpR?}-jp}mWVnM#Nd>D|9%v=a}CHHS_1#fFgOsW+mN=_=>&WB-4NZDe?lVyjiSc zPN@`mg-sLmePP4M@7Tf3>WKZoPAb$k@&|TVCa3(_jNM>A2wFE=6MBQ)6ZE`am&lv! zv7k5odPd%4uQ%Jy_khS-%y)}Alkc!uThy6+hh5!bo5|6UciCM*Pm7`>@3F_C9`^QU zR%NR?lYeF{x7ud%z{p?Nz^%5KJUsFN8&5O`3`sR3eq}D()GR$>+U>Hg&eXAyk6ArI z-*p-r`HVdfw6D|H$luw99Wwo#JT>wUR&SR~?#t&!zF}7cO*f8>1gYb0TYJkRxinz6 z-Cl?^LZOY34pPBx^?F@KS}yCHs%?Bzsf@HFTWw{29;M!HH(_Nb%cCXL^tj*6R9 zWv^{iu18jo>I-^WbUm`7)N!vmDwU*Nd(}~?Bpusp8tswsUZ>zwu)7n^HJw*;Lwt~A$_9tgTLrCL-i z>A9erQyN6olHe1$w;97)N7a^Ae4_TYj`ZLYwYPPo2K$w}OjKQ|rJ$!p#;E#IpZ#iY z8%o*x)!sIg7VcN>GErXAv;E3lCaRJ2YQJ)qiE1R(IH25Rq8dxhWvc9!IWek<)a!uS z+h$V80kyZyq^JXGZ<|T|1!Z)46xm!#5;U{Zw5aCNm;<)4&5bfhA0JSA+e&(QK<#ZS zspY4(-Y$)5Ep-<3v}kG6dy@H6wYTl0d7rAiZ6~ey)YjYeQSGI_KDG6Bb5sY(@u01@ zJEJ;CjSkv+yFbcD@;Rukrgf5n6gnBzSsJL&nW!$(M1{^r`AYc;U5e@|tx?DUx=H&K zn&aMGI;&7rNDt|jLf=L8lwK>HDD-nwAE~86kE8lZ-4yygs-G01 z(Ca9pl&Fvt?I$HGCpL=?m(~mV zJ+)nQq;x=#>(Fk|(b7vnZbO5kBn=f*t-C=xSQ;&;Wp_6iEKL-&v%5h%M4BS#M0YnB zBFz+J?P<^sl?nu9^>l-w(h@-{3K$zEtr2vvAOMC*n*>$p!dQ~DOHk7;0gxmeJgnw( zxb%grgY=x7=n>MT!)iW9O1BTISN4%o>mza|riV9-9x3e+bZg@5XsdKukbl~u=+V-T zg3LqLM5jo#kIEE2bWiknshgl-;U}Y0C5u8oMo*F^9aYyuGNmj*dEMP0Q_4T8Mm#1O=u#$9y0S5tKE= zBPK`sSWwQ8x-q%Zmx6``HIK=cZY$I?W{&jgnB7i{RQ9;~o;pvecwCKoo>WWLL3&Pf z%zVlFxEgh#)Q+eSUWGS|E|m5Qs+yV@vp_m0XhPcXm=C4fg2oRW8?#vQIw4c)(CIPD zrM`l0hR=yvDOnU+AG2B-N0jB{yUd_nD@_*EcNu;sH2Z`a=Q?Sgtb_ENPh-|gD^93! zZje4cp=Ni3v{$sSBe-Go2Fd57Ocf^{iz$)<1r?>9jrm9#An29$< z>6D-)!OvoLNIxo6CU%$f;-nhc9*KW$i>y}cUa9=&YGnJQYM-l-?UU*e6~eUOhSB?^ z^@4tw&>;2`X}_S1R730m=_^57hIEKMD0!TcY5S1Au}7pff`$i&#vYUW6iSLcAq^1J zG^Anl=h9DtdQHrXJte&obUt-v>}kpE3z;4dnIHS5GT(B&c2l{%Q{HUxfXj> zy7h&cv+tzGqP?@`hSA?iDW_%HIPq5OHR%IEwbCBOUYC{#YBuym?2poqf?5pK#@&|K z8JV`3-Q(^`l@;=eyC*dlba-XwxSyq#XKeRPH~2;J5xTAcJ>woo-Os3Z--lA*8C%qT zalcB@LWfa5l7^g7@Ai+RX~OcySX101X{AEZagU{s6dDxwRN6^&m-&qw5%)}L^rbDk zsd2wcz6v?O3u%B%PCbWDk9#R)%LI+<<;A^}J`*%QdSToj((hlYd(eMM4rgV`o$CO9 zN|lH*0r#L^NnU~+5**;Qw1ns`a}8kdMmlp=?a>?Qp=?Pr`Hl2~sL)B%WmVi8>Frsy z4{s!=bLyKZaF27gK5U2s-s+s1PYv%P>mWU6Yn%f&o>TLwH`=wG_JIdp%CC&{^L{arL?P z*UC8^*O0gVTJ`D0{S_UH_vTiaoSs-c;+yeInINw)FRmG%EvWr62WZZhf340X1OMb} zwSNZw6;UDhjjIuF;13k?if_rYFUVf}#^uGe;kt`9dN2Mx-c}(uXvcdg)GNL{k5DK$ z-iMD@C^Ehy-=NTd_|E)ug{<+u{GLMT@!h!NC8hQFp1hGvmEAIz$M@lVWO8~metWzh z4-%xAus7b1M+&MkVSBtkPY~2>!rpj)K1|SsK8ywMWI>PnXhH*cs-P#MV)?>v)hk0huX5Fv-7y{Gd7Z23e6euDRW)i0?#C56DDb`t#2fFO4h%_%}j_UIy|Xl~@=ImN#g99tm|fxEW^2zpz^**VLM1;y7jSVr)Limt6?B!7NQ9lz21`uFPijpo0~Aav%J0jf@v^~Ms@;?7vq289UyzCD$9Zr1Kd;+f_ z=ps>bK`r_PStjs~g8C8l73AD6%rb$8D->&)$Oj9m6ys}2=Q)B3I}Nf-;`0McxOTS7+*^+ zw+K4n?`z591vk|*C!a4Dy7ZinEVKDXH`Oy|j&e8TDPua$;d^Dv%5It4EpxfYE%}RKI67}jur3^WpdhIxX)6+R|;yBf5=k6iv&H$J#H!BJBc!#It@B)Dc}zTx%OqO zfX}}pTXxC`))w$pfU}OOrw~>CS*G^OuwF}|OsG4`4Oa2K zLifhc4Oa7LL5=IV!5Th9(Cd!AmbHAELLLe0_%Vg5C#>i2i;cVzHt+$z*ji|vP{dP( z?u}pTgpE8`P~&>76F%Y#m3r+HKIS_W@=e&ppDWZSVKX;8u#q`o3!kTuC1ESyASkxVF$mXSPo9u$^TF+FGcO*d^1G8~!IV0f+*9pq*Gb731|2X#qvzlm;Co%ZL^@C z=`64C$foOOI>+liQa>^O6*mZa-Em#QdG4psmV~eQY=!nET;OLEI+^edH$1kra53Q` z@ABBz!o`G3JXlcUdKVKe^B6&|JKjjR!pAH0DB)YaSE08FSNU^=DkOf#Bc6zB!AP%! zYkasu^%B457X=N;932Hz96hsNu@)^}+`YIJcR$?S-HSt^6p9yjmvRS(yA~+L<#6}n z?s`c7?f>q*NoJDF?rk&m4KoT8OOK{<@bUPKYb);?apJN}T5`e2afQL^qlBl?HGN^@WK_u0=p z$s-(-^SO|=SiiX^I;?kiJL4O4s*lEjHeZHp-x@D-${R*;+1ozQiT!Q&j|d##L0;WvM}i2(;QUEt71TDAxtE)D*4?DaWh6yTcSESlb*VTWx+6R6k=F z#M-nM9K9&@@MJxQsKkDpTaob4npnkVp(f?AHCd=6w`1DWKG{@2k%JofNiw&XZ#`Qz_35VnOP_24Bqvh*3?W|H!0j0JZcruRC=3zw=VHHf!)1FpOIO<1;n&k~# zE8{MP(4SOhJ!EC^x;oPYQrD*KJ{a;|HS)q+% zn5-EywoW@YS@F)Zi+IXkV%34tnZUCQXNpZCe_le6Qhw{JrU2KUrypthg z@SE=y)kNaojv#k9BzAo7h8Az8gws1F9`?Y{c(DP+t#d;Q8f7%V9}=W=J$Fr?H;X44OeM=^47I#k@5qPXB3ZJ6V9Jw_Y>EN9NOR7nDH;HJk2|| zX5*LeXG6yV z17{b>XPJKMcaM1<-rkG3>R=1zOnf|HNiPM+;6}byJV)iV%NDo6Z1#WE1Et}dN%`;% z&azj?@V!dr#-x@n{^hc@8h?{Q2FmEo%h;7r$*ZHy3N3N9-|EyICz3!^HNb{LaE!hU zg)$aBj}I9X!umHdv;pnkdAmi+Y;v}}{A#*}|HI{Xv#seWL!ckIJ9@2U%&FgLN?33h zYVZHjy+KqakwJY`9%@@EEHG5R9%@@Q=AmXDyy>s*5rT&PFz@HwtPuzh)YRg=r;byRfer)p;xa4Wy&v<;(6-z66#R>Z6+|ycT6)A=H z$F;(=#$adasWgcZDK70Y2(>ZHggmKo$r?--UDCrBwp~`n3En;%?ExXoe%2QuqV+4}WbJeU4bPGZoLM1|Zhfv~ zGZeKmd9yWKr*!JXJWso9Y`I5N4?5M5vYTnUZ=#I~fpd*dJeP%}O>~8pg;y3#qiDw( zc8mJT?@%U9r3f)y*ElG%ETEKYkr*nloL-JH&vxiceZR(dmYFSUE@Ts} zvT7{jhKPzDLA1+PDjHtne;7j*0TYJ|xg>=BaZeC8xx=VQQKE2x?1_KdXb?d6HD2uGSf+vcV^=>*&L5=^mq@b?p?I zW4^BPV^VN5TbPM9*bpk7*s|@i@1+e+O=FZy|B2oK)oA$Y;9CB(Qme8-E5I;yRkrL_ zX`gIX19W3;DtQJ~To!M<)An*MSb8jrr)FZUFVNv_t&~se6jOUJw6E$gM#jGtw{~C= zp!z1F#Y!-A$x1aKS7yxBh#62M$foI>T~fxPWAL?#_3WKaNtr!M>oQG+%FUbn>6FKn zi`K*7NfsRU@j+O6zj17-VUJZ zU?;4L7j0g9_FNCYg|4zR9l^>RCnJ*E>S3tJ%z1U0QUKHMw3@$KrzeHss%6+kHXWCL zi-{d^|0EALiyXJ-YZYD&o|xxrh2L^WbwcB!Xey8XWFPY95SgwuRED&~K|3q#myvtX zKIhQ2Mco6;YKgWgMUP%|mFk}V}kv)J0_w~)zVpX7oAEzAs~_YcwisEhJb zE5498g}xC>oM`V&Xd=blMz6Lc#kK~w?st0Yk;F~JV5dNg=9@@sUk%i+U43QjZ}t52 zpt2D~3`*D#*CsfPFx2mxC zt;GV2GVF4Yo1z>kr}Nv#kJ3mexLrB&gpAgQu-RFW^xv!3;_R_$fpUH&wCRE}G`_?! zVmwrNhN?w?Vu%wjB+oKd@2pg`c$Z|W+GBv7+m&uI`4`oLQdnA9-SC`h^s9WIXLG4U z3xawWv@He8hq8r2+QDdrdFAVQ8uW`ViA5*q<&SfeB4}Qz=XdTKZMmr3Z_Nz!Lk(0b znevBkblaZC|M$eYg#imL>G1pYIs&J&I4aKx8qcaN%FU2*K=fk_Au~dn4zBRl8Lttcn8f0 zidUfuz&YZC87%cK#x6udrBq5cWRGj65SIMx)5BeQMM2}cH}?Coq>tD(UrD;XLi9Ie zM^9;CuU+@nq(1=i!ga4x+J+$m={b@vmv;O%{ElJH4910q{EkFO;YfZqZ6g5v#_o#a7RW=sYHrXpK{u7;1+a!VfE^6}biS*8u(O{>;qlq;C zcbBFXvN*UL&*ZCfE_mkq=(GMf1Aso}xztafEROC4e;mrO zV}eN`$Fu)>HjW{KO{0+{X8`xsxLCI*;ty_Q2%FlYbkTz1+`3&;uDq6KqJ!%(SL(;$ zar@^cT6c6uO&c+fJXzsiJZ`t#dk4~?Rn*4kU-pir7R>#3|BfxD+l3xr{q-3Ok|qS@ zKd%cs;y!a?7+#MF1fTb&pvg*~m&G?hy5}Bz zlNBWQ0@-fI^bZd(fyjGo&A>(ey>}LF{O8=?-!Cg)UK#s-Kts~Nc2Y9 zs-u(1$x7pRt!`)wMc>nF9@V@rQujGX9TBBYvzs*jG*3sJmJvMUC;#^iMlEn0JDWjK zaeKqsm*@%XfEt^F(7caJWG1cGy*H;5$|4uy!eeV}mO%j`7x|>FiAvOy6ZmcRv>$cP zN9FHZLTqI%n##8`C+VUh`?O4LJQb+8F5J_Em3wh?nU0vSZ7gD0eA8r|JX3)W|0l^l z9i#d|aJ8#6@>EWy-*!ga8}JrX^wVYA#o6E9Gq5dw`;od^w0}`=To-i~AYW~i$aWQZ zDpF8HTI7ngF#hSyI(bb#jDgz!S5Q;1-Xg+ny>L+j%CT+C8*SBvaRS`+yKm1Jj!@eACMhqm`71k9lz44i`odc^`#eDCGupXmH3&) zU&x}PFaa|I?z13|?iMP{k89$hk*AJr7^&cru&48TT>0?&8q+XJjeoHI+eE8^U(hFJ zsJw}NJ(x%_~ zd=y9T0R&Q457qAytQIYqZ#Q2>9tb6;QH%s7X>SwTh-Uu0QD@AP;q)|e=vDvSS-n5f zQ>eJ({#&0`=2;EA`Ee3SQyQAP`SH&@VHJ+byKZXzANiC#fa&9--m5^3vzhYa$>vZqbMh{?aBkvV#K81baQFJ&G}LNAg_Tzh z(L@B5f8r|Gw^9o!e{5O)Swc&_^2d0ty4T!iXw@0pHqsOrr_M1Sl~z2O{Ro+i=dlUI z*x#|QfQfHqjVM!|dzZ-2%Bb0JD&%jyiP*-{BX6HKvTjj1e^MAtzo;PIg*q;+zcm(X zfpjR2o}je%ds(RdLnKI#P#V`M*{(xbR!DkJg51IaLVcO{G?sUfFADRoqt(dVlkcPB zxpz#ywEh&VO>T+*bieXgI1lYR9YI}!YIEik$vO!t`t{S(k@c3tHFmh1V z>J>eESL9&7)vTL+F;gAW0iWcI|07nslL|4%&OJu_Q=G6XU~R6wB-0osQPF@Cu@ftY zG(ZAD*okqp#B{MdGEM)OO8(jI;;JLlk7ZhNwJzp9xJ>>yHS{IG3Z-rVxa;vJ9oZLg z(cjyf&0LpqpT3oRyv$s`9UR-nR<#exi#t~Ay#A&-7m!3Dg8>$5PZCiGOP9aOnO8ni zn9BIzQ*$W8*WX}2*k<1`sFU$|q*}Hu@q{xhbv@dXM#$0tXJ7G_KET`nx5dT4bqZm+ zB_kp+a1cAgrgui>mWshwL1@=BF>naP!m55uF%?fLP7u)j!j2aia zP-5E~Fyz*$BNAqRn2!_7st_Tx!#KFCzBlk4l>VVTW3A!@|7LSQv_E6**ldg>mXNi8 z)&Amiplw|u(OI#(3{KTy>>S%C=gn#$VIiAma6s3$111-{oVY*e6L^atcI4bF4qO7= zm*QM9;lFvsp&6i{U)OZgl4JuhDeoN%e=4*Xu%j{2$IQ_2@i=fTVr#lNfoAY7F(Ea< zQQJJ1A`l<)DD`RiS-eC(bd}gFm9`OSMo2PU#_2$@+(SYt7c15!9T_A#g@|u*EFb;X zjEGy1)~nVx(|6oPLz&x9XpJq5wi=XrTk%w?zb#Us4}T{wiW^_wJfcU0FTbSR!xp=Y zk1t7FnNoO8`h}MH`_J%$`|-?m}ttAKQC4HtD`?2-@Orks*=qK~GE)5iZ&qa?E&PCdbYd zhmYbZ9Q`57l{e|RUT{=wNOa;;Di*8`-lzS93i0WqnjG4;20cnz6lu!ei6k`!qU zS%3nrp_BBN*z|!5D|XxmB8J7Ll9xr(gWMUr$oSGF$&B*FH-%x7M8yn{NviB0dkF#= z;YhCWW3<}c)UAlxvg34eW}nb1qsOTCEZVS*$a0l+ParYJSXUCVC}KFC$JC2ZUB!&2@aSJ|{{n3&lKcDjOO<(&;CG7fzC) z`i!RWsPBu?qNt(9?zjiUnC+YNb+krH_!ykoLn44uL+3Ocy82VYl9seeZKnHyPK3JH zc#>38R!3UC-9iT>&?+Wr(2Qm=JTw4I;`NB@W1r}gI*er%MltK8Xg}6wADe@nW`bTO zGghs{K*oFdv5U%>Jv?r*%A@3;cpz`NUm_Q!RQk7tU8(gI;FgEnXInFhT(mewOG^FH zAxZ%XmbCYE@fC51rniYE>PhUXmbb~8fL0KR6_D?j{>mOPG@!mQVt#3yvOQwTscmJ@ zusu>yYC7mQnZ!O9L2s=Z+P;PIDVTHkG*vQ9B*K#%-1thKPXvQnG%a5 z%#bu&%nebNMf0ki!lfg;lXBwKCq=l)r8eys4=j-%*MoMt)Rqh}0nekYFOSN$Uoy~k z$gfsyUBB+5PT9D~EvX+bfyvfbGP?zs7@o$pu2ZhFAu1@|sVKE* zum42?b7JW!Hj=a?Ol6$`w=qfSp%zQNzEuBg_bPa_-|B&F4B=BE!KMhMPjuGrS~Hnr zggOJXyk`Z$gJR+N0a5C9vodx=ipOuqKSGTT2lWL9+v0c*iz}2Y^AndnHx@lEgH>LR zjE+4nvB)Zx=eIr7rI`96EKQSdGIj|l;scd~rn#dk4eN;L*Vu-j}<;dUFObGEMbsR;}YIn z`!_K!qb^3YFNofQLxwOg`dKlLGq@eKOkemOT5#L16hz&aBnZqN(V-+7ycZh`63U=g zVsDu_BrfZtvWiV;T%^GazpL!zc4+@W)^1PsdBfEVgV+Q6Q(7-e2X4qg9?XWf@Hn<8{~6(FR=HG* z=Hp~e8FvUS4El|BDC&(Dngb?aW4>t=NliyoX($?F_=;Ou+D7}3&2l(5Q(y&MqHRRj zq4_sXs@hmn6VSQKMR?_A)NDpky#gFWAt4!(KuQE*+yVw+jBvhQ%kEL(-VWtcD-qmf~sY%Q?tA?v#!=O~S3HVg<3R?;T{1e&Sg!q8!Q)S~`8g>+x)WA~1jOdWA(nmF~`xb=m*~!3C9h-xe z@T|_6J01Tbs~A~T?-IBL*A7=!kX8ZF)3It_W+BTn)RWs2EQX&4d1c+JWjd$F zonVlH9QYoD4^bFvoD^b8~nno{FDf!wi1zqn;1PnqlUMXlZb>VI8DsN9|ynVtA zN3=@BXt?I>LBgV&gGO75zANmF13l1DYgy_kRAh%&#dBb(yu6tv&!MPpV)nyvPo^WW z*&k3s3q;2OSK zAmN5bksGBhPZm7>wW37x!GrSXU}jN3V{V#PbOr;kK&E+vW<~@Oyk5-;E8z82m@T^QN4SFk)nBkcwU2_q-A_y26)=r5Z$_Ne`JN>@3Jz$gzwDDOw;QJ}8y|=x7=x zTuJ3@G`28}KN+A_zbp>sFKjd9D$c*HI(n!YrxoupBecZRwl}zx5%Ir5JDoD=qqz*< zu1_Znk0coxlth71>u4GH>@A>yMK-Oj$+F71W3R_~X3mPjQD+``h!6Q77>8TL7&o>A zGX>x^Yl%ET#Y_yp!_wPDjl6Uo|1uKbp(+t0@KTeMDHmAOQyn2P(-~gwq|qlXC9}?| zm2+xD^t}PPU6n`n5Ot0e&BQ*bRA}+CnTclzPXvou@kf>+6G>|GR-=6i4W!IlxzT>+ zF2f&5z-}7qk|B%SI1kYb8O%V2}OXo^H+1t0uz;y)oBCYCwshZ`C~WTgS(4OyWgp~B5`fsi7v z(TCZ2WuNaBl~&KI-%j7p;ycD6A$SfzyZ;T5r(z)mWkdAYyrK|-o?!tQ8P@PrkLYMa zbd?;420PX{yw!6qQg@Q=u%YMdZ{2Va&f)(N~?h~w3&j>rMItl za@n|i zNu>-sYO#PasU+GQwd4}<907y2T0Mwbf6KV!_xa#jx8e?lVnh`BDWqKjytTCin7OWM z?K@j5p8QZ6Un@|ChQ8EuxIJjW#2G_7G#>3uHi@wmrxJJz)6rF(H7#Vm8A`#C`KjL1 zr}V8-cXzwixyF8vv0^(O+$|kdkN$>A)h9k0aB!xTw5w*Of4#yg{b`*v&*F&%4Y5uN z4Qq=?xz^QCV*8@;*qfLJ?1H2@yykLy;k;=trE<1uJ>~Qw(e&R{S+zVIw@%o@Jv`wm zJ$noO5L}V3ae`J{fpvIT>H@rMn1^|qG^ho)=~_^u` z7Kp$yQGMI|41R?Gfzf0~;VGUthAXrLsV2Vz9Jy1I|15JcLUq(t$~RJ20~-p>Vbn{Oa1VqO$bEDO&tjSHhY_8aBzX04`q zr>12Os7&KnZGTHKSqj2cwXoEsY?V3>Mb)1jxx7^ekZFrG4$vSw$!^VFzj}FjXw5 z&l7*vmBaw1%<;!yhRM;?nrLj)9?x|psI+{3{}SpJch!B;wceQWoN zxU`V&(geY;NwU+Ca$JYeFE(XQGSD$47SIlbYuNZzUmccO5*_|8cHILc#1CWi;lXYez1ZQsPvqtD{RpUc3 zGMPThtef~>5y2Q}=W%c+O_{Odl>$Z)T4A`#e!C_r$epGyrmN@ zn)O>ndl9Yvqw+dFE7LdqHL}WnAcTs!{Rb=qf+Aqo{zH+JO0bl&O~{`*X@{GvgQaYM zMmLN(Rb+w?R9o15Bl50UMVO7dfujwDxIyKGKB|*}m=%W@yGZL+Qj?Px%dhM#O!C~_ zSc*^XPLgGN%j95nqTg%7#kBz?ndvUK6RXXU?r9tO+J@}oI$@v*%GjNZ*Ad;G*um})XF}IM5%w;xCDS4{i?DkUPX(D=9aB$&C%!R7! zU|aDa;^Rr!NKN!nGjX%IFt!2D`g;sX+!}#IAdUKn#h?c*yio5`k<1DsPr2UklEfn} zDtnq(*!16TpuU7ghGEw53K4Y$POFnJ=4dKMcF&6Ub>y*Zgi>=qu`+bHYWt}=vRVguSvXxi*s*)vq;&aIp z`6QG_L;HQmgZ^Cbpa0e6g|x&b+~Dyz{CJu#Zh_k2056)0`?Pwm?BEh1>*5>z$D*Rx zOVi}drwLfB#+>TS%YNtkZb8H6NimY0F4jIIN*CN24%|gRsdqT;;q&7Pm|HBx$7e?c z_44p7Ft`vLezn&*A8pYu{YziU{w{XW>gvRNn@09sm&o@tm!w0qcr?i)U?Led5LPg( ztY-S&zj;wrB(X{!^j9{DGa!eZ1iw*3?7*C}+gf-F}}0 za!1$S;whcFA)pR7H4?02`Rc%}){ax^jd`f^dUi|#o9^E+93lrRTj}MjWUx_#CBnWMyWTI0i^?D z?@_A@l|i_|(T zB+}9fUKuWq_9sgFk--I}u5Q5}RHN6PS+98w8vv#xYCVctW?+2Opw4u2e8KprT+Z1V zWazLA{2j-vTBI}jsPK!p7aKtUfB}y70G4z$HwqD$$Br>@UczR;QKit zj4LLV{BsVwhil-qV3?GQsQ2>S&oq(tIftq`j{u)E`iGpXA?Gi-NXa#ImozO;x-M^M zE}xXzkbHJ%C*xcOZ`JXL{UD$&G?q}@!JNUIo+=<(A{*4E?dvaSR~J%C_-s_u^cvbM zrR!bv{X~NT6rNl9S?D^DQ1B-iyptq%5i@W-mibE>Ty(w$g$!`T^Uc=~uHF*~c2J1G z^$$;|d;Yt=v>obx-SZ#qaRQg2)2dPQ8)0?IRv8fwVN)1wTHoaZaB`n?G`F&~#VV~lTqbJ&Q)aDI*z7$ig zMPHa2&g;;qTjpK=f&n+7ZK&6Seva`sGtm1fc}IYOt)Q+&it|ixu(O5g^@Zxn#0^T$ z+HW{;uC)XLuJEFSfLyr3rc%2Z?_Xk&9IK5;mWwp3qb{xue`-p5|RL zM(csFGo$35wNzN!B%h}ae?Ra6zn{*7GGcS#Hb(=C6E@GjB*azuW>V5C(j;wV`BqYm z@FBiyMX|o{QFl$}{ZA`AB*Rmn=E4_b3KC|H`=tq#+rN8c68N5|VF_z*>kpEFppKer#*|Ka%{2P&-XG}`V?=Yj)1msG5n&V&egq0a zQN?PoI~}=6Kb9UHL)N6TUtw=;;GL;uscEUn z67$+hH@q{jLNXNvt@80=hCxqlvGhEaX_i2OKyk?3p%WYo8lSncE1A9&;1#&W%UnDB zbYPJgezZ5uZoO1&DBk{;`B}2EE`Z#aGU$w8;Wh-{yw#3R%E}L)#FRAI&2(YPu>E z$xxlH*iQOs?Pu>c6^Jf8|6rV}L-(xUWK8iIWGvA>IHOjd?45p4<4q`{48dw!^=tsK z!9}i(GRYlT4y?Hvr;owSBU+`)a`{OI6)s}W5g=DDlzsn9nmTqL1D|uQkG}D~ zWjH|Da|%LI>=H!buFHC{06T!KdGm_!_snJUAc?6ehPfY=+lQ_}&n#q&(*ZX`f~t`4 zto3~aF6XHnFvZZdv`5@+E2VnPt*|lcwD}1VydD(aNNE2Ub1C4JzK_k-dcZaf<&xR2 znn>lsKMX2yv=%IHSM*4y^)GLiZ6Db#>QadqX35aWay7uzQW zGvT1@g)f&zv+@Pgm}#2j*|<%I-eW~%=^$D+_sJTA`?uu>#V z>7S$s%?|e)-#km{S}c{b;EGNRRQKFjh)oby^)wrZPViTxK=mpsRPM#s`%(ssLHFq| zzEZ`y-ajB;itzbmvSzQnf7`-eL2RP$-kKh0X5{getEh30lSuVWwRMojgljZ%S*$dqsmIcGC&c`D_dV7)8&_TpDRqT)vRLEHv8s!k(3 zZ77+ImImvgqf4A@I>TB<&T6?6aR-sml=ig!Q9QFE5>?cbUw9UZ_6FJaBGZ35ZA1iABchGo})AV6Ds)tQP5L2hG*N{_e3hA;HB+*|ZXI|6&$g53X)buhV9 zwa|sUn?8T~g452O{#R>Q)%>J;MvnN0PgJwMM2)BL! z=U;Q!C1}nB)FY?(kXhk^?g&Od;?`fyu=#&A{;lBuSK}tDvGJpKq=2x@eHgbo>ACLy z$1E^Y;{UbSuW*nh-^W+pK3`OKS-s>;+I zOG{<`ESYL=gY=t^!UWw-HJA;EBDfp9G)i*6_hc5^avaHW2#go&--Ws(a-X+mczHjb z_N#v{kw>jpb=_iwpiZObugR+R--OBUJ%zuQ^X!nz5Q0#2}TIul@Gl?;I2J9*~FwGb}TVo?gcWeP1R?EP8$& z`yYr2h+y#D0`>o?lrR4K&XO`YerUusk~32MnNH*K`v=Q6-2Y9xaud9l%6t!Um9-}7 z$!N_GeMzxKw?*Z=d1e}U->qeFC_+8*ekf<4VcIc_GaA~{a8UBWlIKS$A(OjS8fSRz zKm)&RGH0559}lm~S9RF;st6Z{4EvkQ<`1z3^rhtRD$5xlwua4I@u={N;I4+qHG3Qs z8u8M{Dey(=?zY06;xpDWHz&oq)ZeKL7)8MsqS0^h6I~$L z3-)XFn8gS7YgcmIdx~rJ^x8+nXTx8l|KOmVnABeaZWYslFG?9q|6t}4`$U&DXi#_G z^gqoze0XzC)C2OfVfW8x01g#16AwNc_A#blY`;|6>{`@ayJCbA^CUbrr|H|!J+bOT z27xHDruXxl%Rr3Py8mu)YVS*>?Q4=HjqjZit+LiANLn zGH2?iQw5>-2y{5BExoP`$WU+oA?f!qZl-63x*Ij@JPHI4<1`c8vy~Sdo-tqA1ky3Yb86#TUfNtMHcR-Sqr70> z_jl4|w?oSC)Ws-!u&T?H>vt)d#GEIzq%qEijJyYYT4OUUZ|c^2AY<`8qv#nmsfvT0 zQ~N%J&e-}jNsKg{B&mvfp2P2fD9ME@Pg#2DAb{h5fV=+fXw))D3)fzSRJpB()(i$0 zk+X5Z-5)mIRn$B`X1l6o^1Vg*VTYXe+fW{0WrEhfSS8m1zYT}FcXx3|}+Tgl3wC9?8alX4g301;t8>thxu zsCZQ`bBT5A2f`Q4P5xpP@x=I#Rqmo9$Ry}2xvh>&C~8}fYh)ppEX2Mi^1~07qtGEo z-?Unk@;)tY;gN-8Sa*YqRaz~f;rD~nIwV>CCPhJ)^VgQHx3UZwUV#e{xyAbYl&$QK z#Ex^ecJ+UO;L^RZHqUU;mU>G;DGGSK=KoHtJHzA@R3z3xqEX{>< zmx%~_Cv(^RP^_aF6!^x7%PB(Oxuwu;nCN}|@}nfhYdu|`{T}qDVKO*J@-vj;+MGbB zHGd|;eCX?OMbJdVq7z7LzsKpeGPT!7zkYH2*^D6zrgF%BB5g8MzxXN^OO=OP3yNILd;urE|eGSb*n$B>ID4I}+GoZ!*d{G5ZL})@uCt?549}EBeAB`sa z`hQg4wL(8Hj0~pIm$jq6zkwYRkB=^{zt8}nFf>+K~okCTj65) z@?wbiHY4@CdS1^(4dMQV_;E!s%}1nj$p2OZSTb3+LKwk7-oss}"!v2;XM5>%ApMeYC|9GG{!_AFA;F2c5diH>(ybSH(1w z77KgK20%6<5#4c_qbk3$OtaVo!dCgvy+^gY$2lTxE!eR_eQoBfKlXChtI(Kr@zo>9 ziF_cOoKWLTv@5RV5x@ zlTq(lpO&!jNXyqs3WO_bnn$niJf>p|t`E zndzFJm>))LXCG^d=|OpqNyH+3Vs@UW)|~k!C^lDZN)UgEx@XTKiC-@}X}pr|f6l#op;Bz>6K=K>3qr;J-iRF2XiKh1uamqSpFsZBSCUHMgCdxjd5i9;>KppFY_!C}j{Uqb|BV~C&2nyJir7XX zjT^YLIh>DvZHFL_FS&W}Ze-#%n&5VmWBluA?Ox_1#Y68*7_1y?Hwq-40hn~&Wuf~T+rN2D&7k* zth^yR#?5UJ&uvis2KL;w-~jyQV9y;K&jc?YHaV0VuRXr~Vt4~n)rtg})=a1nIMi!7 zw0yR^?PLhp{Nobvi9x1lzCsCfFqAvpqj)m(TosYJlw97o1m)3GWUmS!tba%w zf@2TGt^D86|D=KJW)#A+=b?f8=^$h5fmMlUw4uN+J+{1@6@Wwce zD=FIy=z05LRU>@B>;*HK7!A?P1VYEMrz~JQ=V;~cb*-%6F zCFs+(3AW`l+8{j#!4|DUYzfe${Ka z#HXGcPqhBHq8&di$!PTeXgpOmlSE0$$e)~P4U z=YtgVdjqquSiG?3&Hk&Hk;%!GAC`iILt|R?(agZR9)Hj%*Kk};Uq?2llzdaY5tsSrjc7P#neTd9PrrD_4Jfpm# z1dRPQu*;v%Y4RSOrVej?$fvoxf}qwb3E-)mv-Gyu@?N}?dG;IIsx2=2v3RA*4OGs= z7gkLlcWXM={;^UE_>|pM+EI-JhXg8-(^cH}bVHT8KK1i%Ot~5j zG;T}@RN1h$czzvWLQH3KKRx;dW-(e7bUR>fb`WXt%q7uAWLw>)$5&trgUq>c$NT~t z8iBA|JYx*i5d*qeUAz(-2z!gyHlt9wDqX>-f15Jug?a@^Aaz@siGv|@*Y9D43vfvr*M(Fxte@+Q{-~qn_8N+Yx zQXHH9n_JgBg~)qWH)cXrTOUcx`b2i}oPzQ1QUW3x6(B1!f-d*6dhX*boFr6h9&8pg zy#d|Y&fk35TXM6_I=VL4oI>{5TaL0R;b^C)^pP(i0j+O~y0(#>!uP9N{$x|Z9bI;9 zB45IlR?#Qd-9noq_nz9CQZF#>rU@eN!bzHuHaq@&u9+J%N`R-aEWW;Y*<@}K|LK4F z1Qhe{HQ;YP!+3!eu)n+hDdN{%twD6}o?Tt!-%9_@zWVTW|EwhyAW&4bAkLixxM(p| zEXS`PCgR!T7tO>Q@j{41WuSlEPpzlgSXG}#od+8Fs)cV@_O#e4moK%DOoI=~UHu#o z$eoktPA0YRU4J!9?b$I8&0BoB6w8jjXIch(1@NW*88`cMA(TCQK`y3rbn8_1>#SGn zkB8W7oX;W^z-Na3i5W1Dcc3ueD#>DzHCGM8bJ2{TAyc!bxOv@Q{xy3r{3-wYgr%hX zc;2ID%m;k@8bO1Lbe3P;I!_U(ZWCX1j}cMG?sJ8@yZJtEu(?e{H7{deHtofqAfp)j zo{+np2dqJHNRJ2es}WIzF8Vt*%m_QTMBQpgOzW{oJ$F2ALqkRU?hrN#NKAcRkskL{ z3%6X8hW48Q^SI}sY{XO zE%Tcvk{#H)xRRS0dY|=={HK+90YBm*1_-lVi}l8nJ!g@@2qc1K$)5M=8Rk@L#ceCq zHt~MRv7qfLN3A38`)fgW6ez4LcF|=H^Jfry@yEA184@cE@j4p%QU$<1p{LhJb1_}) zIDu_7;Gvi=aI^|7_Lm#>8jX(oalHU*7e``PyufP~FLbeoH@#Q8jdGN0dY$k$y(A+P zA5kBB^ml#DGlqdLzL8XNx(+&ct1VT2=GIx;n(29^;H@8ZS96 zK4EAaZ(wf_&n|jT$r_cL?$XvOnv?Fri|as{Nw_R+(2nbZj8plV!bWkc!}^lP`W!j4 zKa=a>btWXE?p`tTe@wjxR8z|rH~PQVf{KEof=E*l0clDVX(CPOML@bp@6rj7a209N zq<2E^MS2ZUK}zTyLWv5Yg(Q-Y00~Ln@%!Fe>#cQ8rk!bf|7Pz!GbeLSb&1CwlIL>$ zPJ**De_W9g`w1+YZ@O6yhasdVTCaX!GSR5*B7Zvm+ClJbGnpwB8B%Z7u3cNEafO186-2xzk54E7g$ldLa*0l(vu0#i~l_8)z^|SfagSQ zQEk)RpEz~*vkv8wdiYO6MCzZZgxNB8k5XgHVuzJ|x<@C6=4AFQr})DEsj!=8)rz0r@wS-Kw~9d@^cc;taHsyOxKS`5aP0$Vkfd@*|=$=eGuS^3uQ)*ABky+nqVbDj!e(edFU#;8bx_RX7lj12{cJ>bNuDZ%wC zvl4d|#-PW~bgwNp>OoHwPo9*YFh@MAplvmt^6RRWCTGJB$ctqmF5}wvA9c;guACLC z;08_?iGF?;AjvuBjCY#6%Zs`f@Q!*O52BJ5%dH>er93(}>yn(}Z24lSjLXKs`J=W) znCsR@%bda-(EFad({|3P?}iR*DZSMxSNT1lV~J6wlAyf23-j6cnwiJ+qB@$*JiQ;Z z_6g5aPI;(~N0EWQZdg?@SHyY_u3BFhNZ$bcI4$@bBODgs(E|Iz2mC+MLscxwfiNuX zuk%ZwVv?6SUVoN)`z6)UTP)%8Zt=3})SJD`FtK5yM{hq3dw<>%^32kjknOnX{)kos z1n9&d znH?m5ThTh|tuAKemSX4yboSGSmwA<$CBDExb#paWKO3VFfdQp{u%-9#~{XS<3 z^R`s^rBspd+PoS5=jQD1rNCJV^Bhk~bd}A>KhODf>3e~*=P!`6H3z#>c|TmQ5N^U$ zfG()KMc9xBcco4BBL6BcR>_qqc7ot zf3>-Heql~S-?Z<1M{hXgsA;|Sy)5iECeq=(5Aww~Pk~ci0eiaA&X#NOS72GR;|gyF zpIG`z0=LM13#Yu7i*Y}BQ{`(WQcYUkYE z`yO@sj^4*V7Qk-ci*LIrPbTSzdSk7N)ya1-uEm?zjyd`FL1!U@7fgZelmIiVU*a{S z%3y8w61s-{se|9W;4b%b9YgQ(ZExL6bPVd+i;_>i`$6@!qtwiuy6^l7v)99Jo!z>a zpnKI)@qNn04`~1;n6IJuRfXC)-EOq6BuHOk`ZK(D-eWf5`7e`~E2){Hp4TpdA9hX& zX&iH2?+OpI{PgapaNYPTRZ}JpDbsf~X$xEH(rHt`^nZE1Rl3-Kr*8c6w-%NK=jca0 z`t%APB=_BFeapPcS;?rer^BHQpIra|D^{cpSr?CKj;>axM*$VnDU#C}g zg_rnILDn+q&jk-Dw@9s|KNFgxH~W@cyZ&WdZasM(?@~0*(bqA}eqqh4_1yP2Tw(@) zTBiwT`MA7_A5l3sy9}q6UE@DGa(SgZLTOrwiuUIwJq{6NmtA%}`5shvknoM)uBv%D z^FpQPM3;J}cd6J-`j_dN9?3qe2issBtvdG)^ z9pkZWUaeQ2A2V0kM}BrK^|~?R)tdOcFR}gQYp?4*X@<_F zUhF~e{{mYrp4gXqU7J|W_m6)!q`C4?v02``_761C?4ES^#lG|ldUhYo9zN;)=%0DX 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 a|7S;Kxo^<_Ap!mMf`2vdYvfk|_P+pnBITa| From 386cf8364a739ea92e58a5bb279706190cbb2fbf Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Sun, 21 Dec 2025 19:33:48 +0100 Subject: [PATCH 31/36] update README.md --- Janus/README.md | 686 +++++++----------------------------------------- 1 file changed, 90 insertions(+), 596 deletions(-) diff --git a/Janus/README.md b/Janus/README.md index 7817fe3..9b2acc3 100644 --- a/Janus/README.md +++ b/Janus/README.md @@ -1,22 +1,20 @@ -![Logo](https://raw.githubusercontent.com/PaulBraetz/RhoMicro.CodeAnalysis/master/UnionsGenerator/ReadmeLogo.svg) +![Logo](https://raw.githubusercontent.com/PaulBraetz/RhoMicro.CodeAnalysis/master/Janus/ReadmeLogo.svg) -# UnionsGenerator +# Janus +This is a source generator for generating union types. Read about union types here: https://en.wikipedia.org/wiki/Union_type ## Licensing This source code generator is licensed to you under the MPL-2.0. -The code generated by the tool, is however not subject to this license, but rather the licensing scheme of whatever project it is being used in. ## Features - generate rich examination and conversion api -- automatic relation type detection (congruency, superset, subset, intersection) - generate conversion operators - generate meaningful api names like `myUnion.IsResult` or `MyUnion.CreateFromResult(result)` -- generate the most efficient impementation for your usecase and optimize against boxing or size constraints -- group representable types and use members like `myUnion.IsNumber` +- group variants and use members like `myUnion.IsNumber` - use `System.Text.Json` serialization ## Installation @@ -24,19 +22,14 @@ The code generated by the tool, is however not subject to this license, but rath Package Reference: ``` - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - + ``` CLI: ``` -dotnet add package RhoMicro.CodeAnalysis.UnionsGenerator +dotnet add package RhoMicro.CodeAnalysis.Janus ``` -*Note: The source code generator will generate netstandard2.0 compliant code that potentially uses newer language features. It is expected that consumers enable the `11.0` flag. This could change in the future to support a wider number of language versions, however the netstandard2.0 constraint will remain. The generator generates members (e.g.: [NotNullWhenAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.codeanalysis.notnullwhenattribute?view=netstandard-2.1) ) that rely on special types not required by netstandard2.0, therefore under some circumstances (i.e. in analyzers) a polyfill (see [PolySharp](https://www.nuget.org/packages/PolySharp/#readme-body-tab)) is required.* - ## How To Use Annotate your union type with the `UnionType` attribute: @@ -56,7 +49,7 @@ u = false; //CS0029 Cannot implicitly convert type 'bool' to 'Union' #### General Usage -Use `UnionTypeAttribute` to add `T0` to the list of representable types: +Use `UnionTypeAttribute` to add `T0` to the list of variants: ```cs [UnionType] [UnionType] @@ -67,7 +60,7 @@ Usage: IntOrString u = "Hello, World!"; //implicitly converted u = 32; //implicitly converted ``` -Use `UnionTypeAttribute` on type parameters to add the targeted type parameter to the list of representable types: +Use `UnionTypeAttribute` on type parameters to add the targeted type parameter to the list of variants: ```cs partial struct GenericUnion<[UnionType] T0, [UnionType] T1>; ``` @@ -78,12 +71,12 @@ u = GenericUnion.CreateFromT0(32); ``` *Note: due to compiler restrictions no conversions from or to generic type parameters are generated. Using factory methods is an alternative way of creating union instances.* -- `Alias` +- `Name` -Define aliae for generated members using `Alias` , e.g.: +Define names for generated members using `Name` , e.g.: ```cs -[UnionType>(Alias = "MultipleNames")] -[UnionType(Alias = "SingleName")] +[UnionType>(Name = "MultipleNames")] +[UnionType(Name = "SingleName")] partial struct Names; ``` Usage: @@ -98,116 +91,34 @@ if(n.IsSingleName) } ``` -#### `Options` - -Define miscellaneous behaviour for the represented type using `Options`. - -- `ImplicitConversionIfSolitary` - -Instructs the generator to emit an implicit conversion to the representable type if it is the only one. -In effect, this option will enable the union type to act as an alias wrapper for the representable type. - -This option is enabled by default. +- `Description` +Provide a description for the variant: ```cs -[UnionType(Options = UnionTypeOptions.ImplicitConversionIfSolitary)] -partial struct Int32Alias; +[UnionType(Description = "This variant is used for integer values.")] +partial struct Result ``` -Usage: -```cs -var i = 32; -Int32Alias u = i; -i = u; -``` - -- `Nullable` -Instructs the generator to treat the representable reference type -as nullable, allowing for `null` arguments in factories, conversions etc. +- `IsNullable` +Specify the nullability of a reference type variant: ```cs -[UnionType(Options = UnionTypeOptions.Nullable)] -[UnionType>] +NullableStringUnion foo1 = "value"; +string value1 = foo1.CastToString; // CS8600 Converting null literal or possible null value to non-nullable type. + +NonNullableStringUnion foo2 = "value"; +string value2 = foo2.CastToString; // no nullability warning + +[UnionType(IsNullable = true)] partial struct NullableStringUnion; + +[UnionType] // implicitly non-nullable +partial struct NonNullableStringUnion; ``` -Usage: -```cs -NullableStringUnion u = (String?)null; -u = new List(); -u = "Nonnull String"; -u = (List?)null; //CS8604 - Possible null reference argument for parameter. -``` -#### `Storage` - -Optimize the generated storage implementation for the representable type against boxing or size constraints using `Storage`. - -- `Auto` -> The generator will automatically decide on a storage strategy. -> -> If the representable type is known to be a value type, -> this will store values of that type inside a shared value type container. -> Boxing will not occur. -> -> If the representable type is known to be a reference type, -> this will store values of that type inside a shared reference type container. -> -> If the representable type is neither known to be a reference type -> nor a value type, this option will cause values of that type to -> be stored inside a shared reference type container. -> If the representable type is a generic type parameter, -> boxing will occur for value type arguments to that parameter. - -- `Reference` -> The generator will always store values of the representable type -> inside a shared reference type container. -> -> If the representable type is known to be a value type, -> boxing will occur. -> -> If the representable type is a generic type parameter, -> boxing will occur for value type arguments to that parameter. - -- `Value` -> The generator will attempt to store values of the representable type -> inside a value type container. -> -> If the representable type is known to be a value type, -> this will store values of that type inside a shared value type container. -> Boxing will not occur. -> -> If the representable type is known to be a reference type, -> this will store values of that type inside a shared reference type container. -> Boxing will not occur. -> -> If the representable type is neither known to be a reference type -> nor a value type, this option will cause values of that type to -> be stored inside a shared value type container. -> If the representable type is a generic type parameter, -> an exception of type TypeLoadException will occur for -> reference type arguments to that parameter. - -- `Field` -> The generator will attempt to store values of the representable type -> inside a dedicated container for that type. -> -> If the representable type is known to be a value type, -> this will store values of that type inside a dedicated -> value type container. -> Boxing will not occur. -> -> If the representable type is known to be a reference type, -> this will store values of that type inside a -> dedicated reference type container. -> -> If the representable type is neither known to be a reference type -> nor a value type, this option will cause values of that type to -> be stored inside a dedicated strongly typed container. -> Boxing will not occur. - -#### `Groups` - -Group representable types into categories by assigning `Groups`: +- `Groups` + +Group variants into categories by assigning `Groups`: ```cs [UnionType(Groups = ["Number"])] [UnionType(Groups = ["Text"])] @@ -216,21 +127,21 @@ partial struct GroupedUnion; Usage: ```cs GroupedUnion u = "Hello, World!"; -if(u.IsNumberGroup) +if(u.Variant.Groups.ContainsNumber) { Assert.Fail("Expected union to be text."); } -if(!u.IsTextGroup) +if(!u.Variant.Groups.ContainsText) { Assert.Fail("Expected union to be text."); } u = 32f; -if(!u.IsNumberGroup) +if(!u.Variant.Groups.ContainsNumber) { Assert.Fail("Expected union to be number."); } -if(u.IsTextGroup) +if(u.Variant.Groups.ContainsText) { Assert.Fail("Expected union to be number."); } @@ -238,180 +149,80 @@ if(u.IsTextGroup) ### `UnionTypeAttribute` -The generic `UnionTypeAttribute` types allow to define multiple representable types inline. Except for `Alias`, they support all of the features that `UnionTypeAttribute` and `UnionTypeAttribute` provide. Any such features will be applied to all representable types listed in the type arguments list. - -For sample usage, see [Groups](#groups). - +The generic `UnionTypeAttribute` types allow to define multiple variants inline: +```cs +[UnionType] +partial struct Union; +``` ### `UnionTypeSettingsAttribute` -Use the `UnionTypeSettingsAttribute` to supply additional instructions to the generator. The attribute may be applied to either an assembly or a union type. When targeting a union type, it defines settings specific to that type. If, however, the attribute is annotating an assembly, it supplies the default settings for every union type in that assembly. +Use the `UnionTypeSettingsAttribute` to supply additional instructions to the generator. +The attribute may be applied to either an assembly or a union type. +When targeting a union type, it defines settings specific to that type. +If, however, the attribute is annotating an assembly, it supplies the default settings for every union type in that assembly. Settings inheritance is therefore ordered like so: -- default settings are applied (see [UnionTypeSettingsAttribute](https://raw.githubusercontent.com/PaulBraetz/RhoMicro.CodeAnalysis/master/UnionsGenerator/Attributes/UnionTypeSettingsAttribute.cs)) -- if settings could be located on assembly, settings defined therein are applied and override default settings -- if settings could be located on union type, settings defined therein are applied and override default or assembly settings - -#### `ConstructorAccessibility` - -Define the accessibility of generated constructors: - -- `PublicIfInconvertible` ->Generated constructors should always be private, unless ->no conversion operators are generated for the type they ->accept. This would be the case for interface types or ->supertypes of the target union. - -- `Private` -> Generated constructors should always be private. - -- `Public` -> Generated constructors should always be public. - -#### `DiagnosticsLevel` - -Define the reporting of diagnostics: - -- `Info` -> Instructs the analyzer to report info diagnostics. - -- `Warning` -> Instructs the analyzer to report warning diagnostics. - -- `Error` -> Instructs the analyzer to report error diagnostics. - -- `All` -> Instructs the analyzer to report all diagnostics. +- default settings are applied (see [UnionTypeSettingsAttribute](https://raw.githubusercontent.com/PaulBraetz/RhoMicro.CodeAnalysis/master/Janus/Attributes/UnionTypeSettingsAttribute.cs)) +- if settings could be located on assembly, settings defined therein are applied and override inherited settings +- if settings could be located on union type, settings defined therein are applied and override inherited or assembly settings #### `ToStringSetting` Define how implementations of `ToString` should be generated: +- `Inherit` +> Inherits the setting. This is the default value. +> - If the target is a type, it will inherit the setting from its containing assembly. +> - If the target is an assembly, the `Detailed` setting will be used. + - `Detailed` > The generator will emit an implementation that returns detailed information, including: > - the name of the union type -> - a list of types representable by the union type -> - an indication of which type is being represented by the instance +> - the set of variants +> - an indication of which variant is being represented by the instance > - the value currently being represented by the instance - `None` -> The generator will not generate an implementation of ToString. +> The generator will not generate an implementation of `ToString`. - `Simple` > The generator will generate an implementation that returns the result of -> calling ToString on the currently represented value. - -#### `Layout` - -Generate a layout attribute for size optimization: - -- `Small` -> Generate an annotation optimized for size. - -- `Auto` -> Do not generate any annotations. - -#### Identifiers - -Define various identifiers used in the generated implementation: -- `TypeDeclarationPreface` -> A raw code preface to prepend before the generated type declaration. -- `GenericTValueName` -> The name of the generic parameter for generic Is, As and factory methods. -> Set this property in order to avoid name collisions with generic union type parameters -- `TryConvertTypeName` -> The name of the generic parameter for the TryConvert method. -> Set this property in order to avoid name collisions with generic union type parameters -- `MatchTypeName` -> The name of the generic parameter for the Match method. -> Set this property in order to avoid name collisions with generic union type parameters -- `TagTypeName` -> The name to use for the discriminating tag type. -- `ValueTypeContainerTypeName` -> The name to use for the container type containing value types. -- `ValueTypeContainerName` -> The name to use for the field containing value types. -- `ReferenceTypeContainerName` -> The name to use for the field containing reference types. -- `TagFieldName` -> The name to use for the field containing the discriminating tag. -- `TagNoneName` -> The name to use for the default (uninitialized) tag value. -- `JsonConverterTypeName` -> The name of the generated json converter type. - -### `RelationAttribute` - -This attribute defines a relation between the targeted union type the supplied type. The following relations are available: -- `Disjunct` -- `Congruent` -- `Superset` -- `Subset` -- `Intersection` - -The generator will automatically detect the relation between two union types. The only requirement is for one of the two types to be annotated with the `RelationAttribute`: -```cs -[UnionType] -[Relation] -readonly partial struct Union; +> calling `ToString` on the currently represented value. -[UnionType] -sealed partial class CongruentUnion; +#### `EqualityOperatorsSetting` -[UnionType] -partial class SubsetUnion; +Define if equality operators should be generated: -[UnionType] -partial struct SupersetUnion; +- `Inherit` +> Inherits the setting. This is the default value. +> - If the target is a type, it will inherit the setting from its containing assembly. +> - If the target is an assembly, the `EmitOperatorsIfValueType` setting will be used. -[UnionType>] -partial class IntersectionUnion; -``` +- `EmitOperatorsIfValueType` +> Equality operators will be emitted only if the target union type is a value type. + +- `EmitOperators` +> Equality operators will be emitted. + +- `OmitOperators` +> Equality operators will be omitted. + +#### `JsonConverterSetting` + +Define how json support should be generated: -Upon detecting a union relation, the generator will emit conversion operators approriate to the inferred relation type: - -- `Disjunct` -> There is no relation between the provided type and target type. -> They do not share any representable types. -> No conversion operators will be generated. - -- `BidirectionalRelation` -> The relation is defined on both the target type as well as the provided type. -> Only for one of the two union types will conversion operators be generated. - -- `Superset` -> The target type is a superset of the provided type. -> The target type may represent all of the provided types representable types. -> This means that two conversion operations will be generated: -> - an implicit conversion operator from the provided type to the target type -> - an explicit conversion operator from the target type to the provided type -> This option is not available if the provided type has already defined a relation to the target type. - -- `Subset` -> The target type is a subset of the provided type. -> The provided type may represent all of the target types representable types. -> This means that two conversion operations will be generated: -> - an implicit conversion operator from the target type to the provided type -> - an explicit conversion operator from the provided type to the target type -> This option is not available if the provided type has already defined a relation to the target type. - -- `Intersection` -> The target type intersects the provided type. -> The target type may represent some, but not all of the provided types representable types; and vice-versa. -> This means that two conversion operations will be generated: -> - an explicit conversion operator from the target type to the provided type -> - an explicit conversion operator from the provided type to the target type -> This option is not available if the provided type has already defined a relation to the target type. - -- `Congruent` -> The target type is congruent to the provided type. -> The target type may represent all of the provided types representable types; and vice-versa. -> This means that two conversion operations will be generated: -> - an implicit conversion operator from the target type to the provided type -> - an implicit conversion operator from the provided type to the target type -> This option is not available if the provided type has already defined a relation to the target type. +- `Inherit` +> Inherits the setting. This is the default value. +> - If the target is a type, it will inherit the setting from its containing assembly. +> - If the target is an assembly, the `OmitJsonConverter` setting will be used. + +- `OmitJsonConverter` +> No JSON converter implementation is emitted. + +- `EmitJsonConverter` +> A JSON converter implementation is emitted. ## Contrived Example @@ -434,7 +245,8 @@ The `User` type represents a user. The `ErrorCode` represents an error that does We define a union type to represent our imaginary query: ```cs -[UnionType] +[UnionType(Groups = ["Error"])] +[UnionType(Groups = ["Success"])] readonly partial struct GetUserResult; ``` Instances of `GetUserResult` can represent *either* an instance of `ErrorCode`, `MultipleUsersError` or `User`. @@ -505,10 +317,10 @@ sealed class UserModel public void SetUser(String name) { var getUserResult = _service.GetUserByName(name); - User = getUserResult.Match( - HandleErrorCode, - HandleMultipleResult, - user => user); + User = getUserResult.Switch( + onErrorCode: HandleErrorCode, + onMultipleUsersError: HandleMultipleResult, + onUser: user => user); } private User? HandleErrorCode(ErrorCode code) { @@ -527,321 +339,3 @@ sealed class UserModel } } ``` - -Here is a list of some generated members on the `GetUserResult` union type (implementations and some details have been elided): -```cs -///

public ToStringSetting ToStringSetting { get; set; } + /// /// Indicates how to generate equality operators. /// By default, equality operators will only be emitted for value types, to preserve /// reference equality for comparing reference union types via == or !=. /// public EqualityOperatorsSetting EqualityOperatorsSetting { get; set; } - - + /// /// Gets or sets a value indicating whether to make the union type JSON serializable. /// From ad09438a8e0a58b2fa481ee62b05bd9c1d377e50 Mon Sep 17 00:00:00 2001 From: Paul Braetz Date: Sun, 21 Dec 2025 19:45:10 +0100 Subject: [PATCH 36/36] update README.md --- Janus/README.md | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/Janus/README.md b/Janus/README.md index 9b2acc3..8298295 100644 --- a/Janus/README.md +++ b/Janus/README.md @@ -25,6 +25,7 @@ Package Reference: ``` + CLI: ``` dotnet add package RhoMicro.CodeAnalysis.Janus @@ -55,30 +56,36 @@ Use `UnionTypeAttribute` to add `T0` to the list of variants: [UnionType] partial struct IntOrString; ``` + Usage: ```cs IntOrString u = "Hello, World!"; //implicitly converted u = 32; //implicitly converted ``` + Use `UnionTypeAttribute` on type parameters to add the targeted type parameter to the list of variants: ```cs partial struct GenericUnion<[UnionType] T0, [UnionType] T1>; ``` + Usage: ```cs var u = GenericUnion.CreateFromT1("Hello, World!"); u = GenericUnion.CreateFromT0(32); ``` -*Note: due to compiler restrictions no conversions from or to generic type parameters are generated. Using factory methods is an alternative way of creating union instances.* + +*Note: due to compiler restrictions no conversions from or to generic type parameters are generated. Using factory +methods is an alternative way of creating union instances.* - `Name` -Define names for generated members using `Name` , e.g.: +Define names for generated members using `Name`: ```cs [UnionType>(Name = "MultipleNames")] [UnionType(Name = "SingleName")] partial struct Names; ``` + Usage: ```cs Names n = "John"; @@ -124,6 +131,7 @@ Group variants into categories by assigning `Groups`: [UnionType(Groups = ["Text"])] partial struct GroupedUnion; ``` + Usage: ```cs GroupedUnion u = "Hello, World!"; @@ -160,13 +168,16 @@ partial struct Union; Use the `UnionTypeSettingsAttribute` to supply additional instructions to the generator. The attribute may be applied to either an assembly or a union type. When targeting a union type, it defines settings specific to that type. -If, however, the attribute is annotating an assembly, it supplies the default settings for every union type in that assembly. +If, however, the attribute is annotating an assembly, it supplies the default settings for every union type in that +assembly. Settings inheritance is therefore ordered like so: -- default settings are applied (see [UnionTypeSettingsAttribute](https://raw.githubusercontent.com/PaulBraetz/RhoMicro.CodeAnalysis/master/Janus/Attributes/UnionTypeSettingsAttribute.cs)) +- default settings are applied ( + see [UnionTypeSettingsAttribute](https://raw.githubusercontent.com/PaulBraetz/RhoMicro.CodeAnalysis/master/Janus/Attributes/UnionTypeSettingsAttribute.cs)) - if settings could be located on assembly, settings defined therein are applied and override inherited settings -- if settings could be located on union type, settings defined therein are applied and override inherited or assembly settings +- if settings could be located on union type, settings defined therein are applied and override inherited or assembly + settings #### `ToStringSetting` @@ -211,7 +222,7 @@ Define if equality operators should be generated: #### `JsonConverterSetting` -Define how json support should be generated: +Define how JSON support should be generated: - `Inherit` > Inherits the setting. This is the default value. @@ -224,10 +235,10 @@ Define how json support should be generated: - `EmitJsonConverter` > A JSON converter implementation is emitted. -## Contrived Example - -In our imaginary usecase, a user shall be retrieved from the infrastructure via a name query. The following types will be found throughout the example: +## Compound Example +In our imaginary usecase, a user shall be retrieved from the infrastructure via a name query. The following types will +be found throughout the example: ```cs sealed record User(String Name); @@ -240,15 +251,16 @@ enum ErrorCode readonly record struct MultipleUsersError(Int32 Count); ``` -The `User` type represents a user. The `ErrorCode` represents an error that does not contain additional information, like `MultipleUsersError` does. It represents multiple users having been found while only one was requested. +The `User` type represents a user. The `ErrorCode` represents an error that does not contain additional information, +like `MultipleUsersError` does. It represents multiple users having been found while only one was requested. We define a union type to represent our imaginary query: - ```cs [UnionType(Groups = ["Error"])] [UnionType(Groups = ["Success"])] readonly partial struct GetUserResult; ``` + Instances of `GetUserResult` can represent *either* an instance of `ErrorCode`, `MultipleUsersError` or `User`. It will be used in a service façade like so: @@ -258,6 +270,7 @@ interface IUserService GetUserResult GetUserByName(String name); } ``` + A repository abstracts over the underlying infrastructure: ```cs interface IUserRepository @@ -265,10 +278,12 @@ interface IUserRepository IQueryable UsersByName(String name); } ``` + Access violations would be communicated through the repository using the following exception type: ```cs sealed class UnauthorizedDatabaseAccessException : Exception; ``` + An implementation of the `IUserService` is provided as follows: ```cs sealed class UserService : IUserService @@ -301,10 +316,12 @@ sealed class UserService : IUserService } } ``` -As you can see, possible representations of `GetUserResult` are implicitly converted and returned by the service. Users of `OneOf` will be familiar with this. -On the consumer side of this api, a generated `Match` function helps with transforming the union instance to another type: +As you can see, possible representations of `GetUserResult` are implicitly converted and returned by the service. Users +of `OneOf` will be familiar with this. +On the consumer side of this api, a generated `Match` function helps with transforming the union instance to another +type: ```cs sealed class UserModel {