Skip to content

Commit

Permalink
Introduce IOwned<out T> and IAsyncOwned<out T> (#158)
Browse files Browse the repository at this point in the history
Introduce `IOwned<out T>` and `IAsyncOwned<out T>`.

These can be resolved directly.

Also allow passing a null disposeActions to Owned/AsyncOwned.
  • Loading branch information
jnm2 authored Oct 23, 2021
1 parent 34d2a8a commit f983e81
Show file tree
Hide file tree
Showing 7 changed files with 313 additions and 16 deletions.
9 changes: 8 additions & 1 deletion StrongInject.Generator/InstanceSourcesScope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,14 @@ private bool TryGetUnderlyingSource(ITypeSymbol target, out InstanceSource insta

if (target.IsWellKnownOwnedType(_wellKnownTypes, out var isAsync, out var valueType))
{
instanceSource = new OwnedSource(target, valueType, isAsync);
var concreteOwnedType = (isAsync ? _wellKnownTypes.AsyncOwned : _wellKnownTypes.Owned).Construct(valueType);
instanceSource = new OwnedSource(concreteOwnedType, valueType, isAsync);

if (target.TypeKind == TypeKind.Interface)
{
instanceSource = ForwardedInstanceSource.Create((INamedTypeSymbol)target, instanceSource);
}

return true;
}

Expand Down
8 changes: 6 additions & 2 deletions StrongInject.Generator/RoslynExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,12 @@ public static bool IsWellKnownTaskType(this ITypeSymbol type, WellKnownTypes wel

public static bool IsWellKnownOwnedType(this ITypeSymbol type, WellKnownTypes wellKnownTypes, out bool isAsync, out ITypeSymbol valueType)
{
isAsync = type.OriginalDefinition.Equals(wellKnownTypes.AsyncOwned, SymbolEqualityComparer.Default);
if (isAsync || type.OriginalDefinition.Equals(wellKnownTypes.Owned, SymbolEqualityComparer.Default))
isAsync = type.OriginalDefinition.Equals(wellKnownTypes.AsyncOwned, SymbolEqualityComparer.Default)
|| type.OriginalDefinition.Equals(wellKnownTypes.IAsyncOwned, SymbolEqualityComparer.Default);

if (isAsync
|| type.OriginalDefinition.Equals(wellKnownTypes.Owned, SymbolEqualityComparer.Default)
|| type.OriginalDefinition.Equals(wellKnownTypes.IOwned, SymbolEqualityComparer.Default))
{
valueType = ((INamedTypeSymbol)type).TypeArguments[0];
return true;
Expand Down
8 changes: 8 additions & 0 deletions StrongInject.Generator/WellKnownTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ internal record WellKnownTypes(
INamedTypeSymbol IAsyncDisposable,
INamedTypeSymbol ConcurrentBagOfAction,
INamedTypeSymbol ConcurrentBagOfFuncTask,
INamedTypeSymbol IOwned,
INamedTypeSymbol Owned,
INamedTypeSymbol IAsyncOwned,
INamedTypeSymbol AsyncOwned,
INamedTypeSymbol RegisterAttribute,
INamedTypeSymbol RegisterModuleAttribute,
Expand Down Expand Up @@ -52,7 +54,9 @@ public static bool TryCreate(Compilation compilation, Action<Diagnostic> reportD
var concurrentBagOfFuncTask = funcOfTask is null
? null
: concurrentBag?.Construct(funcOfTask);
var iOwned = compilation.GetTypeOrReport("StrongInject.IOwned`1", reportDiagnostic);
var owned = compilation.GetTypeOrReport("StrongInject.Owned`1", reportDiagnostic);
var iAsyncOwned = compilation.GetTypeOrReport("StrongInject.IAsyncOwned`1", reportDiagnostic);
var asyncOwned = compilation.GetTypeOrReport("StrongInject.AsyncOwned`1", reportDiagnostic);
var registerAttribute = compilation.GetTypeOrReport("StrongInject.RegisterAttribute", reportDiagnostic);
var registerModuleAttribute = compilation.GetTypeOrReport("StrongInject.RegisterModuleAttribute", reportDiagnostic);
Expand All @@ -77,7 +81,9 @@ public static bool TryCreate(Compilation compilation, Action<Diagnostic> reportD
|| iAsyncDisposable is null
|| concurrentBagOfAction is null
|| concurrentBagOfFuncTask is null
|| iOwned is null
|| owned is null
|| iAsyncOwned is null
|| asyncOwned is null
|| registerAttribute is null
|| registerModuleAttribute is null
Expand Down Expand Up @@ -108,7 +114,9 @@ public static bool TryCreate(Compilation compilation, Action<Diagnostic> reportD
IAsyncDisposable: iAsyncDisposable,
ConcurrentBagOfAction: concurrentBagOfAction,
ConcurrentBagOfFuncTask: concurrentBagOfFuncTask,
IOwned: iOwned,
Owned: owned,
IAsyncOwned: iAsyncOwned,
AsyncOwned: asyncOwned,
RegisterAttribute: registerAttribute,
RegisterModuleAttribute: registerModuleAttribute,
Expand Down
8 changes: 7 additions & 1 deletion StrongInject.Tests.Unit/GeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2831,7 +2831,13 @@ public void ReportMissingTypes()
// (1,1): Error SI0201: Missing Type 'StrongInject.IRequiresAsyncInitialization'. Are you missing an assembly reference?
// Missing Type.SI0201
new DiagnosticResult("SI0201", @"<UNKNOWN>", DiagnosticSeverity.Error).WithLocation(1, 1),
// (1,1): Error SI0201: Missing Type 'StrongInject.Owned`1[T]'. Are you missing an assembly reference?
// (1,1): Error SI0201: Missing Type 'StrongInject.IOwned`1'. Are you missing an assembly reference?
// Missing Type.SI0201
new DiagnosticResult("SI0201", @"<UNKNOWN>", DiagnosticSeverity.Error).WithLocation(1, 1),
// (1,1): Error SI0201: Missing Type 'StrongInject.Owned`1'. Are you missing an assembly reference?
// Missing Type.SI0201
new DiagnosticResult("SI0201", @"<UNKNOWN>", DiagnosticSeverity.Error).WithLocation(1, 1),
// (1,1): Error SI0201: Missing Type 'StrongInject.IAsyncOwned`1'. Are you missing an assembly reference?
// Missing Type.SI0201
new DiagnosticResult("SI0201", @"<UNKNOWN>", DiagnosticSeverity.Error).WithLocation(1, 1),
// (1,1): Error SI0201: Missing Type 'StrongInject.AsyncOwned`1'. Are you missing an assembly reference?
Expand Down
197 changes: 197 additions & 0 deletions StrongInject.Tests.Unit/OwnedInjectionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2038,5 +2038,202 @@ public record B : IRequiresAsyncInitialization { public ValueTask InitializeAsyn
// Container
new DiagnosticResult("SI0103", @"Container", DiagnosticSeverity.Error).WithLocation(7, 22));
}

[Fact]
public void IOwnedCanBeInjected()
{
string userSource = @"
using System;
using StrongInject;
[Register(typeof(A)), Register(typeof(B))]
public partial class Container : IContainer<A> { }
public record A(Func<IOwned<B>> B);
public record B : IDisposable { public void Dispose() { } };
";
var comp = RunGeneratorWithStrongInjectReference(userSource, out var generatorDiagnostics, out var generated);
generatorDiagnostics.Verify();
comp.GetDiagnostics().Verify();
var file = Assert.Single(generated);
file.Should().BeIgnoringLineEndings(@"#pragma warning disable CS1998
partial class Container
{
private int _disposed = 0;
private bool Disposed => _disposed != 0;
public void Dispose()
{
var disposed = global::System.Threading.Interlocked.Exchange(ref this._disposed, 1);
if (disposed != 0)
return;
}
TResult global::StrongInject.IContainer<global::A>.Run<TResult, TParam>(global::System.Func<global::A, TParam, TResult> func, TParam param)
{
if (Disposed)
throw new global::System.ObjectDisposedException(nameof(Container));
global::System.Func<global::StrongInject.IOwned<global::B>> func_0_1;
global::A a_0_0;
func_0_1 = () =>
{
global::StrongInject.Owned<global::B> owned_1_1;
global::StrongInject.IOwned<global::B> iOwned_1_0;
global::StrongInject.Owned<global::B> CreateOwnedB_2()
{
global::B b_1_0;
b_1_0 = new global::B();
return new global::StrongInject.Owned<global::B>(b_1_0, () =>
{
((global::System.IDisposable)b_1_0).Dispose();
});
}
owned_1_1 = CreateOwnedB_2();
iOwned_1_0 = (global::StrongInject.IOwned<global::B>)owned_1_1;
return iOwned_1_0;
};
a_0_0 = new global::A(B: func_0_1);
TResult result;
try
{
result = func(a_0_0, param);
}
finally
{
}
return result;
}
global::StrongInject.Owned<global::A> global::StrongInject.IContainer<global::A>.Resolve()
{
if (Disposed)
throw new global::System.ObjectDisposedException(nameof(Container));
global::System.Func<global::StrongInject.IOwned<global::B>> func_0_1;
global::A a_0_0;
func_0_1 = () =>
{
global::StrongInject.Owned<global::B> owned_1_1;
global::StrongInject.IOwned<global::B> iOwned_1_0;
global::StrongInject.Owned<global::B> CreateOwnedB_2()
{
global::B b_1_0;
b_1_0 = new global::B();
return new global::StrongInject.Owned<global::B>(b_1_0, () =>
{
((global::System.IDisposable)b_1_0).Dispose();
});
}
owned_1_1 = CreateOwnedB_2();
iOwned_1_0 = (global::StrongInject.IOwned<global::B>)owned_1_1;
return iOwned_1_0;
};
a_0_0 = new global::A(B: func_0_1);
return new global::StrongInject.Owned<global::A>(a_0_0, () =>
{
});
}
}");
}

[Fact]
public void IAsyncOwnedCanBeInjected()
{
string userSource = @"
using System;
using System.Threading.Tasks;
using StrongInject;
[Register(typeof(A)), Register(typeof(B))]
public partial class Container : IContainer<A> { }
public record A(Func<IAsyncOwned<B>> B);
public record B : IAsyncDisposable { public ValueTask DisposeAsync() => default; };
";
var comp = RunGeneratorWithStrongInjectReference(userSource, out var generatorDiagnostics, out var generated);
generatorDiagnostics.Verify();
comp.GetDiagnostics().Verify();
var file = Assert.Single(generated);
file.Should().BeIgnoringLineEndings(@"#pragma warning disable CS1998
partial class Container
{
private int _disposed = 0;
private bool Disposed => _disposed != 0;
public void Dispose()
{
var disposed = global::System.Threading.Interlocked.Exchange(ref this._disposed, 1);
if (disposed != 0)
return;
}
TResult global::StrongInject.IContainer<global::A>.Run<TResult, TParam>(global::System.Func<global::A, TParam, TResult> func, TParam param)
{
if (Disposed)
throw new global::System.ObjectDisposedException(nameof(Container));
global::System.Func<global::StrongInject.IAsyncOwned<global::B>> func_0_1;
global::A a_0_0;
func_0_1 = () =>
{
global::StrongInject.AsyncOwned<global::B> asyncOwned_1_1;
global::StrongInject.IAsyncOwned<global::B> iAsyncOwned_1_0;
global::StrongInject.AsyncOwned<global::B> CreateAsyncOwnedB_2()
{
global::B b_1_0;
b_1_0 = new global::B();
return new global::StrongInject.AsyncOwned<global::B>(b_1_0, async () =>
{
await ((global::System.IAsyncDisposable)b_1_0).DisposeAsync();
});
}
asyncOwned_1_1 = CreateAsyncOwnedB_2();
iAsyncOwned_1_0 = (global::StrongInject.IAsyncOwned<global::B>)asyncOwned_1_1;
return iAsyncOwned_1_0;
};
a_0_0 = new global::A(B: func_0_1);
TResult result;
try
{
result = func(a_0_0, param);
}
finally
{
}
return result;
}
global::StrongInject.Owned<global::A> global::StrongInject.IContainer<global::A>.Resolve()
{
if (Disposed)
throw new global::System.ObjectDisposedException(nameof(Container));
global::System.Func<global::StrongInject.IAsyncOwned<global::B>> func_0_1;
global::A a_0_0;
func_0_1 = () =>
{
global::StrongInject.AsyncOwned<global::B> asyncOwned_1_1;
global::StrongInject.IAsyncOwned<global::B> iAsyncOwned_1_0;
global::StrongInject.AsyncOwned<global::B> CreateAsyncOwnedB_2()
{
global::B b_1_0;
b_1_0 = new global::B();
return new global::StrongInject.AsyncOwned<global::B>(b_1_0, async () =>
{
await ((global::System.IAsyncDisposable)b_1_0).DisposeAsync();
});
}
asyncOwned_1_1 = CreateAsyncOwnedB_2();
iAsyncOwned_1_0 = (global::StrongInject.IAsyncOwned<global::B>)asyncOwned_1_1;
return iAsyncOwned_1_0;
};
a_0_0 = new global::A(B: func_0_1);
return new global::StrongInject.Owned<global::A>(a_0_0, () =>
{
});
}
}");
}
}
}
55 changes: 55 additions & 0 deletions StrongInject.Tests.Unit/OwnedTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System.Threading.Tasks;
using Xunit;

namespace StrongInject.Generator.Tests.Unit
{
public static class OwnedTests
{
[Fact]
public static void OwnedCanBeImplicitlyCastToWiderT()
{
var narrowerType = new Owned<string>("", dispose: () => { });
IOwned<object> widerType = narrowerType;

// This should survive any refactoring of the types or APIs.
// (E.g. Replacing IOwned with owned.Cast<object>() returning an Owned<object> wrapper would not be acceptable.)
Assert.Same(narrowerType, widerType);
}

[Fact]
public static void AsyncOwnedCanBeImplicitlyCastToWiderT()
{
var narrowerType = new AsyncOwned<string>("", dispose: () => ValueTask.CompletedTask);
IAsyncOwned<object> widerType = narrowerType;

// This should survive any refactoring of the types or APIs.
// (E.g. Replacing IOwned with owned.Cast<object>() returning an Owned<object> wrapper would not be acceptable.)
Assert.Same(narrowerType, widerType);
}

[Fact]
public static void OwnedCanBeUsedWithNullDisposeAction()
{
var owned = new Owned<string>("", dispose: null);

// If a check is ever added in the future to return null or throw if Value is accessed after disposal,
// the null action should not cause Owned<T> to think that disposal has already happened.
Assert.Equal("", owned.Value);

owned.Dispose();
}

[Fact]
public static void AsyncOwnedCanBeUsedWithNullDisposeAction()
{
var owned = new AsyncOwned<string>("", dispose: null);

// If a check is ever added in the future to return null or throw if Value is accessed after disposal,
// the null action should not cause Owned<T> to think that disposal has already happened.
Assert.Equal("", owned.Value);

// This should complete instantly since there is nothing to do.
Assert.True(owned.DisposeAsync().IsCompletedSuccessfully);
}
}
}
Loading

0 comments on commit f983e81

Please sign in to comment.