< Summary

Information
Class: NGql.Core.Builders.FieldFactory
Assembly: NGql.Core
File(s): /home/runner/work/NGql/NGql/src/Core/Builders/FieldFactory.cs
Line coverage
100%
Covered lines: 343
Uncovered lines: 0
Coverable lines: 343
Total lines: 911
Line coverage: 100%
Branch coverage
100%
Covered branches: 232
Total branches: 232
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
GetOrAddField(...)100%66100%
GetOrAddField(...)100%1212100%
GetOrAddDottedField(...)100%22100%
GetOrAddDottedField(...)100%22100%
ProcessDottedFieldFastPath(...)100%88100%
ProcessDottedFieldFastPath(...)100%44100%
GetOrCreateRootSegment(...)100%22100%
TryGetRootFieldBySpan(...)100%22100%
TryGetRootFieldBySpan(...)100%11100%
GetOrCreateChildSegment(...)100%66100%
PromoteToObjectIfNeeded(...)100%44100%
ProcessDottedFieldWithMetadata(...)100%66100%
ProcessDottedFieldWithMetadata(...)100%66100%
ProcessDottedFieldSegments(...)100%1414100%
ProcessDottedFieldSegments(...)100%1212100%
EmptyDottedPath()100%11100%
CreateDottedFieldSegment(...)100%22100%
ProcessDottedSegment(...)100%44100%
ProcessDottedSegment(...)100%66100%
MergeArgumentsIntoExistingChild(...)100%11100%
PromoteIntermediateChildToObject(...)100%22100%
CreateDottedSegmentField(...)100%66100%
GetOrAddComplexField(...)100%22100%
GetOrAddComplexField(...)100%66100%
ProcessComplexFieldSegments(...)100%1414100%
ProcessComplexFieldSegments(...)100%1212100%
ProcessFieldSegment(...)100%22100%
ProcessFieldSegment(...)100%22100%
FindExistingSegmentChild(...)100%1212100%
SegmentAliasMatches(...)100%44100%
CreateNewField(...)100%11100%
CreateNewField(...)100%11100%
ResolveSegmentArgsAndMetadata(...)100%22100%
ResolveSegmentFieldType(...)100%88100%
UpdateExistingField(...)100%66100%
UpdateExistingField(...)100%66100%
ApplyIntermediateUpdates(...)100%88100%
ApplyParsedFieldType(...)100%44100%
CreateOrMergeField(...)100%22100%
CreateOrMergeField(...)100%22100%
CloneFieldDefinitionForMerge(...)100%11100%
ExtractDottedSegment(...)100%44100%
ExtractDottedSegmentWithPath(...)100%44100%
ExtractNextSegment(...)100%88100%
MalformedComplexSegment(...)100%11100%
ParseAliasAndName(...)100%1818100%

File(s)

/home/runner/work/NGql/NGql/src/Core/Builders/FieldFactory.cs

#LineLine coverage
 1using System.Runtime.CompilerServices;
 2using NGql.Core.Abstractions;
 3using NGql.Core.Extensions;
 4using NGql.Core.Pooling;
 5
 6namespace NGql.Core.Builders;
 7
 8/// <summary>
 9/// Factory class for creating and processing FieldDefinition instances.
 10/// Handles complex field creation logic, including dotted paths, type parsing, and field merging.
 11/// </summary>
 12internal static class FieldFactory
 13{
 14    /// <summary>
 15    /// Gets or adds a field to the collection, handling all field path complexities.
 16    /// This overload operates on the root-level <see cref="QueryDefinition.Fields"/> dictionary.
 17    /// </summary>
 18    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 19    internal static FieldDefinition GetOrAddField(Dictionary<string, FieldDefinition> fieldDefinitions, ReadOnlySpan<cha
 20    {
 8435121        var fieldType = type.IsEmpty ? Constants.DefaultFieldTypeSpan : type;
 22
 23        // FAST PATH: Simple field name
 8435124        if (fieldPath.IsSimpleField())
 25        {
 848426            return fieldDefinitions.GetOrAddSimpleField(fieldPath, fieldType, arguments, parentPath, metadata);
 27        }
 28
 29        // MEDIUM PATH: Dotted field
 7586730        if (fieldPath.IsDottedField())
 31        {
 7413932            return GetOrAddDottedField(fieldDefinitions, fieldPath, fieldType, arguments, parentPath, metadata);
 33        }
 34
 35        // SLOW PATH: Complex field processing
 172836        return GetOrAddComplexField(fieldDefinitions, fieldPath, fieldType, arguments, parentPath, metadata);
 37    }
 38
 39    /// <summary>
 40    /// Gets or adds a field as a child of the given parent node, handling all field path complexities.
 41    /// This overload is for per-node child access (not root-level dictionary access).
 42    /// </summary>
 43    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 44    internal static FieldDefinition GetOrAddField(FieldDefinition parent, ReadOnlySpan<char> fieldPath, ReadOnlySpan<cha
 45    {
 418846        var fieldType = type.IsEmpty ? Constants.DefaultFieldTypeSpan : type;
 418847        var children = parent._children ??= new FieldChildren();
 48
 49        // parent's memoized deep fingerprint (if any) summarizes its entire subtree, including
 50        // whatever descendant fieldPath resolves to. Only an argument-bearing call can change the
 51        // merge-relevant subtree, so only those need to invalidate — but they must invalidate here,
 52        // at the single choke point every simple/dotted/complex descendant mutation funnels through,
 53        // since parent itself is never revisited by the per-segment walks below.
 418854        if (arguments is { Count: > 0 })
 55        {
 26756            parent.ClearMergeMemo();
 57        }
 58
 59        // FAST PATH: Simple field name
 418860        if (fieldPath.IsSimpleField())
 61        {
 337862            return children.GetOrAddSimpleField(fieldPath, fieldType, arguments, parentPath, metadata);
 63        }
 64
 65        // MEDIUM PATH: Dotted field
 81066        if (fieldPath.IsDottedField())
 67        {
 43568            return GetOrAddDottedField(parent, fieldPath, fieldType, arguments, parentPath, metadata);
 69        }
 70
 71        // SLOW PATH: Complex field processing
 37572        return GetOrAddComplexField(parent, fieldPath, fieldType, arguments, parentPath, metadata);
 73    }
 74
 75    /// <summary>
 76    /// Gets or adds a dotted field (contains dots for nested access) — root-level variant.
 77    /// </summary>
 78    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 79    private static FieldDefinition GetOrAddDottedField(Dictionary<string, FieldDefinition> fieldDefinitions, ReadOnlySpa
 80    {
 7413981        var hasNoArguments = arguments == null;
 7413982        var hasNoMetadata = metadata == null;
 83
 84        // FAST PATH: No arguments/metadata - use optimized processing
 7413985        if (hasNoArguments && hasNoMetadata)
 86        {
 7209087            return ProcessDottedFieldFastPath(fieldDefinitions, fieldPath, fieldType);
 88        }
 89
 90        // SLOW PATH: With arguments/metadata
 204991        return ProcessDottedFieldWithMetadata(fieldDefinitions, fieldPath, fieldType, arguments, parentPath, metadata);
 92    }
 93
 94    /// <summary>
 95    /// Gets or adds a dotted field (contains dots for nested access) — per-node variant.
 96    /// </summary>
 97    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 98    private static FieldDefinition GetOrAddDottedField(FieldDefinition rootParent, ReadOnlySpan<char> fieldPath, ReadOnl
 99    {
 435100        var hasNoArguments = arguments == null;
 435101        var hasNoMetadata = metadata == null;
 102
 435103        if (hasNoArguments && hasNoMetadata)
 104        {
 312105            return ProcessDottedFieldFastPath(rootParent, fieldPath, fieldType);
 106        }
 107
 123108        return ProcessDottedFieldWithMetadata(rootParent, fieldPath, fieldType, arguments, parentPath, metadata);
 109    }
 110
 111    /// <summary>
 112    /// Processes dotted fields without arguments or metadata for optimal performance — root-level variant.
 113    /// The first segment uses the root dictionary; subsequent segments use <see cref="FieldDefinition._children"/>.
 114    /// </summary>
 115    // Callers reach here only via IsDottedField() which guarantees fieldPath contains '.',
 116    // so the loop runs at least twice and parentField is non-null on exit.
 117    private static FieldDefinition ProcessDottedFieldFastPath(Dictionary<string, FieldDefinition> rootFields, ReadOnlySp
 118    {
 72090119        FieldDefinition? parentField = null;
 72090120        var pathStart = 0;
 121
 287775122        while (pathStart < fieldPath.Length)
 123        {
 215685124            ExtractDottedSegment(fieldPath, pathStart, out var spanSegment, out var nextStart);
 215685125            if (spanSegment.Name.IsWhiteSpace())
 126            {
 48127                pathStart = nextStart;
 48128                continue;
 129            }
 215637130            parentField = parentField is null
 215637131                ? GetOrCreateRootSegment(rootFields, spanSegment, fieldPath, pathStart, fieldType)
 215637132                : GetOrCreateChildSegment(parentField, spanSegment, fieldPath, pathStart, fieldType);
 215637133            pathStart = nextStart;
 134        }
 135
 136        // A dotted path made up entirely of empty/whitespace segments (e.g. "." or "..") yields
 137        // no field. Reject it rather than dereferencing a null result.
 72090138        return parentField ?? throw EmptyDottedPath();
 139    }
 140
 141    /// <summary>
 142    /// Processes dotted fields without arguments or metadata — per-node variant.
 143    /// All segments use <see cref="FieldDefinition._children"/>.
 144    /// </summary>
 145    private static FieldDefinition ProcessDottedFieldFastPath(FieldDefinition rootParent, ReadOnlySpan<char> fieldPath, 
 146    {
 312147        var currentParent = rootParent;
 312148        var pathStart = 0;
 149
 1506150        while (pathStart < fieldPath.Length)
 151        {
 1194152            ExtractDottedSegment(fieldPath, pathStart, out var spanSegment, out var nextStart);
 1194153            if (spanSegment.Name.IsWhiteSpace())
 154            {
 9155                pathStart = nextStart;
 9156                continue;
 157            }
 1185158            currentParent = GetOrCreateChildSegment(currentParent, spanSegment, fieldPath, pathStart, fieldType);
 1185159            pathStart = nextStart;
 160        }
 161
 312162        return currentParent;
 163    }
 164
 165    private static FieldDefinition GetOrCreateRootSegment(Dictionary<string, FieldDefinition> rootFields, SpanSegment sp
 166    {
 72084167        if (!TryGetRootFieldBySpan(rootFields, spanSegment.Name, out var field, out var segmentName))
 168        {
 58299169            field = CreateDottedFieldSegment(spanSegment.Name, fieldPath, pathStart + spanSegment.Name.Length, spanSegme
 58299170            rootFields[segmentName] = field;
 58299171            return field;
 172        }
 13785173        PromoteToObjectIfNeeded(field, spanSegment.IsLastFragment);
 13785174        return field;
 175    }
 176
 177    /// <summary>
 178    /// Probes <paramref name="rootFields"/> (keyed <see cref="StringComparer.OrdinalIgnoreCase"/>) by
 179    /// span without allocating on .NET 9+, via
 180    /// <c>Dictionary&lt;TKey,TValue&gt;.GetAlternateLookup&lt;ReadOnlySpan&lt;char&gt;&gt;()</c>. On a
 181    /// miss (or on net8.0, where the alternate-lookup API does not exist), materializes
 182    /// <paramref name="name"/> exactly once and hands it back via <paramref name="key"/> so the
 183    /// caller's subsequent write on the not-found path reuses it instead of calling
 184    /// <see cref="ReadOnlySpan{T}.ToString"/> again.
 185    /// </summary>
 186    private static bool TryGetRootFieldBySpan(Dictionary<string, FieldDefinition> rootFields, ReadOnlySpan<char> name, [
 187    {
 188#if NET9_0_OR_GREATER
 49202189        if (rootFields.GetAlternateLookup<ReadOnlySpan<char>>().TryGetValue(name, out field))
 190        {
 9804191            key = null!;
 9804192            return true;
 193        }
 39398194        key = name.ToString();
 39398195        return false;
 196#else
 24601197        key = name.ToString();
 24601198        return rootFields.TryGetValue(key, out field);
 199#endif
 200    }
 201
 202    private static FieldDefinition GetOrCreateChildSegment(FieldDefinition parentField, SpanSegment spanSegment, ReadOnl
 203    {
 144738204        var children = parentField._children ??= new FieldChildren();
 144738205        if (!children.TryGetValue(spanSegment.Name, out var field) || field is null)
 206        {
 139566207            field = CreateDottedFieldSegment(spanSegment.Name, fieldPath, pathStart + spanSegment.Name.Length, spanSegme
 139566208            children.Append(field);
 139566209            return field;
 210        }
 5172211        PromoteToObjectIfNeeded(field, spanSegment.IsLastFragment);
 5172212        return field;
 213    }
 214
 215    private static void PromoteToObjectIfNeeded(FieldDefinition field, bool isLastFragment)
 216    {
 18957217        if (!isLastFragment && field.ShouldConvertToObjectType())
 218        {
 6126219            field._type = Constants.ObjectFieldType;
 220        }
 18957221    }
 222
 223    /// <summary>
 224    /// Processes dotted fields with arguments and metadata — root-level variant.
 225    /// </summary>
 226    private static FieldDefinition ProcessDottedFieldWithMetadata(Dictionary<string, FieldDefinition> fieldDefinitions, 
 227    {
 2049228        var parentPathSpan = parentPath.AsSpan();
 229
 230        // Use stack allocation for small paths, pooled resources for larger ones
 2049231        var estimatedPathLength = parentPathSpan.Length + fieldPath.Length + 10; // Extra space for dots
 232
 2049233        if (estimatedPathLength <= 512)
 234        {
 2043235            Span<char> pathBuffer = stackalloc char[512];
 2043236            var pathBuilder = new SpanPathBuilder(pathBuffer);
 237
 2043238            if (!parentPathSpan.IsEmpty)
 239            {
 6240                pathBuilder.Append(parentPathSpan);
 241            }
 242
 2043243            return ProcessDottedFieldSegments(fieldDefinitions, fieldPath, fieldType, arguments, metadata, ref pathBuild
 244        }
 245        else
 246        {
 247            // Use pooled resources for very long paths
 6248            using var pooledArray = CharArrayPool.GetPooled(estimatedPathLength);
 6249            var pathBuilder = new SpanPathBuilder(pooledArray.AsSpan());
 250
 6251            if (!parentPathSpan.IsEmpty)
 252            {
 3253                pathBuilder.Append(parentPathSpan);
 254            }
 255
 6256            return ProcessDottedFieldSegments(fieldDefinitions, fieldPath, fieldType, arguments, metadata, ref pathBuild
 257        }
 6258    }
 259
 260    /// <summary>
 261    /// Processes dotted fields with arguments and metadata — per-node variant.
 262    /// </summary>
 263    private static FieldDefinition ProcessDottedFieldWithMetadata(FieldDefinition rootParent, ReadOnlySpan<char> fieldPa
 264    {
 123265        var parentPathSpan = parentPath.AsSpan();
 123266        var estimatedPathLength = parentPathSpan.Length + fieldPath.Length + 10;
 267
 123268        if (estimatedPathLength <= 512)
 269        {
 120270            Span<char> pathBuffer = stackalloc char[512];
 120271            var pathBuilder = new SpanPathBuilder(pathBuffer);
 231272            if (!parentPathSpan.IsEmpty) pathBuilder.Append(parentPathSpan);
 120273            return ProcessDottedFieldSegments(rootParent, fieldPath, fieldType, arguments, metadata, ref pathBuilder);
 274        }
 275        else
 276        {
 3277            using var pooledArray = CharArrayPool.GetPooled(estimatedPathLength);
 3278            var pathBuilder = new SpanPathBuilder(pooledArray.AsSpan());
 6279            if (!parentPathSpan.IsEmpty) pathBuilder.Append(parentPathSpan);
 3280            return ProcessDottedFieldSegments(rootParent, fieldPath, fieldType, arguments, metadata, ref pathBuilder);
 281        }
 3282    }
 283
 284    /// <summary>
 285    /// Processes individual segments of a dotted field path — root-level variant.
 286    /// </summary>
 287    // Callers reach here only via IsDottedField() which guarantees fieldPath contains '.', so the
 288    // loop runs at least once and result is non-null on exit.
 289    private static FieldDefinition ProcessDottedFieldSegments(Dictionary<string, FieldDefinition> rootFields, ReadOnlySp
 290    {
 2049291        FieldDefinition? parentField = null;
 2049292        FieldDefinition? result = null;
 293
 10149294        while (fieldPath.Length > 0)
 295        {
 8100296            ExtractDottedSegmentWithPath(fieldPath, out var spanSegment, out var remainingPath);
 297
 8100298            if (spanSegment.Name.IsWhiteSpace())
 299            {
 9300                fieldPath = remainingPath;
 9301                continue;
 302            }
 303
 8091304            pathBuilder.Append(spanSegment.Name);
 305
 8091306            if (parentField == null)
 307            {
 308                // Root level — use the root Dictionary.
 2046309                result = ProcessDottedSegment(rootFields, spanSegment.Name, spanSegment.IsLastFragment, fieldType, argum
 310            }
 311            else
 312            {
 313                // Nested level — use FieldChildren. parentField is an ancestor of whatever this
 314                // segment (or a later one) mutates, so its memoized fingerprint must not survive
 315                // an argument-bearing walk past it.
 6045316                if (arguments is { Count: > 0 })
 317                {
 5556318                    parentField.ClearMergeMemo();
 319                }
 6045320                var children = parentField._children ??= new FieldChildren();
 6045321                result = ProcessDottedSegment(children, spanSegment.Name, spanSegment.IsLastFragment, fieldType, argumen
 322            }
 323
 8091324            parentField = result;
 8091325            fieldPath = remainingPath;
 326        }
 327
 2049328        return result ?? throw EmptyDottedPath();
 329    }
 330
 331    /// <summary>
 332    /// Processes individual segments of a dotted field path — per-node variant.
 333    /// </summary>
 334    private static FieldDefinition ProcessDottedFieldSegments(FieldDefinition rootParent, ReadOnlySpan<char> fieldPath, 
 335    {
 123336        var currentParent = rootParent;
 123337        FieldDefinition? result = null;
 338
 1158339        while (fieldPath.Length > 0)
 340        {
 1035341            ExtractDottedSegmentWithPath(fieldPath, out var spanSegment, out var remainingPath);
 342
 1035343            if (spanSegment.Name.IsWhiteSpace())
 344            {
 15345                fieldPath = remainingPath;
 15346                continue;
 347            }
 348
 1020349            pathBuilder.Append(spanSegment.Name);
 350            // currentParent is an ancestor of whatever this segment (or a later one) mutates —
 351            // see the root-level variant above for the full invalidation rationale.
 1020352            if (arguments is { Count: > 0 })
 353            {
 522354                currentParent.ClearMergeMemo();
 355            }
 1020356            var children = currentParent._children ??= new FieldChildren();
 1020357            result = ProcessDottedSegment(children, spanSegment.Name, spanSegment.IsLastFragment, fieldType, arguments, 
 1020358            currentParent = result;
 1020359            fieldPath = remainingPath;
 360        }
 361
 362        // Matches the per-node fast-path variant: an all-empty dotted path leaves the parent
 363        // unchanged rather than throwing.
 123364        return result ?? rootParent;
 365    }
 366
 367    /// <summary>
 368    /// Creates an exception for a dotted path that contains no non-empty segments.
 369    /// Mirrors the null/empty guard used at the public AddField boundary.
 370    /// </summary>
 371    private static ArgumentException EmptyDottedPath()
 9372        => new("Field cannot be null or empty");
 373
 374    /// <summary>
 375    /// Creates a field segment for dotted field processing.
 376    /// </summary>
 377    private static FieldDefinition CreateDottedFieldSegment(ReadOnlySpan<char> segment, ReadOnlySpan<char> fullPath, int
 378    {
 197865379        var segmentType = isLastSegment ? fieldType : Constants.ObjectFieldTypeSpan;
 197865380        var segmentPath = fullPath.Slice(0, segmentEnd);
 381
 197865382        return Helpers.CreateFieldDefinition(segment, segmentType, ReadOnlySpan<char>.Empty, null, segmentPath, null);
 383    }
 384
 385    /// <summary>
 386    /// Processes a single dotted segment with arguments and metadata — root-Dict variant.
 387    /// </summary>
 388    private static FieldDefinition ProcessDottedSegment(Dictionary<string, FieldDefinition> currentFields, ReadOnlySpan<
 389    {
 2046390        if (!currentFields.TryGetValue(segment, out var field))
 391        {
 1362392            field = CreateDottedSegmentField(segment, isLastSegment, fieldType, arguments, metadata, segmentPath);
 1362393            currentFields.SetValue(segment, field);
 1362394            return field;
 395        }
 396
 397        // This Dictionary variant only processes the FIRST segment of dotted paths, so isLastSegment
 398        // is necessarily false (single-segment paths route through GetOrAddSimpleField instead).
 684399        var existing = field!;
 684400        if (existing.ShouldConvertToObjectType())
 401        {
 309402            existing = existing with { Type = Constants.ObjectFieldType };
 309403            currentFields.SetValue(segment, existing);
 404        }
 684405        return existing;
 406    }
 407
 408    /// <summary>
 409    /// Processes a single dotted segment with arguments and metadata — FieldChildren variant.
 410    /// </summary>
 411    private static FieldDefinition ProcessDottedSegment(FieldChildren children, ReadOnlySpan<char> segment, bool isLastS
 412    {
 7065413        if (!children.TryGetValue(segment, out var field))
 414        {
 6606415            field = CreateDottedSegmentField(segment, isLastSegment, fieldType, arguments, metadata, segmentPath);
 6606416            children.Append(field);
 6606417            return field;
 418        }
 419
 561420        if (!isLastSegment) return PromoteIntermediateChildToObject(field!);
 421        // FieldBuilder normalizes empty argument dictionaries to null upstream, so a
 422        // non-null `arguments` here always has Count > 0.
 357423        return arguments is null ? field! : MergeArgumentsIntoExistingChild(children, segment, field!, arguments);
 424    }
 425
 426    // ProcessDottedFieldFastPath handles the args-null case before reaching here, and
 427    // FieldBuilder.Create normalizes empty argument dictionaries to null upstream — so by
 428    // the time we get here arguments is always a non-null dictionary with Count > 0.
 429    private static FieldDefinition MergeArgumentsIntoExistingChild(FieldChildren children, ReadOnlySpan<char> segment, F
 430    {
 354431        var merged = existing.MergeFieldArguments(arguments);
 354432        children.Set(segment, merged);
 354433        return merged;
 434    }
 435
 436    private static FieldDefinition PromoteIntermediateChildToObject(FieldDefinition existing)
 437    {
 102438        if (existing.ShouldConvertToObjectType())
 439        {
 6440            existing._type = Constants.ObjectFieldType;
 441        }
 102442        return existing;
 443    }
 444
 445    private static FieldDefinition CreateDottedSegmentField(ReadOnlySpan<char> segment, bool isLastSegment, ReadOnlySpan
 446    {
 7968447        var segmentArgs = isLastSegment ? arguments : null;
 7968448        var segmentType = isLastSegment ? fieldType : Constants.ObjectFieldTypeSpan;
 7968449        var segmentMetadata = isLastSegment ? metadata : null;
 7968450        return Helpers.CreateFieldDefinition(segment, segmentType, ReadOnlySpan<char>.Empty, segmentArgs, segmentPath, s
 451    }
 452
 453    /// <summary>
 454    /// Gets or adds a complex field with type parsing and alias handling — root-level variant.
 455    /// The Dictionary variant is the entry point from QueryBuilder.AddField; callers never pass a
 456    /// non-empty parentPath (that's used only by the per-node FieldChildren overload below). The
 457    /// parameter exists to share a signature with the FieldChildren variant via the public dispatch.
 458    /// </summary>
 459    // AddFieldCore rejects null/whitespace fieldPath at the public-API boundary, so by the time
 460    // we reach here at least one non-whitespace segment exists and result is non-null on exit.
 461#pragma warning disable S1172 // parentPath unused — kept for signature symmetry with the FieldChildren variant.
 462    private static FieldDefinition GetOrAddComplexField(Dictionary<string, FieldDefinition> fieldDefinitions, ReadOnlySp
 463#pragma warning restore S1172
 464    {
 465        // Type/alias parsing only ever shrinks the accumulated path, so fieldPath length plus slack
 466        // for separators is a safe upper bound. Mirror the dotted path: stackalloc for the common
 467        // short case, pooled fallback for long paths so we never overflow SpanPathBuilder.
 1728468        var estimatedPathLength = fieldPath.Length + 10;
 469
 1728470        if (estimatedPathLength <= 512)
 471        {
 1719472            Span<char> pathBuffer = stackalloc char[512];
 1719473            var pathBuilder = new SpanPathBuilder(pathBuffer);
 1719474            return ProcessComplexFieldSegments(fieldDefinitions, fieldPath, fieldType, arguments, metadata, ref pathBuil
 475        }
 476        else
 477        {
 9478            using var pooledArray = CharArrayPool.GetPooled(estimatedPathLength);
 9479            var pathBuilder = new SpanPathBuilder(pooledArray.AsSpan());
 9480            return ProcessComplexFieldSegments(fieldDefinitions, fieldPath, fieldType, arguments, metadata, ref pathBuil
 481        }
 9482    }
 483
 484    /// <summary>
 485    /// Gets or adds a complex field with type parsing and alias handling — per-node variant.
 486    /// </summary>
 487    private static FieldDefinition GetOrAddComplexField(FieldDefinition rootParent, ReadOnlySpan<char> fieldPath, ReadOn
 488    {
 375489        var parentPathSpan = parentPath.AsSpan();
 490
 491        // Type/alias parsing only shrinks the segment path, so parent + fieldPath + slack is a safe
 492        // upper bound. Mirror the dotted path: stackalloc for short paths, pooled fallback for long
 493        // ones so we never overflow SpanPathBuilder.
 375494        var estimatedPathLength = parentPathSpan.Length + fieldPath.Length + 10;
 495
 375496        if (estimatedPathLength <= 512)
 497        {
 369498            Span<char> pathBuffer = stackalloc char[512];
 369499            var pathBuilder = new SpanPathBuilder(pathBuffer);
 735500            if (!parentPathSpan.IsEmpty) pathBuilder.Append(parentPathSpan);
 369501            return ProcessComplexFieldSegments(rootParent, fieldPath, fieldType, arguments, metadata, ref pathBuilder);
 502        }
 503        else
 504        {
 6505            using var pooledArray = CharArrayPool.GetPooled(estimatedPathLength);
 6506            var pathBuilder = new SpanPathBuilder(pooledArray.AsSpan());
 12507            if (!parentPathSpan.IsEmpty) pathBuilder.Append(parentPathSpan);
 6508            return ProcessComplexFieldSegments(rootParent, fieldPath, fieldType, arguments, metadata, ref pathBuilder);
 509        }
 6510    }
 511
 512    private static FieldDefinition ProcessComplexFieldSegments(Dictionary<string, FieldDefinition> fieldDefinitions, Rea
 513    {
 1728514        fieldPath = Helpers.ParseFieldTypeFromPath(fieldPath, fieldType, out var parsedFieldType);
 515
 1728516        FieldDefinition? parentField = null;
 1728517        FieldDefinition? result = null;
 518
 7149519        while (fieldPath.Length > 0)
 520        {
 5430521            ExtractNextSegment(fieldPath, out var segment, out var remaining);
 522
 5421523            if (!segment.Name.IsWhiteSpace())
 524            {
 5418525                pathBuilder.Append(segment.Name);
 5418526                var typeToUse = !segment.ParsedType.IsEmpty ? segment.ParsedType : parsedFieldType;
 527
 5418528                if (parentField == null)
 529                {
 530                    // Root level
 1719531                    result = ProcessFieldSegment(fieldDefinitions, segment, arguments, typeToUse, pathBuilder.AsSpan(), 
 532                }
 533                else
 534                {
 535                    // Nested level. parentField is an ancestor of whatever this segment (or a
 536                    // later one) mutates — only the last fragment ever carries arguments, but
 537                    // every field visited on the way there must lose its memoized fingerprint.
 3699538                    if (arguments is { Count: > 0 })
 539                    {
 84540                        parentField.ClearMergeMemo();
 541                    }
 3699542                    var children = parentField._children ??= new FieldChildren();
 3699543                    result = ProcessFieldSegment(children, segment, arguments, typeToUse, pathBuilder.AsSpan(), metadata
 544                }
 545
 5418546                parentField = result;
 547            }
 548
 5421549            fieldPath = remaining;
 550        }
 551
 1719552        return result!;
 553    }
 554
 555    private static FieldDefinition ProcessComplexFieldSegments(FieldDefinition rootParent, ReadOnlySpan<char> fieldPath,
 556    {
 375557        fieldPath = Helpers.ParseFieldTypeFromPath(fieldPath, fieldType, out var parsedFieldType);
 558
 375559        var currentParent = rootParent;
 375560        FieldDefinition? result = null;
 561
 852562        while (fieldPath.Length > 0)
 563        {
 477564            ExtractNextSegment(fieldPath, out var segment, out var remaining);
 565
 477566            if (!segment.Name.IsWhiteSpace())
 567            {
 474568                pathBuilder.Append(segment.Name);
 474569                var typeToUse = !segment.ParsedType.IsEmpty ? segment.ParsedType : parsedFieldType;
 570                // currentParent is an ancestor of whatever this segment (or a later one) mutates —
 571                // see the root-Dict variant above for the full invalidation rationale.
 474572                if (arguments is { Count: > 0 })
 573                {
 3574                    currentParent.ClearMergeMemo();
 575                }
 474576                var children = currentParent._children ??= new FieldChildren();
 474577                result = ProcessFieldSegment(children, segment, arguments, typeToUse, pathBuilder.AsSpan(), metadata);
 474578                currentParent = result;
 579            }
 580
 477581            fieldPath = remaining;
 582        }
 583
 375584        return result!;
 585    }
 586
 587    /// <summary>
 588    /// Processes a field segment for complex field creation — root-Dict variant.
 589    /// </summary>
 590    private static FieldDefinition ProcessFieldSegment(Dictionary<string, FieldDefinition> currentFields, SpanSegment se
 591    {
 1719592        if (!TryGetRootFieldBySpan(currentFields, segment.Name, out var field, out var segmentKey))
 593        {
 798594            return CreateNewField(currentFields, segmentKey, segment, arguments, parsedFieldType, fullPath, metadata);
 595        }
 596
 921597        return UpdateExistingField(currentFields, segment, field, arguments, parsedFieldType);
 598    }
 599
 600    /// <summary>
 601    /// Processes a field segment for complex field creation — FieldChildren variant.
 602    /// </summary>
 603    private static FieldDefinition ProcessFieldSegment(FieldChildren children, SpanSegment segment, IDictionary<string, 
 604    {
 4173605        var field = FindExistingSegmentChild(children, segment);
 4173606        if (field is null)
 607        {
 2844608            return CreateNewField(children, segment, arguments, parsedFieldType, fullPath, metadata);
 609        }
 610
 1329611        return UpdateExistingField(children, segment, field, arguments, parsedFieldType);
 612    }
 613
 614    /// <summary>
 615    /// Finds a child matching <paramref name="segment"/>. For the LAST fragment of a complex path,
 616    /// identity is (Name, alias) — not Name alone — because two children may legitimately share a
 617    /// Name while differing by alias (distinct response keys), e.g. adding <c>"aliasA:id"</c> then
 618    /// <c>"aliasB:id"</c> under the same parent must produce two siblings, not collapse the second
 619    /// into the first. Intermediate (non-last) segments route purely by Name, exactly like dotted
 620    /// paths: a path addresses an intermediate node structurally by name, and an alias set on it
 621    /// (e.g. <c>"alias:profile.displayName:name"</c>) is rendering metadata, not routing identity —
 622    /// a LATER reference to the same node via its bare name (e.g. <c>"profile.userEmail:email"</c>)
 623    /// must still resolve to that one node, never fork a duplicate.
 624    /// </summary>
 625    private static FieldDefinition? FindExistingSegmentChild(FieldChildren children, SpanSegment segment)
 626    {
 4173627        var candidate = children.Find(segment.Name);
 6990628        if (candidate is null) return null;
 2652629        if (!segment.IsLastFragment) return candidate;
 630
 90631        if (SegmentAliasMatches(segment, candidate)) return candidate;
 632
 135633        foreach (var f in children.AsSpan())
 634        {
 39635            if (f.Name.AsSpan().Equals(segment.Name, StringComparison.OrdinalIgnoreCase) && SegmentAliasMatches(segment,
 3636                return f;
 637        }
 27638        return null;
 639    }
 640
 641    // An existing child with NO alias yet always matches — it hasn't picked a response-key identity
 642    // yet, so this segment either confirms the unaliased identity or (via ApplyIntermediateUpdates)
 643    // adopts an incoming alias onto it, exactly like the FIRST time any alias is set on a field.
 644    // Once a child HAS an alias, a segment only matches it when the aliases agree — a same-named
 645    // segment carrying a DIFFERENT (or absent) alias is a distinct sibling, not the same field.
 646    private static bool SegmentAliasMatches(SpanSegment segment, FieldDefinition field)
 99647        => field._alias is null
 99648            || (segment.HasAlias
 99649                ? segment.Alias.Equals(field._alias.AsSpan(), StringComparison.Ordinal)
 99650                : string.IsNullOrEmpty(field._alias));
 651
 652    /// <summary>
 653    /// Creates a new field for complex field processing — root-Dict variant. <paramref name="segmentKey"/>
 654    /// is the already-materialized dictionary key for <c>segment.Name</c> (computed once by the
 655    /// caller's failed probe via <see cref="TryGetRootFieldBySpan"/>), reused here for the insert
 656    /// instead of calling <see cref="ReadOnlySpan{T}.ToString"/> a second time.
 657    /// </summary>
 658    private static FieldDefinition CreateNewField(Dictionary<string, FieldDefinition> currentFields, string segmentKey, 
 659    {
 798660        var (fieldArgs, fieldMetadata) = ResolveSegmentArgsAndMetadata(segment, arguments, metadata);
 798661        var fieldType = ResolveSegmentFieldType(segment, parsedFieldType);
 798662        var field = Helpers.CreateFieldDefinition(segment.Name, fieldType, segment.Alias, fieldArgs, fullPath, fieldMeta
 798663        currentFields[segmentKey] = field;
 798664        return field;
 665    }
 666
 667    /// <summary>
 668    /// Creates a new field for complex field processing — FieldChildren variant.
 669    /// </summary>
 670    private static FieldDefinition CreateNewField(FieldChildren children, SpanSegment segment, IDictionary<string, objec
 671    {
 2844672        var (fieldArgs, fieldMetadata) = ResolveSegmentArgsAndMetadata(segment, arguments, metadata);
 2844673        var fieldType = ResolveSegmentFieldType(segment, parsedFieldType);
 2844674        var field = Helpers.CreateFieldDefinition(segment.Name, fieldType, segment.Alias, fieldArgs, fullPath, fieldMeta
 2844675        children.Append(field);
 2844676        return field;
 677    }
 678
 679    private static (IDictionary<string, object?>? args, Dictionary<string, object?>? meta) ResolveSegmentArgsAndMetadata
 3642680        => segment.IsLastFragment ? (arguments, metadata) : (null, null);
 681
 682    private static ReadOnlySpan<char> ResolveSegmentFieldType(SpanSegment segment, ReadOnlySpan<char> parsedFieldType)
 683    {
 5685684        if (segment.IsLastFragment) return segment.HasParsedType ? segment.ParsedType : parsedFieldType;
 1599685        return segment.HasParsedType && segment.ParsedType.SequenceEqual(Constants.ArrayTypeMarkerSpan)
 1599686            ? Constants.ArrayTypeMarkerSpan
 1599687            : Constants.ObjectFieldTypeSpan;
 688    }
 689
 690    /// <summary>
 691    /// Updates an existing field during complex field processing — root-Dict variant.
 692    /// </summary>
 693    private static FieldDefinition UpdateExistingField(Dictionary<string, FieldDefinition> currentFields, SpanSegment se
 694    {
 1824695        if (!ApplyIntermediateUpdates(segment, field)) return field;
 696
 18697        if (arguments?.Count > 0)
 698        {
 699            // field was found via a case-insensitive lookup keyed by this exact string (set at
 700            // insertion time in CreateNewField), so reusing it here — rather than re-materializing
 701            // segment.Name — both avoids a second allocation and targets the correct existing slot.
 3702            var fieldKey = field.Name;
 3703            field = currentFields[fieldKey] = field.MergeFieldArguments(arguments);
 704        }
 18705        ApplyParsedFieldType(field, parsedFieldType);
 18706        return field;
 707    }
 708
 709    private static FieldDefinition UpdateExistingField(FieldChildren children, SpanSegment segment, FieldDefinition fiel
 710    {
 2625711        if (!ApplyIntermediateUpdates(segment, field)) return field;
 712
 33713        if (arguments?.Count > 0)
 714        {
 9715            var mergedField = field.MergeFieldArguments(arguments);
 716            // Replace by REFERENCE: field was matched by (Name, alias) in FindExistingSegmentChild,
 717            // and a name-keyed Set could instead overwrite a different same-named/different-alias sibling.
 9718            children.ReplaceReference(field, mergedField);
 9719            field = mergedField;
 720        }
 33721        ApplyParsedFieldType(field, parsedFieldType);
 33722        return field;
 723    }
 724
 725    // Mutates intermediate-segment metadata (alias, object-promotion). Returns true when the
 726    // segment is the last fragment and the caller should continue with last-fragment updates.
 727    private static bool ApplyIntermediateUpdates(SpanSegment segment, FieldDefinition field)
 728    {
 729        // segment.HasAlias <=> !Alias.IsEmpty by SpanSegment's invariant.
 2250730        if (segment.HasAlias && field._alias is null)
 731        {
 15732            field._alias = segment.Alias.ToString();
 733        }
 2250734        if (!segment.IsLastFragment && field.ShouldConvertToObjectType())
 735        {
 9736            field._type = Constants.ObjectFieldType;
 737        }
 2250738        return segment.IsLastFragment;
 739    }
 740
 741    private static void ApplyParsedFieldType(FieldDefinition field, ReadOnlySpan<char> parsedFieldType)
 742    {
 51743        if (!parsedFieldType.Equals(Constants.DefaultFieldTypeSpan, StringComparison.OrdinalIgnoreCase)
 51744            && !field._type.AsSpan().Equals(parsedFieldType, StringComparison.OrdinalIgnoreCase))
 745        {
 6746            field._type = parsedFieldType.ToString();
 747        }
 51748    }
 749
 750    /// <summary>
 751    /// Creates or merges a field definition into the target collection — root-Dict variant.
 752    /// </summary>
 753    internal static FieldDefinition CreateOrMergeField(Dictionary<string, FieldDefinition> fields, FieldDefinition field
 754    {
 755        // Try to find existing field to merge with
 282756        var existingField = Helpers.FindExistingField(fields, fieldDefinition);
 282757        if (existingField != null)
 758        {
 183759            var mergedField = existingField.MergeFieldArguments(fieldDefinition._arguments);
 183760            fields[existingField.Name] = mergedField;
 183761            return mergedField;
 762        }
 763
 99764        var newField = CloneFieldDefinitionForMerge(fieldDefinition);
 99765        fields[fieldDefinition.Name] = newField;
 99766        return newField;
 767    }
 768
 769    /// <summary>
 770    /// Creates or merges a field definition into the target collection — FieldChildren variant.
 771    /// </summary>
 772    internal static FieldDefinition CreateOrMergeField(FieldChildren children, FieldDefinition fieldDefinition)
 773    {
 411774        var existingField = Helpers.FindExistingField(children, fieldDefinition);
 411775        if (existingField != null)
 776        {
 57777            var mergedField = existingField.MergeFieldArguments(fieldDefinition._arguments);
 778            // Replace by REFERENCE, not by name: existingField was matched by (Name, alias) above,
 779            // and a name-keyed replace could instead overwrite a different same-named sibling that
 780            // carries a different alias (see FieldChildren.ReplaceReference).
 57781            children.ReplaceReference(existingField, mergedField);
 57782            return mergedField;
 783        }
 784
 354785        var newField = CloneFieldDefinitionForMerge(fieldDefinition);
 354786        children.Append(newField);
 354787        return newField;
 788    }
 789
 790    private static FieldDefinition CloneFieldDefinitionForMerge(FieldDefinition fieldDefinition)
 791    {
 453792        return Helpers.CreateFieldDefinition(
 453793            fieldDefinition.Name,
 453794            fieldDefinition._type.AsSpan(),
 453795            fieldDefinition._alias,
 453796            fieldDefinition._arguments,
 453797            fieldDefinition.Path,
 453798            fieldDefinition._metadata);
 799    }
 800
 801    private static void ExtractDottedSegment(ReadOnlySpan<char> fieldPath, int pathStart, out SpanSegment segment, out i
 802    {
 216879803        var dotIndex = fieldPath.Slice(pathStart).IndexOf('.');
 216879804        var isLastSegment = dotIndex == -1;
 216879805        var segmentEnd = isLastSegment ? fieldPath.Length : pathStart + dotIndex;
 216879806        var segmentSpan = fieldPath.Slice(pathStart, segmentEnd - pathStart);
 216879807        nextStart = isLastSegment ? fieldPath.Length : segmentEnd + 1;
 808
 216879809        segment = new SpanSegment(segmentSpan, ReadOnlySpan<char>.Empty, isLastSegment, ReadOnlySpan<char>.Empty);
 216879810    }
 811
 812    private static void ExtractDottedSegmentWithPath(ReadOnlySpan<char> fieldPath, out SpanSegment segment, out ReadOnly
 813    {
 9135814        var dotIndex = fieldPath.IndexOf('.');
 9135815        var isLastSegment = dotIndex == -1;
 9135816        var segmentSpan = isLastSegment ? fieldPath : fieldPath[..dotIndex];
 9135817        remainingPath = isLastSegment ? ReadOnlySpan<char>.Empty : fieldPath[(dotIndex + 1)..];
 818
 9135819        segment = new SpanSegment(segmentSpan, ReadOnlySpan<char>.Empty, isLastSegment, ReadOnlySpan<char>.Empty);
 9135820    }
 821
 822    private static void ExtractNextSegment(ReadOnlySpan<char> fieldPath, out SpanSegment segment, out ReadOnlySpan<char>
 823    {
 5907824        var nextDot = fieldPath.IndexOf('.');
 5907825        var isLastFragment = nextDot == -1;
 5907826        var currentPart = isLastFragment ? fieldPath : fieldPath[..nextDot];
 5907827        var trimmedPart = currentPart.Trim();
 828
 829        // Parse and remove type information from the segment
 5907830        var cleanedPath = Helpers.ParseFieldTypeFromPath(trimmedPart, Constants.DefaultFieldType, out var parsedType);
 831
 832        // Parse field name and alias from cleanedPath: only exactly 2 non-empty trimmed
 833        // colon-separated parts mean alias:name. Span-based equivalent of
 834        // Split(':', TrimEntries | RemoveEmptyEntries) without the string/array allocations.
 5907835        ParseAliasAndName(cleanedPath, out var name, out var alias, out var malformed);
 836
 837        // Only include parsed type if it's not the default
 5907838        var typeToInclude = parsedType.SequenceEqual(Constants.DefaultFieldTypeSpan) ? ReadOnlySpan<char>.Empty : parsed
 839
 840        // Reject a colon that produced an empty alias/name part (e.g. "a : b" → ": b", or
 841        // "alias:"): folding the stray colon back into the field name emits invalid GraphQL such
 842        // as a field literally named ": b". Throw a clear error instead.
 5907843        if (malformed)
 844        {
 9845            throw MalformedComplexSegment(trimmedPart);
 846        }
 847
 5898848        segment = new SpanSegment(name, alias, isLastFragment, typeToInclude);
 5898849        remaining = isLastFragment ? ReadOnlySpan<char>.Empty : fieldPath[(nextDot + 1)..];
 5898850    }
 851
 852    private static ArgumentException MalformedComplexSegment(ReadOnlySpan<char> segment)
 9853        => new($"Malformed field segment '{segment.ToString()}'. Use 'alias:name' (no spaces around ':') and put any typ
 854
 855    /// <summary>
 856    /// Splits <paramref name="cleanedPath"/> on ':' into trimmed non-empty parts. Exactly two
 857    /// parts mean <c>alias:name</c>; a colon-free segment is the name with no alias. A colon that
 858    /// yields an empty alias or name part (e.g. <c>": name"</c> or <c>"alias:"</c>) sets
 859    /// <paramref name="malformed"/> so the caller can reject it rather than folding the stray
 860    /// colon back into a field name and emitting invalid GraphQL. Three or more parts still fall
 861    /// back to treating the whole segment as the name (unchanged from prior behavior).
 862    /// </summary>
 863    private static void ParseAliasAndName(ReadOnlySpan<char> cleanedPath, out ReadOnlySpan<char> name, out ReadOnlySpan<
 864    {
 5907865        malformed = false;
 866
 5907867        if (cleanedPath.IndexOf(':') < 0)
 868        {
 3513869            name = cleanedPath.Trim();
 3513870            alias = ReadOnlySpan<char>.Empty;
 3513871            return;
 872        }
 873
 4788874        ReadOnlySpan<char> first = default, second = default;
 2394875        var partCount = 0;
 2394876        var emptyPartSeen = false;
 2394877        var rest = cleanedPath;
 2415878        while (true)
 879        {
 4809880            var colonIndex = rest.IndexOf(':');
 4809881            var part = (colonIndex < 0 ? rest : rest[..colonIndex]).Trim();
 4809882            if (part.IsEmpty)
 883            {
 27884                emptyPartSeen = true;
 885            }
 886            else
 887            {
 7176888                if (partCount == 0) first = part;
 4773889                else if (partCount == 1) second = part;
 4782890                partCount++;
 4782891                if (partCount > 2) break;
 892            }
 4806893            if (colonIndex < 0) break;
 2415894            rest = rest[(colonIndex + 1)..];
 895        }
 896
 2394897        if (partCount == 2)
 898        {
 2382899            alias = first;
 2382900            name = second;
 2382901            return;
 902        }
 903
 904        // A colon that produced an empty alias/name part (e.g. ": name") is malformed: surface it
 905        // instead of folding the stray colon back into the name. Other shapes (three or more
 906        // parts) keep the historical whole-segment-as-name fallback.
 12907        name = cleanedPath.Trim();
 12908        alias = ReadOnlySpan<char>.Empty;
 12909        malformed = emptyPartSeen && partCount < 2;
 12910    }
 911}

Methods/Properties

GetOrAddField(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
GetOrAddField(NGql.Core.Abstractions.FieldDefinition,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
GetOrAddDottedField(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
GetOrAddDottedField(NGql.Core.Abstractions.FieldDefinition,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
ProcessDottedFieldFastPath(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>)
ProcessDottedFieldFastPath(NGql.Core.Abstractions.FieldDefinition,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>)
GetOrCreateRootSegment(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,NGql.Core.Abstractions.SpanSegment,System.ReadOnlySpan`1<System.Char>,System.Int32,System.ReadOnlySpan`1<System.Char>)
TryGetRootFieldBySpan(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,System.ReadOnlySpan`1<System.Char>,NGql.Core.Abstractions.FieldDefinition&,System.String&)
TryGetRootFieldBySpan(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,System.ReadOnlySpan`1<System.Char>,NGql.Core.Abstractions.FieldDefinition&,System.String&)
GetOrCreateChildSegment(NGql.Core.Abstractions.FieldDefinition,NGql.Core.Abstractions.SpanSegment,System.ReadOnlySpan`1<System.Char>,System.Int32,System.ReadOnlySpan`1<System.Char>)
PromoteToObjectIfNeeded(NGql.Core.Abstractions.FieldDefinition,System.Boolean)
ProcessDottedFieldWithMetadata(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
ProcessDottedFieldWithMetadata(NGql.Core.Abstractions.FieldDefinition,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
ProcessDottedFieldSegments(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.Object>,NGql.Core.Builders.SpanPathBuilder&)
ProcessDottedFieldSegments(NGql.Core.Abstractions.FieldDefinition,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.Object>,NGql.Core.Builders.SpanPathBuilder&)
EmptyDottedPath()
CreateDottedFieldSegment(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Int32,System.Boolean,System.ReadOnlySpan`1<System.Char>)
ProcessDottedSegment(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,System.ReadOnlySpan`1<System.Char>,System.Boolean,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.ReadOnlySpan`1<System.Char>)
ProcessDottedSegment(NGql.Core.Abstractions.FieldChildren,System.ReadOnlySpan`1<System.Char>,System.Boolean,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.ReadOnlySpan`1<System.Char>)
MergeArgumentsIntoExistingChild(NGql.Core.Abstractions.FieldChildren,System.ReadOnlySpan`1<System.Char>,NGql.Core.Abstractions.FieldDefinition,System.Collections.Generic.IDictionary`2<System.String,System.Object>)
PromoteIntermediateChildToObject(NGql.Core.Abstractions.FieldDefinition)
CreateDottedSegmentField(System.ReadOnlySpan`1<System.Char>,System.Boolean,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.ReadOnlySpan`1<System.Char>)
GetOrAddComplexField(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
GetOrAddComplexField(NGql.Core.Abstractions.FieldDefinition,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.String,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
ProcessComplexFieldSegments(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.Object>,NGql.Core.Builders.SpanPathBuilder&)
ProcessComplexFieldSegments(NGql.Core.Abstractions.FieldDefinition,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.Object>,NGql.Core.Builders.SpanPathBuilder&)
ProcessFieldSegment(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,NGql.Core.Abstractions.SpanSegment,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
ProcessFieldSegment(NGql.Core.Abstractions.FieldChildren,NGql.Core.Abstractions.SpanSegment,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
FindExistingSegmentChild(NGql.Core.Abstractions.FieldChildren,NGql.Core.Abstractions.SpanSegment)
SegmentAliasMatches(NGql.Core.Abstractions.SpanSegment,NGql.Core.Abstractions.FieldDefinition)
CreateNewField(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,System.String,NGql.Core.Abstractions.SpanSegment,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
CreateNewField(NGql.Core.Abstractions.FieldChildren,NGql.Core.Abstractions.SpanSegment,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
ResolveSegmentArgsAndMetadata(NGql.Core.Abstractions.SpanSegment,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.Object>)
ResolveSegmentFieldType(NGql.Core.Abstractions.SpanSegment,System.ReadOnlySpan`1<System.Char>)
UpdateExistingField(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,NGql.Core.Abstractions.SpanSegment,NGql.Core.Abstractions.FieldDefinition,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.ReadOnlySpan`1<System.Char>)
UpdateExistingField(NGql.Core.Abstractions.FieldChildren,NGql.Core.Abstractions.SpanSegment,NGql.Core.Abstractions.FieldDefinition,System.Collections.Generic.IDictionary`2<System.String,System.Object>,System.ReadOnlySpan`1<System.Char>)
ApplyIntermediateUpdates(NGql.Core.Abstractions.SpanSegment,NGql.Core.Abstractions.FieldDefinition)
ApplyParsedFieldType(NGql.Core.Abstractions.FieldDefinition,System.ReadOnlySpan`1<System.Char>)
CreateOrMergeField(System.Collections.Generic.Dictionary`2<System.String,NGql.Core.Abstractions.FieldDefinition>,NGql.Core.Abstractions.FieldDefinition)
CreateOrMergeField(NGql.Core.Abstractions.FieldChildren,NGql.Core.Abstractions.FieldDefinition)
CloneFieldDefinitionForMerge(NGql.Core.Abstractions.FieldDefinition)
ExtractDottedSegment(System.ReadOnlySpan`1<System.Char>,System.Int32,NGql.Core.Abstractions.SpanSegment&,System.Int32&)
ExtractDottedSegmentWithPath(System.ReadOnlySpan`1<System.Char>,NGql.Core.Abstractions.SpanSegment&,System.ReadOnlySpan`1<System.Char>&)
ExtractNextSegment(System.ReadOnlySpan`1<System.Char>,NGql.Core.Abstractions.SpanSegment&,System.ReadOnlySpan`1<System.Char>&)
MalformedComplexSegment(System.ReadOnlySpan`1<System.Char>)
ParseAliasAndName(System.ReadOnlySpan`1<System.Char>,System.ReadOnlySpan`1<System.Char>&,System.ReadOnlySpan`1<System.Char>&,System.Boolean&)