Skip to content

Mapping a nested list onto a readonly list can lead to nullreference exception when the source list is null. #2303

Description

@wieke-osw

Please do the checklist before filing an issue:

  • I have read the documentation, including the FAQ
  • I can reproduce the bug using the latest prerelease version
  • I have searched existing discussion and issue to avoid duplicates

Describe the bug
When mapping a nested list onto a readonly list the null check on the source list is done improperly and can lead to a nullreference exception and possibly a CS8602 warning.

It is similar to already reported issues (e.g. #1650) but it concerns the source object not the target being null.

Repro
https://github.com/wieke-osw/FailingMap

I was unable to reproduce the CS8602 warning. However the problematic part of the code is basically the same.

Declaration code

using Riok.Mapperly.Abstractions;
using System.Collections.Generic;

namespace FailingMapTest;

[Mapper]
public static partial class TestMap
{
    [MapProperty(nameof(@Source.Nested.Items), nameof(Target.Items))]
    public static partial Target Map(Source source);
}

public class Source
{
    public Nested Nested { get; set; }
}

public class Nested
{
    public IEnumerable<int> Items { get; set; }
}

public class Target()
{
    public readonly List<int> Items = [];
}

public class Test
{
    [Fact]
    public void Fails()
    {
        var target = TestMap.Map(new Source());
        Assert.Empty(target.Items);
    }
}

Actual relevant generated code

public static partial global::ConsoleApp.Target? Map(global::ConsoleApp.Source? source)
{
    if (source == null)
        return default;
    var target = new global::ConsoleApp.Target();
    if (source.Nested.Items != null && target.Items != null) // This check will fail if source.Nested is null
    {
        if (global::System.Linq.Enumerable.TryGetNonEnumeratedCount(source.Nested.Items, out var sourceCount))
        {
            target.Items.EnsureCapacity(sourceCount + target.Items.Count);
        }
        foreach (var item in source.Nested.Items)
        {
            target.Items.Add(item);
        }
    }
    return target;
}

Expected relevant generated code

public static partial global::ConsoleApp.Target? Map(global::ConsoleApp.Source? source)
{
    if (source == null)
        return default;
    var target = new global::ConsoleApp.Target();
    if (source.Nested?.Items != null && target.Items != null) // Added a null-conditional operator ?
    {
        if (global::System.Linq.Enumerable.TryGetNonEnumeratedCount(source.Nested.Items, out var sourceCount))
        {
            target.Items.EnsureCapacity(sourceCount + target.Items.Count);
        }
        foreach (var item in source.Nested.Items)
        {
            target.Items.Add(item);
        }
    }
    return target;
}

Reported relevant diagnostics
I am getting this warning in my non-reproduction code, not in my reproduction project.

  • CS8602 Dereference of a possibly null reference.

Environment (please complete the following information):

  • Mapperly Version: 5.0.0-next.8
  • Nullable reference types: disabled
  • .NET Version: 10.0.300
  • Target Framework: .net10.0
  • Compiler Version: 5.6.0-2.26230.15 (a0ccfc09f8af4a6d4756397fa89c65efd95f70a6)
  • C# Language Version: 14.0
  • IDE: Visual Studio 2026 18.6.2
  • OS: Windows 11

Additional context
This only happens if the target list is readonly, otherwise it will generate a null coalesing nullcheck.

public static partial global::FailingMapTest.Target? Map(global::FailingMapTest.Source? source)
{
    if (source == null)
        return default;
    var target = new global::FailingMapTest.Target();
    if (source.Nested?.Items != null)
    {
        target.Items = global::System.Linq.Enumerable.ToList(source.Nested.Items);
    }
    else
    {
        target.Items = null;
    }
    return target;
}

(But I cannot change the target property in the non-reproduction project.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions