Skip to content

Commit 00c444b

Browse files
committed
Enhance nullable reference type support and clean up code across multiple files
- Added nullable attributes for compatibility with netstandard2.0 - Updated method signatures to use nullable reference types - Refactored code to improve clarity and remove unnecessary variables - Suppressed specific warnings in project files for generated code
1 parent 0dce1ef commit 00c444b

File tree

12 files changed

+65
-31
lines changed

12 files changed

+65
-31
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
// These attributes are defined here because netstandard2.0 doesn't include them.
5+
// The compiler will use these definitions for nullable flow analysis.
6+
7+
#if NETSTANDARD2_0
8+
9+
namespace System.Diagnostics.CodeAnalysis;
10+
11+
/// <summary>
12+
/// Specifies that when a method returns <see cref="ReturnValue"/>,
13+
/// the parameter will not be null even if the corresponding type allows it.
14+
/// </summary>
15+
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
16+
internal sealed class NotNullWhenAttribute : Attribute
17+
{
18+
/// <summary>
19+
/// Initializes the attribute with the specified return value condition.
20+
/// </summary>
21+
/// <param name="returnValue">
22+
/// The return value condition. If the method returns this value,
23+
/// the associated parameter will not be null.
24+
/// </param>
25+
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
26+
27+
/// <summary>
28+
/// Gets the return value condition.
29+
/// </summary>
30+
public bool ReturnValue { get; }
31+
}
32+
33+
#endif

src/Yamlify.SourceGenerator/YamlSourceGenerator.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Collections.Immutable;
4+
using System.Diagnostics.CodeAnalysis;
45
using System.Linq;
56
using System.Text;
67
using Microsoft.CodeAnalysis;
@@ -498,15 +499,14 @@ private static void GenerateReadMethod(StringBuilder sb, TypeToGenerate type, IR
498499

499500
// Check if this type is polymorphic (from [YamlSerializable] or [YamlPolymorphic] attributes)
500501
var polyInfo = GetPolymorphicInfoForType(type);
501-
var isPolymorphicBase = polyInfo is not null && polyInfo.DerivedTypes.Count > 0;
502-
if (isPolymorphicBase && (type.Symbol.IsAbstract || type.Symbol.TypeKind == TypeKind.Interface))
502+
if (polyInfo is { DerivedTypes.Count: > 0 } && (type.Symbol.IsAbstract || type.Symbol.TypeKind == TypeKind.Interface))
503503
{
504504
// Generate polymorphic read - dispatch to correct derived type based on discriminator
505505
GeneratePolymorphicRead(sb, type, polyInfo, allTypes, fullTypeName);
506506
sb.AppendLine(" }");
507507
return;
508508
}
509-
else if (isPolymorphicBase)
509+
else if (polyInfo is { DerivedTypes.Count: > 0 })
510510
{
511511
// Non-abstract base type with derived types - still needs polymorphic dispatch
512512
// but must also handle the case where the base type itself is serialized
@@ -2678,7 +2678,7 @@ private static string ToKebabCase(string name)
26782678
return null;
26792679
}
26802680

2681-
private static bool IsListOrArray(ITypeSymbol type, out ITypeSymbol? elementType, out bool isHashSet)
2681+
private static bool IsListOrArray(ITypeSymbol type, [NotNullWhen(true)] out ITypeSymbol? elementType, out bool isHashSet)
26822682
{
26832683
elementType = null;
26842684
isHashSet = false;
@@ -2714,7 +2714,7 @@ private static bool IsListOrArray(ITypeSymbol type, out ITypeSymbol? elementType
27142714
return false;
27152715
}
27162716

2717-
private static bool IsDictionary(ITypeSymbol type, out ITypeSymbol? keyType, out ITypeSymbol? valueType)
2717+
private static bool IsDictionary(ITypeSymbol type, [NotNullWhen(true)] out ITypeSymbol? keyType, [NotNullWhen(true)] out ITypeSymbol? valueType)
27182718
{
27192719
keyType = null;
27202720
valueType = null;

src/Yamlify.SourceGenerator/Yamlify.SourceGenerator.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@
44
<TargetFramework>netstandard2.0</TargetFramework>
55
<LangVersion>12.0</LangVersion>
66
<Nullable>enable</Nullable>
7+
<WarningsAsErrors>$(WarningsAsErrors);nullable</WarningsAsErrors>
78
<ImplicitUsings>disable</ImplicitUsings>
89
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
910
<IsRoslynComponent>true</IsRoslynComponent>
1011

1112
<!-- Package metadata - not published separately, bundled with Yamlify -->
1213
<IsPackable>false</IsPackable>
14+
15+
<!-- Suppress analyzer release tracking warning (bundled with main package) -->
16+
<NoWarn>$(NoWarn);RS2008</NoWarn>
1317
</PropertyGroup>
1418

1519
<ItemGroup>

src/Yamlify/Core/Utf8YamlReader.Helpers.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -393,14 +393,12 @@ private bool HasTabsInIndentation()
393393
// Check for tabs before content while still in the indentation zone
394394
// Tabs are only invalid if they appear at positions that would affect indentation
395395
int pos = _lineStart;
396-
bool foundNonTabWhitespace = false;
397396

398397
while (pos < _consumed && pos < _buffer.Length)
399398
{
400399
byte b = _buffer[pos];
401400
if (b == Space)
402401
{
403-
foundNonTabWhitespace = true;
404402
pos++;
405403
}
406404
else if (b == Tab)

src/Yamlify/Core/Utf8YamlReader.Parsing.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1714,12 +1714,9 @@ private bool ParseFlowMappingValue()
17141714
_consumed++; // Skip :
17151715
SkipSpaces();
17161716

1717-
_parsingFlowMappingValue = true; // Track that we're parsing a value in flow mapping
1718-
17191717
// For node-level API, don't emit Value token - parse the value content directly
17201718
if (_consumed >= _buffer.Length)
17211719
{
1722-
_parsingFlowMappingValue = false;
17231720
throw new YamlException("Unexpected end of input in flow mapping", Position);
17241721
}
17251722

@@ -1732,7 +1729,6 @@ private bool ParseFlowMappingValue()
17321729
_valueSpan = default;
17331730
_tokenType = YamlTokenType.Scalar;
17341731
_scalarStyle = ScalarStyle.Plain;
1735-
_parsingFlowMappingValue = false;
17361732
return true;
17371733
}
17381734

@@ -1744,7 +1740,6 @@ private bool ParseFlowMappingValue()
17441740
_valueSpan = default;
17451741
_tokenType = YamlTokenType.Scalar;
17461742
_scalarStyle = ScalarStyle.Plain;
1747-
_parsingFlowMappingValue = false;
17481743
return true;
17491744
}
17501745

@@ -1760,7 +1755,6 @@ private bool ParseFlowMappingValue()
17601755
_ => ParsePlainScalar()
17611756
};
17621757

1763-
_parsingFlowMappingValue = false;
17641758
return result;
17651759
}
17661760

src/Yamlify/Core/Utf8YamlReader.Tokens.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,11 @@ public void Invalidate()
204204
internal ref struct TokenBuffer
205205
{
206206
// Inline storage for common cases (8 tokens = ~320 bytes)
207+
// These fields are accessed via Unsafe.Add from _token0
208+
#pragma warning disable CS0169
207209
private RawToken _token0, _token1, _token2, _token3;
208210
private RawToken _token4, _token5, _token6, _token7;
211+
#pragma warning restore CS0169
209212

210213
// Overflow storage for rare complex cases
211214
private RawToken[]? _overflow;
@@ -388,8 +391,11 @@ private void SetInline(int index, RawToken value)
388391
internal ref struct SimpleKeyStack
389392
{
390393
// Inline storage (8 levels covers most real-world cases)
394+
// These fields are accessed via Unsafe.Add from _key0
395+
#pragma warning disable CS0169
391396
private SimpleKeyInfo _key0, _key1, _key2, _key3;
392397
private SimpleKeyInfo _key4, _key5, _key6, _key7;
398+
#pragma warning restore CS0169
393399

394400
// Overflow for deeply nested flows
395401
private SimpleKeyInfo[]? _overflow;
@@ -499,10 +505,13 @@ internal struct TagHandleStorage
499505
private const int MaxHandles = 4;
500506

501507
// Inline storage for handles (fixed-size arrays)
508+
// These fields are accessed via Unsafe.Add from _handle0
509+
#pragma warning disable CS0169, CS0649
502510
private TagHandleEntry _handle0;
503511
private TagHandleEntry _handle1;
504512
private TagHandleEntry _handle2;
505513
private TagHandleEntry _handle3;
514+
#pragma warning restore CS0169, CS0649
506515

507516
private int _count;
508517

src/Yamlify/Core/Utf8YamlReader.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ public ref partial struct Utf8YamlReader
4949
private bool _expectingMappingValue; // True after parsing a mapping key, expecting value
5050
private bool _hadValueOnLine; // True if we parsed a complete key:value on the current line
5151
private int _lastValueLine; // Line number where _hadValueOnLine was set
52-
private bool _parsingFlowMappingValue; // True when parsing value after : in flow mapping
5352
private bool _crossedLineBreakInFlow; // True when SkipFlowWhitespaceAndComments crossed a line break
5453
private bool _onDocumentStartLine; // True when content follows --- on the same line
5554

src/Yamlify/Core/Utf8YamlWriter.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ public sealed class Utf8YamlWriter : IDisposable
2121
private readonly bool _ownsOutput;
2222

2323
private int _currentDepth;
24-
private int _pendingIndent;
2524
private bool _needsNewLine;
2625
private bool _inFlowContext;
2726
private bool _afterPropertyName; // True if we just wrote a property name and need a value
@@ -59,7 +58,6 @@ public Utf8YamlWriter(IBufferWriter<byte> output, YamlWriterOptions? options = n
5958
_options = options ?? YamlWriterOptions.Default;
6059
_ownsOutput = false;
6160
_currentDepth = 0;
62-
_pendingIndent = 0;
6361
_needsNewLine = false;
6462
_inFlowContext = false;
6563
_afterPropertyName = false;
@@ -539,7 +537,6 @@ public void Flush()
539537
public void Reset()
540538
{
541539
_currentDepth = 0;
542-
_pendingIndent = 0;
543540
_needsNewLine = false;
544541
_inFlowContext = false;
545542
_afterPropertyName = false;
@@ -687,6 +684,7 @@ private void WriteScalarValue(ReadOnlySpan<char> value)
687684
if (needsQuoting)
688685
{
689686
WriteRaw((byte)'\'');
687+
Span<byte> charBuffer = stackalloc byte[4];
690688
foreach (char c in value)
691689
{
692690
if (c == '\'')
@@ -695,7 +693,6 @@ private void WriteScalarValue(ReadOnlySpan<char> value)
695693
}
696694
else
697695
{
698-
Span<byte> charBuffer = stackalloc byte[4];
699696
var chars = new ReadOnlySpan<char>(in c);
700697
int byteCount = Encoding.UTF8.GetBytes(chars, charBuffer);
701698
WriteRaw(charBuffer[..byteCount]);

src/Yamlify/Serialization/YamlSerializer.cs

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using System.Buffers;
2-
using System.Diagnostics.CodeAnalysis;
32
using System.Text;
43
using Yamlify.Core;
54

@@ -420,8 +419,7 @@ public static async Task SerializeAsync<TValue>(
420419
/// <param name="reader">The reader to read from.</param>
421420
/// <param name="typeInfo">The source-generated type info for the target type.</param>
422421
/// <returns>A <typeparamref name="TValue"/> representation of the YAML value.</returns>
423-
[return: MaybeNull]
424-
public static TValue Deserialize<TValue>(ref Utf8YamlReader reader, YamlTypeInfo<TValue> typeInfo)
422+
public static TValue? Deserialize<TValue>(ref Utf8YamlReader reader, YamlTypeInfo<TValue> typeInfo)
425423
{
426424
ArgumentNullException.ThrowIfNull(typeInfo);
427425
return DeserializeCore(ref reader, typeInfo);
@@ -440,8 +438,7 @@ public static TValue Deserialize<TValue>(ref Utf8YamlReader reader, YamlTypeInfo
440438
/// <exception cref="InvalidOperationException">
441439
/// Thrown when no TypeInfoResolver is configured on <see cref="YamlSerializerOptions.Default"/>.
442440
/// </exception>
443-
[return: MaybeNull]
444-
public static TValue Deserialize<TValue>(Stream stream)
441+
public static TValue? Deserialize<TValue>(Stream stream)
445442
{
446443
ArgumentNullException.ThrowIfNull(stream);
447444
var options = YamlSerializerOptions.Default;
@@ -457,8 +454,7 @@ public static TValue Deserialize<TValue>(Stream stream)
457454
/// <param name="stream">The stream to read from.</param>
458455
/// <param name="typeInfo">The source-generated type info for the target type.</param>
459456
/// <returns>A <typeparamref name="TValue"/> representation of the YAML value.</returns>
460-
[return: MaybeNull]
461-
public static TValue Deserialize<TValue>(Stream stream, YamlTypeInfo<TValue> typeInfo)
457+
public static TValue? Deserialize<TValue>(Stream stream, YamlTypeInfo<TValue> typeInfo)
462458
{
463459
ArgumentNullException.ThrowIfNull(stream);
464460
ArgumentNullException.ThrowIfNull(typeInfo);
@@ -478,8 +474,7 @@ public static TValue Deserialize<TValue>(Stream stream, YamlTypeInfo<TValue> typ
478474
/// <exception cref="InvalidOperationException">
479475
/// Thrown when no TypeInfoResolver is configured on the options.
480476
/// </exception>
481-
[return: MaybeNull]
482-
public static TValue Deserialize<TValue>(Stream stream, YamlSerializerOptions options)
477+
public static TValue? Deserialize<TValue>(Stream stream, YamlSerializerOptions options)
483478
{
484479
ArgumentNullException.ThrowIfNull(stream);
485480
ArgumentNullException.ThrowIfNull(options);
@@ -498,8 +493,7 @@ public static TValue Deserialize<TValue>(Stream stream, YamlSerializerOptions op
498493
/// <exception cref="InvalidOperationException">
499494
/// Thrown when no TypeInfoResolver is configured on <see cref="YamlSerializerOptions.Default"/>.
500495
/// </exception>
501-
[return: MaybeNull]
502-
public static async ValueTask<TValue> DeserializeAsync<TValue>(
496+
public static async ValueTask<TValue?> DeserializeAsync<TValue>(
503497
Stream stream,
504498
CancellationToken cancellationToken = default)
505499
{
@@ -518,8 +512,7 @@ public static async ValueTask<TValue> DeserializeAsync<TValue>(
518512
/// <param name="typeInfo">The source-generated type info for the target type.</param>
519513
/// <param name="cancellationToken">A cancellation token.</param>
520514
/// <returns>A <typeparamref name="TValue"/> representation of the YAML value.</returns>
521-
[return: MaybeNull]
522-
public static async ValueTask<TValue> DeserializeAsync<TValue>(
515+
public static async ValueTask<TValue?> DeserializeAsync<TValue>(
523516
Stream stream,
524517
YamlTypeInfo<TValue> typeInfo,
525518
CancellationToken cancellationToken = default)

src/Yamlify/Yamlify.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<LangVersion>preview</LangVersion>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<Nullable>enable</Nullable>
8+
<WarningsAsErrors>$(WarningsAsErrors);nullable</WarningsAsErrors>
89
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
910

1011
<!-- AOT compatibility -->

0 commit comments

Comments
 (0)