Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ private static void AnalyzeMethodDeclaration(SyntaxNodeAnalysisContext context)
Errors.DataLoaderReturnTypeInvalid,
methodDeclaration.ReturnType.GetLocation(),
contract.Kind is DataLoaderKind.Batch
? "Task/ValueTask of IReadOnlyDictionary<TKey, TValue> or IDictionary<TKey, TValue>"
? "Task/ValueTask of IReadOnlyDictionary<TKey, TValue>, IDictionary<TKey, TValue>, or Dictionary<TKey, TValue>"
: "Task/ValueTask of TValue"));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,10 +234,12 @@ public static bool HasValidReturnType(
var readOnlyDictionary = compilation.GetTypeByMetadataName(
"System.Collections.Generic.IReadOnlyDictionary`2");
var dictionary = compilation.GetTypeByMetadataName("System.Collections.Generic.IDictionary`2");
var concreteDictionary = compilation.GetTypeByMetadataName("System.Collections.Generic.Dictionary`2");

return resultType is INamedTypeSymbol { TypeArguments.Length: 2 } namedType
&& (SymbolEqualityComparer.Default.Equals(namedType.ConstructedFrom, readOnlyDictionary)
|| SymbolEqualityComparer.Default.Equals(namedType.ConstructedFrom, dictionary))
|| SymbolEqualityComparer.Default.Equals(namedType.ConstructedFrom, dictionary)
|| SymbolEqualityComparer.Default.Equals(namedType.ConstructedFrom, concreteDictionary))
&& SymbolEqualityComparer.Default.Equals(namedType.TypeArguments[0], contract.KeyType)
&& SymbolEqualityComparer.Default.Equals(namedType.TypeArguments[1], contract.ValueType);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,10 @@ private static bool IsDictionary(
wellKnownTypes.ReadOnlyDictionary)
|| SymbolEqualityComparer.Default.Equals(
namedType.ConstructedFrom,
wellKnownTypes.DictionaryInterface))
wellKnownTypes.DictionaryInterface)
|| SymbolEqualityComparer.Default.Equals(
namedType.ConstructedFrom,
wellKnownTypes.Dictionary))
&& namedType.TypeArguments[0].Equals(keyType, SymbolEqualityComparer.Default)
&& namedType.TypeArguments[1].Equals(valueType, SymbolEqualityComparer.Default);

Expand Down Expand Up @@ -329,6 +332,7 @@ private sealed class DataLoaderSymbols(
INamedTypeSymbol readOnlyList,
INamedTypeSymbol readOnlyDictionary,
INamedTypeSymbol dictionaryInterface,
INamedTypeSymbol dictionary,
INamedTypeSymbol task,
INamedTypeSymbol valueTask)
{
Expand All @@ -344,6 +348,8 @@ private sealed class DataLoaderSymbols(

public INamedTypeSymbol DictionaryInterface { get; } = dictionaryInterface;

public INamedTypeSymbol Dictionary { get; } = dictionary;

public INamedTypeSymbol Task { get; } = task;

public INamedTypeSymbol ValueTask { get; } = valueTask;
Expand All @@ -360,6 +366,8 @@ private sealed class DataLoaderSymbols(
"System.Collections.Generic.IReadOnlyDictionary`2");
var dictionaryInterface = compilation.GetTypeByMetadataName(
"System.Collections.Generic.IDictionary`2");
var dictionary = compilation.GetTypeByMetadataName(
"System.Collections.Generic.Dictionary`2");
var task = compilation.GetTypeByMetadataName("System.Threading.Tasks.Task`1");
var valueTask = compilation.GetTypeByMetadataName("System.Threading.Tasks.ValueTask`1");

Expand All @@ -369,6 +377,7 @@ private sealed class DataLoaderSymbols(
&& readOnlyList is not null
&& readOnlyDictionary is not null
&& dictionaryInterface is not null
&& dictionary is not null
&& task is not null
&& valueTask is not null
? new DataLoaderSymbols(
Expand All @@ -378,6 +387,7 @@ private sealed class DataLoaderSymbols(
readOnlyList,
readOnlyDictionary,
dictionaryInterface,
dictionary,
task,
valueTask)
: null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,7 @@ public sealed class NestedAttribute<T> : System.Attribute { }
"",
"IBatchDataLoader<int, string>",
"Task<Dictionary<int, string>> GetAsync(IReadOnlyList<int> keys) => default!;"),
null
DataLoaderKind.Batch
];
yield return
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ internal static Task<ILookup<int, string>> GetInvalidBatchReturnTypeAsync(

[DataLoader<ICacheDataLoader<int, string>>]
internal static Task<int> GetInvalidCacheReturnTypeAsync(int key) => default!;

[DataLoader<IBatchDataLoader<int, string>>]
internal static Task<Dictionary<string, string>> GetInvalidDictionaryKeyTypeAsync(
IReadOnlyList<int> keys)
=> default!;

[DataLoader<IBatchDataLoader<int, string>>]
internal static ValueTask<Dictionary<int, int>> GetInvalidDictionaryValueTypeAsync(
IReadOnlyList<int> keys)
=> default;
}
""";

Expand Down Expand Up @@ -246,6 +256,8 @@ namespace TestNamespace;

internal interface IEntityByIdDataLoader : IBatchDataLoader<int, string> { }

internal interface IEntitiesByIdDataLoader : IBatchDataLoader<int, string[]> { }

internal static class TestClass
{
[DataLoader<IEntityByIdDataLoader>]
Expand All @@ -255,6 +267,16 @@ internal static ValueTask<IDictionary<int, string>> GetByIdAsync(

[DataLoader<ICacheDataLoader<int, string>>]
internal static ValueTask<string> GetByKeyAsync(int key) => default;

[DataLoader<IBatchDataLoader<int, string>>]
internal static Task<Dictionary<int, string>> GetDictionaryByIdAsync(
IReadOnlyList<int> keys)
=> Task.FromResult(new Dictionary<int, string>());

[DataLoader<IEntitiesByIdDataLoader>]
internal static ValueTask<Dictionary<int, string[]>> GetDictionaryGroupsByIdAsync(
IReadOnlyList<int> keys)
=> new(new Dictionary<int, string[]>());
}
""";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"WarningLevel": 0,
"Location": ": (12,20)-(12,62)",
"MessageFormat": "The return type of a [DataLoader<T>] method must be {0}",
"Message": "The return type of a [DataLoader<T>] method must be Task/ValueTask of IReadOnlyDictionary<TKey, TValue> or IDictionary<TKey, TValue>",
"Message": "The return type of a [DataLoader<T>] method must be Task/ValueTask of IReadOnlyDictionary<TKey, TValue>, IDictionary<TKey, TValue>, or Dictionary<TKey, TValue>",
"Category": "DataLoader",
"CustomTags": []
},
Expand All @@ -20,7 +20,7 @@
"WarningLevel": 0,
"Location": ": (17,20)-(17,71)",
"MessageFormat": "The return type of a [DataLoader<T>] method must be {0}",
"Message": "The return type of a [DataLoader<T>] method must be Task/ValueTask of IReadOnlyDictionary<TKey, TValue> or IDictionary<TKey, TValue>",
"Message": "The return type of a [DataLoader<T>] method must be Task/ValueTask of IReadOnlyDictionary<TKey, TValue>, IDictionary<TKey, TValue>, or Dictionary<TKey, TValue>",
"Category": "DataLoader",
"CustomTags": []
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"WarningLevel": 0,
"Location": ": (32,20)-(32,46)",
"MessageFormat": "The return type of a [DataLoader<T>] method must be {0}",
"Message": "The return type of a [DataLoader<T>] method must be Task/ValueTask of IReadOnlyDictionary<TKey, TValue> or IDictionary<TKey, TValue>",
"Message": "The return type of a [DataLoader<T>] method must be Task/ValueTask of IReadOnlyDictionary<TKey, TValue>, IDictionary<TKey, TValue>, or Dictionary<TKey, TValue>",
"Category": "DataLoader",
"CustomTags": []
},
Expand All @@ -67,6 +67,28 @@
"Message": "The return type of a [DataLoader<T>] method must be Task/ValueTask of TValue",
"Category": "DataLoader",
"CustomTags": []
},
{
"Id": "HC0124",
"Title": "Invalid DataLoader Return Type",
"Severity": "Error",
"WarningLevel": 0,
"Location": ": (40,20)-(40,52)",
"MessageFormat": "The return type of a [DataLoader<T>] method must be {0}",
"Message": "The return type of a [DataLoader<T>] method must be Task/ValueTask of IReadOnlyDictionary<TKey, TValue>, IDictionary<TKey, TValue>, or Dictionary<TKey, TValue>",
"Category": "DataLoader",
"CustomTags": []
},
{
"Id": "HC0124",
"Title": "Invalid DataLoader Return Type",
"Severity": "Error",
"WarningLevel": 0,
"Location": ": (45,20)-(45,51)",
"MessageFormat": "The return type of a [DataLoader<T>] method must be {0}",
"Message": "The return type of a [DataLoader<T>] method must be Task/ValueTask of IReadOnlyDictionary<TKey, TValue>, IDictionary<TKey, TValue>, or Dictionary<TKey, TValue>",
"Category": "DataLoader",
"CustomTags": []
}
]
```
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,104 @@ namespace TestNamespace
}
}
}

/// <summary>
/// A DataLoader generated from <see cref="global::TestNamespace.TestClass.GetDictionaryByIdAsync(global::System.Collections.Generic.IReadOnlyList&lt;int&gt;)"/>.
/// </summary>
public sealed partial class DictionaryByIdDataLoader
: global::GreenDonut.DataLoaderBase<int, string>
, global::GreenDonut.IBatchDataLoader<int, string>
{
private readonly global::System.IServiceProvider _services;

public DictionaryByIdDataLoader(
global::System.IServiceProvider services,
global::GreenDonut.IBatchScheduler batchScheduler,
global::GreenDonut.DataLoaderOptions options)
: base(batchScheduler, options)
{
_services = services ??
throw new global::System.ArgumentNullException(nameof(services));
}

protected override async global::System.Threading.Tasks.ValueTask FetchAsync(
global::System.Collections.Generic.IReadOnlyList<int> keys,
global::System.Memory<GreenDonut.Result<string?>> results,
global::GreenDonut.DataLoaderFetchContext<string> context,
global::System.Threading.CancellationToken ct)
{
var temp = await global::TestNamespace.TestClass.GetDictionaryByIdAsync(keys).ConfigureAwait(false);
CopyResults(keys, results.Span, temp);
}

private void CopyResults(
global::System.Collections.Generic.IReadOnlyList<int> keys,
global::System.Span<GreenDonut.Result<string?>> results,
global::System.Collections.Generic.Dictionary<int, string> resultMap)
{
for (var i = 0; i < keys.Count; i++)
{
var key = keys[i];
if (resultMap.TryGetValue(key, out var value))
{
results[i] = global::GreenDonut.Result<string?>.Resolve(value);
}
else
{
results[i] = global::GreenDonut.Result<string?>.Resolve(default(string));
}
}
}
}

/// <summary>
/// A DataLoader generated from <see cref="global::TestNamespace.TestClass.GetDictionaryGroupsByIdAsync(global::System.Collections.Generic.IReadOnlyList&lt;int&gt;)"/>.
/// </summary>
public sealed partial class DictionaryGroupsByIdDataLoader
: global::GreenDonut.DataLoaderBase<int, string[]>
, global::TestNamespace.IEntitiesByIdDataLoader
{
private readonly global::System.IServiceProvider _services;

public DictionaryGroupsByIdDataLoader(
global::System.IServiceProvider services,
global::GreenDonut.IBatchScheduler batchScheduler,
global::GreenDonut.DataLoaderOptions options)
: base(batchScheduler, options)
{
_services = services ??
throw new global::System.ArgumentNullException(nameof(services));
}

protected override async global::System.Threading.Tasks.ValueTask FetchAsync(
global::System.Collections.Generic.IReadOnlyList<int> keys,
global::System.Memory<GreenDonut.Result<string[]?>> results,
global::GreenDonut.DataLoaderFetchContext<string[]> context,
global::System.Threading.CancellationToken ct)
{
var temp = await global::TestNamespace.TestClass.GetDictionaryGroupsByIdAsync(keys).ConfigureAwait(false);
CopyResults(keys, results.Span, temp);
}

private void CopyResults(
global::System.Collections.Generic.IReadOnlyList<int> keys,
global::System.Span<GreenDonut.Result<string[]?>> results,
global::System.Collections.Generic.Dictionary<int, string[]> resultMap)
{
for (var i = 0; i < keys.Count; i++)
{
var key = keys[i];
if (resultMap.TryGetValue(key, out var value))
{
results[i] = global::GreenDonut.Result<string[]?>.Resolve(value);
}
else
{
results[i] = global::GreenDonut.Result<string[]?>.Resolve(default(string[]));
}
}
}
}
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,12 +260,12 @@ public static async Task<IReadOnlyDictionary<int, User>> GetUserByIdAsync(

The method signature must match the selected contract:

| Contract | First parameter | Return type |
| -------------------------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `IBatchDataLoader<TKey, TValue>` | `IReadOnlyList<TKey>` | `Task<IReadOnlyDictionary<TKey, TValue>>`, `ValueTask<IReadOnlyDictionary<TKey, TValue>>`, `Task<IDictionary<TKey, TValue>>`, or `ValueTask<IDictionary<TKey, TValue>>` |
| `ICacheDataLoader<TKey, TValue>` | `TKey` | `Task<TValue>` or `ValueTask<TValue>` |
| Contract | First parameter | Return type |
| -------------------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `IBatchDataLoader<TKey, TValue>` | `IReadOnlyList<TKey>` | `Task<T>` or `ValueTask<T>` where `T` is `IReadOnlyDictionary<TKey, TValue>`, `IDictionary<TKey, TValue>`, or `Dictionary<TKey, TValue>` |
| `ICacheDataLoader<TKey, TValue>` | `TKey` | `Task<TValue>` or `ValueTask<TValue>` |

Group loading uses the batch contract with an array value. For example, `IBatchDataLoader<int, Order[]>` requires a method returning `Task<IReadOnlyDictionary<int, Order[]>>`. `[DataLoader<T>]` batch methods do not accept `ILookup<TKey, TValue>` return types.
Group loading uses the batch contract with an array value. For example, `IBatchDataLoader<int, Order[]>` can use a method returning `Task<Dictionary<int, Order[]>>`. `[DataLoader<T>]` batch methods do not accept `ILookup<TKey, TValue>` return types.

When a closed `T` is used by one `[DataLoader<T>]` method in an assembly, it is registered for dependency injection and can be injected by interface. When multiple methods use the same closed `T`, each generated DataLoader is registered only by its concrete class. Inject the generated class in that case.

Expand Down
Loading