Skip to content

Commit 2a85304

Browse files
committed
Improve error message in ad hoc unions' methods "AsFoo"
1 parent 6539101 commit 2a85304

File tree

69 files changed

+1095
-299
lines changed

Some content is hidden

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

69 files changed

+1095
-299
lines changed

.serena/project.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,8 @@ language_backend:
129129
# list of regex patterns which, when matched, mark a memory entry as read‑only.
130130
# Extends the list from the global configuration, merging the two lists.
131131
read_only_memory_patterns: []
132+
133+
# line ending convention to use when writing source files.
134+
# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default)
135+
# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings.
136+
line_ending:

src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/AdHocUnions/AdHocUnionCodeGenerator.cs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ private void GenerateUnion(CancellationToken cancellationToken)
8787
"); // index is 1-based
8888

8989
GenerateMemberTypeFieldsAndProps();
90+
GenerateGetMemberTypeName();
9091
GenerateRawValueGetter();
9192
GenerateConstructors();
9293
GenerateFactoriesForTypeDuplicates();
@@ -1042,10 +1043,34 @@ private void GenerateMemberTypeFieldsAndProps()
10421043
_sb.AppendBackingFieldAccess(_state, _useSharedObjectForRefTypes, memberType).Append(memberType.IsReferenceType && memberType.NullableAnnotation != NullableAnnotation.Annotated ? "!" : null);
10431044
}
10441045

1045-
_sb.Append(" : throw new global::System.InvalidOperationException($\"'{nameof(").AppendTypeFullyQualified(_state).Append(")}' is not of type '").AppendTypeMinimallyQualified(memberType).Append("'.\");");
1046+
_sb.Append(" : throw new global::System.InvalidOperationException($\"'{nameof(").AppendTypeFullyQualified(_state).Append(")}' is not of type '").AppendTypeMinimallyQualified(memberType).Append("' but of type '{GetMemberTypeName()}'.\");");
10461047
}
10471048
}
10481049

1050+
private void GenerateGetMemberTypeName()
1051+
{
1052+
_sb.Append(@"
1053+
1054+
private string GetMemberTypeName()
1055+
{
1056+
return this._valueIndex switch
1057+
{
1058+
0 => ""<uninitialized>"",");
1059+
1060+
for (var i = 0; i < _state.MemberTypes.Length; i++)
1061+
{
1062+
var memberType = _state.MemberTypes[i];
1063+
1064+
_sb.Append(@"
1065+
").Append(i + 1).Append(@" => """).AppendTypeMinimallyQualified(memberType).Append(@""",");
1066+
}
1067+
1068+
_sb.Append(@"
1069+
_ => throw new global::System.IndexOutOfRangeException($""Unexpected value index '{this._valueIndex}'."")
1070+
};
1071+
}");
1072+
}
1073+
10491074
private void GenerateRawValueGetter()
10501075
{
10511076
var hasNullableTypes = _state.MemberTypes.Any(t => t.IsNullableStruct || t.NullableAnnotation == NullableAnnotation.Annotated);

test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/AdHocUnionSourceGeneratorTests.Should_generate_class_with_3_types_file=Thinktecture.Tests.TestUnion.AdHocUnion.g.cs.verified.txt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,19 +47,31 @@ namespace Thinktecture.Tests
4747
/// Gets the current value as <see cref="string"/>.
4848
/// </summary>
4949
/// <exception cref="global::System.InvalidOperationException">If the current value is not of type <see cref="string"/>.</exception>
50-
public string AsString => IsString ? this._string! : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'string'.");
50+
public string AsString => IsString ? this._string! : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'string' but of type '{GetMemberTypeName()}'.");
5151

5252
/// <summary>
5353
/// Gets the current value as <see cref="int"/>.
5454
/// </summary>
5555
/// <exception cref="global::System.InvalidOperationException">If the current value is not of type <see cref="int"/>.</exception>
56-
public int AsInt32 => IsInt32 ? this._int32 : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'int'.");
56+
public int AsInt32 => IsInt32 ? this._int32 : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'int' but of type '{GetMemberTypeName()}'.");
5757

5858
/// <summary>
5959
/// Gets the current value as <see cref="bool"/>.
6060
/// </summary>
6161
/// <exception cref="global::System.InvalidOperationException">If the current value is not of type <see cref="bool"/>.</exception>
62-
public bool AsBoolean => IsBoolean ? this._boolean : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'bool'.");
62+
public bool AsBoolean => IsBoolean ? this._boolean : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'bool' but of type '{GetMemberTypeName()}'.");
63+
64+
private string GetMemberTypeName()
65+
{
66+
return this._valueIndex switch
67+
{
68+
0 => "<uninitialized>",
69+
1 => "string",
70+
2 => "int",
71+
3 => "bool",
72+
_ => throw new global::System.IndexOutOfRangeException($"Unexpected value index '{this._valueIndex}'.")
73+
};
74+
}
6375

6476
/// <summary>
6577
/// Gets the current value as <see cref="object"/>.

test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/AdHocUnionSourceGeneratorTests.Should_generate_class_with_4_types_file=Thinktecture.Tests.TestUnion.AdHocUnion.g.cs.verified.txt

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,25 +54,38 @@ namespace Thinktecture.Tests
5454
/// Gets the current value as <see cref="string"/>.
5555
/// </summary>
5656
/// <exception cref="global::System.InvalidOperationException">If the current value is not of type <see cref="string"/>.</exception>
57-
public string AsString => IsString ? this._string! : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'string'.");
57+
public string AsString => IsString ? this._string! : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'string' but of type '{GetMemberTypeName()}'.");
5858

5959
/// <summary>
6060
/// Gets the current value as <see cref="int"/>.
6161
/// </summary>
6262
/// <exception cref="global::System.InvalidOperationException">If the current value is not of type <see cref="int"/>.</exception>
63-
public int AsInt32 => IsInt32 ? this._int32 : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'int'.");
63+
public int AsInt32 => IsInt32 ? this._int32 : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'int' but of type '{GetMemberTypeName()}'.");
6464

6565
/// <summary>
6666
/// Gets the current value as <see cref="bool"/>.
6767
/// </summary>
6868
/// <exception cref="global::System.InvalidOperationException">If the current value is not of type <see cref="bool"/>.</exception>
69-
public bool AsBoolean => IsBoolean ? this._boolean : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'bool'.");
69+
public bool AsBoolean => IsBoolean ? this._boolean : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'bool' but of type '{GetMemberTypeName()}'.");
7070

7171
/// <summary>
7272
/// Gets the current value as <see cref="global::System.Guid"/>.
7373
/// </summary>
7474
/// <exception cref="global::System.InvalidOperationException">If the current value is not of type <see cref="global::System.Guid"/>.</exception>
75-
public global::System.Guid AsGuid => IsGuid ? this._guid : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'Guid'.");
75+
public global::System.Guid AsGuid => IsGuid ? this._guid : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'Guid' but of type '{GetMemberTypeName()}'.");
76+
77+
private string GetMemberTypeName()
78+
{
79+
return this._valueIndex switch
80+
{
81+
0 => "<uninitialized>",
82+
1 => "string",
83+
2 => "int",
84+
3 => "bool",
85+
4 => "Guid",
86+
_ => throw new global::System.IndexOutOfRangeException($"Unexpected value index '{this._valueIndex}'.")
87+
};
88+
}
7689

7790
/// <summary>
7891
/// Gets the current value as <see cref="object"/>.

test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/AdHocUnionSourceGeneratorTests.Should_generate_class_with_AdHocUnion_and_3_types_file=Thinktecture.Tests.TestUnion.AdHocUnion.g.cs.verified.txt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,19 +47,31 @@ namespace Thinktecture.Tests
4747
/// Gets the current value as <see cref="string"/>.
4848
/// </summary>
4949
/// <exception cref="global::System.InvalidOperationException">If the current value is not of type <see cref="string"/>.</exception>
50-
public string AsString => IsString ? this._string! : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'string'.");
50+
public string AsString => IsString ? this._string! : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'string' but of type '{GetMemberTypeName()}'.");
5151

5252
/// <summary>
5353
/// Gets the current value as <see cref="int"/>.
5454
/// </summary>
5555
/// <exception cref="global::System.InvalidOperationException">If the current value is not of type <see cref="int"/>.</exception>
56-
public int AsInt32 => IsInt32 ? this._int32 : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'int'.");
56+
public int AsInt32 => IsInt32 ? this._int32 : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'int' but of type '{GetMemberTypeName()}'.");
5757

5858
/// <summary>
5959
/// Gets the current value as <see cref="bool"/>.
6060
/// </summary>
6161
/// <exception cref="global::System.InvalidOperationException">If the current value is not of type <see cref="bool"/>.</exception>
62-
public bool AsBoolean => IsBoolean ? this._boolean : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'bool'.");
62+
public bool AsBoolean => IsBoolean ? this._boolean : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'bool' but of type '{GetMemberTypeName()}'.");
63+
64+
private string GetMemberTypeName()
65+
{
66+
return this._valueIndex switch
67+
{
68+
0 => "<uninitialized>",
69+
1 => "string",
70+
2 => "int",
71+
3 => "bool",
72+
_ => throw new global::System.IndexOutOfRangeException($"Unexpected value index '{this._valueIndex}'.")
73+
};
74+
}
6375

6476
/// <summary>
6577
/// Gets the current value as <see cref="object"/>.

test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/AdHocUnionSourceGeneratorTests.Should_generate_class_with_AdHocUnion_and_4_types_file=Thinktecture.Tests.TestUnion.AdHocUnion.g.cs.verified.txt

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,25 +54,38 @@ namespace Thinktecture.Tests
5454
/// Gets the current value as <see cref="string"/>.
5555
/// </summary>
5656
/// <exception cref="global::System.InvalidOperationException">If the current value is not of type <see cref="string"/>.</exception>
57-
public string AsString => IsString ? this._string! : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'string'.");
57+
public string AsString => IsString ? this._string! : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'string' but of type '{GetMemberTypeName()}'.");
5858

5959
/// <summary>
6060
/// Gets the current value as <see cref="int"/>.
6161
/// </summary>
6262
/// <exception cref="global::System.InvalidOperationException">If the current value is not of type <see cref="int"/>.</exception>
63-
public int AsInt32 => IsInt32 ? this._int32 : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'int'.");
63+
public int AsInt32 => IsInt32 ? this._int32 : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'int' but of type '{GetMemberTypeName()}'.");
6464

6565
/// <summary>
6666
/// Gets the current value as <see cref="bool"/>.
6767
/// </summary>
6868
/// <exception cref="global::System.InvalidOperationException">If the current value is not of type <see cref="bool"/>.</exception>
69-
public bool AsBoolean => IsBoolean ? this._boolean : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'bool'.");
69+
public bool AsBoolean => IsBoolean ? this._boolean : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'bool' but of type '{GetMemberTypeName()}'.");
7070

7171
/// <summary>
7272
/// Gets the current value as <see cref="global::System.Guid"/>.
7373
/// </summary>
7474
/// <exception cref="global::System.InvalidOperationException">If the current value is not of type <see cref="global::System.Guid"/>.</exception>
75-
public global::System.Guid AsGuid => IsGuid ? this._guid : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'Guid'.");
75+
public global::System.Guid AsGuid => IsGuid ? this._guid : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'Guid' but of type '{GetMemberTypeName()}'.");
76+
77+
private string GetMemberTypeName()
78+
{
79+
return this._valueIndex switch
80+
{
81+
0 => "<uninitialized>",
82+
1 => "string",
83+
2 => "int",
84+
3 => "bool",
85+
4 => "Guid",
86+
_ => throw new global::System.IndexOutOfRangeException($"Unexpected value index '{this._valueIndex}'.")
87+
};
88+
}
7689

7790
/// <summary>
7891
/// Gets the current value as <see cref="object"/>.

test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/AdHocUnionSourceGeneratorTests.Should_generate_class_with_all_5_custom_names_file=Thinktecture.Tests.TestUnion.AdHocUnion.g.cs.verified.txt

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,31 +61,45 @@ namespace Thinktecture.Tests
6161
/// Gets the current value as <see cref="string"/>.
6262
/// </summary>
6363
/// <exception cref="global::System.InvalidOperationException">If the current value is not of type <see cref="string"/>.</exception>
64-
public string AsText => IsText ? this._text! : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'string'.");
64+
public string AsText => IsText ? this._text! : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'string' but of type '{GetMemberTypeName()}'.");
6565

6666
/// <summary>
6767
/// Gets the current value as <see cref="int"/>.
6868
/// </summary>
6969
/// <exception cref="global::System.InvalidOperationException">If the current value is not of type <see cref="int"/>.</exception>
70-
public int AsNumber => IsNumber ? this._number : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'int'.");
70+
public int AsNumber => IsNumber ? this._number : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'int' but of type '{GetMemberTypeName()}'.");
7171

7272
/// <summary>
7373
/// Gets the current value as <see cref="bool"/>.
7474
/// </summary>
7575
/// <exception cref="global::System.InvalidOperationException">If the current value is not of type <see cref="bool"/>.</exception>
76-
public bool AsFlag => IsFlag ? this._flag : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'bool'.");
76+
public bool AsFlag => IsFlag ? this._flag : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'bool' but of type '{GetMemberTypeName()}'.");
7777

7878
/// <summary>
7979
/// Gets the current value as <see cref="global::System.Guid"/>.
8080
/// </summary>
8181
/// <exception cref="global::System.InvalidOperationException">If the current value is not of type <see cref="global::System.Guid"/>.</exception>
82-
public global::System.Guid AsId => IsId ? this._id : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'Guid'.");
82+
public global::System.Guid AsId => IsId ? this._id : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'Guid' but of type '{GetMemberTypeName()}'.");
8383

8484
/// <summary>
8585
/// Gets the current value as <see cref="char"/>.
8686
/// </summary>
8787
/// <exception cref="global::System.InvalidOperationException">If the current value is not of type <see cref="char"/>.</exception>
88-
public char AsCharacter => IsCharacter ? this._character : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'char'.");
88+
public char AsCharacter => IsCharacter ? this._character : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'char' but of type '{GetMemberTypeName()}'.");
89+
90+
private string GetMemberTypeName()
91+
{
92+
return this._valueIndex switch
93+
{
94+
0 => "<uninitialized>",
95+
1 => "string",
96+
2 => "int",
97+
3 => "bool",
98+
4 => "Guid",
99+
5 => "char",
100+
_ => throw new global::System.IndexOutOfRangeException($"Unexpected value index '{this._valueIndex}'.")
101+
};
102+
}
89103

90104
/// <summary>
91105
/// Gets the current value as <see cref="object"/>.

test/Thinktecture.Runtime.Extensions.SourceGenerator.Tests/SourceGeneratorTests/AdHocUnionSourceGeneratorTests.Should_generate_class_with_all_nullable_reference_types_file=Thinktecture.Tests.TestUnion.AdHocUnion.g.cs.verified.txt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,19 +45,31 @@ namespace Thinktecture.Tests
4545
/// Gets the current value as <c>string?</c>.
4646
/// </summary>
4747
/// <exception cref="global::System.InvalidOperationException">If the current value is not of type <c>string?</c>.</exception>
48-
public string? AsString => IsString ? ((string?)this._obj) : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'string?'.");
48+
public string? AsString => IsString ? ((string?)this._obj) : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'string?' but of type '{GetMemberTypeName()}'.");
4949

5050
/// <summary>
5151
/// Gets the current value as <c>global::System.Collections.Generic.List&lt;int&gt;?</c>.
5252
/// </summary>
5353
/// <exception cref="global::System.InvalidOperationException">If the current value is not of type <c>global::System.Collections.Generic.List&lt;int&gt;?</c>.</exception>
54-
public global::System.Collections.Generic.List<int>? AsListOfInt32 => IsListOfInt32 ? ((global::System.Collections.Generic.List<int>?)this._obj) : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'List<int>?'.");
54+
public global::System.Collections.Generic.List<int>? AsListOfInt32 => IsListOfInt32 ? ((global::System.Collections.Generic.List<int>?)this._obj) : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'List<int>?' but of type '{GetMemberTypeName()}'.");
5555

5656
/// <summary>
5757
/// Gets the current value as <c>object?</c>.
5858
/// </summary>
5959
/// <exception cref="global::System.InvalidOperationException">If the current value is not of type <c>object?</c>.</exception>
60-
public object? AsObject => IsObject ? ((object?)this._obj) : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'object?'.");
60+
public object? AsObject => IsObject ? ((object?)this._obj) : throw new global::System.InvalidOperationException($"'{nameof(global::Thinktecture.Tests.TestUnion)}' is not of type 'object?' but of type '{GetMemberTypeName()}'.");
61+
62+
private string GetMemberTypeName()
63+
{
64+
return this._valueIndex switch
65+
{
66+
0 => "<uninitialized>",
67+
1 => "string?",
68+
2 => "List<int>?",
69+
3 => "object?",
70+
_ => throw new global::System.IndexOutOfRangeException($"Unexpected value index '{this._valueIndex}'.")
71+
};
72+
}
6173

6274
/// <summary>
6375
/// Gets the current value as <see cref="object"/>.

0 commit comments

Comments
 (0)