Skip to content

Commit

Permalink
Support for generic attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
NikolayPianikov committed Jun 5, 2024
1 parent f895f30 commit 31c8f55
Show file tree
Hide file tree
Showing 22 changed files with 589 additions and 46 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ dotnet run
- [Tag attribute](readme/tag-attribute.md)
- [Type attribute](readme/type-attribute.md)
- [Custom attributes](readme/custom-attributes.md)
- [Custom universal attribute](readme/custom-universal-attribute.md)
### Interception
- [Decorator](readme/decorator.md)
- [Interception](readme/interception.md)
Expand Down
4 changes: 2 additions & 2 deletions readme/constructor-ordinal-attribute.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ class Service : IService

DI.Setup(nameof(Composition))
.Arg<string>("serviceName")
.Bind<IDependency>().To<Dependency>()
.Bind<IService>().To<Service>()
.Bind().To<Dependency>()
.Bind().To<Service>()

// Composition root
.Root<IService>("Root");
Expand Down
39 changes: 35 additions & 4 deletions readme/custom-attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,31 +29,45 @@ class MyTagAttribute(object tag) : Attribute;
| AttributeTargets.Field)]
class MyTypeAttribute(Type type) : Attribute;

[AttributeUsage(
AttributeTargets.Parameter
| AttributeTargets.Property
| AttributeTargets.Field)]
class MyGenericTypeAttribute<T> : Attribute;

interface IPerson;

class Person([MyTag("NikName")] string name) : IPerson
{
private object? _state;

[MyOrdinal(1)]
[MyType(typeof(int))]
internal object Id = "";

public override string ToString() => $"{Id} {name}";
[MyOrdinal(2)]
public void Initialize([MyGenericType<Uri>] object state) =>
_state = state;

public override string ToString() => $"{Id} {name} {_state}";
}

DI.Setup(nameof(PersonComposition))
.TagAttribute<MyTagAttribute>()
.OrdinalAttribute<MyOrdinalAttribute>()
.TypeAttribute<MyTypeAttribute>()
.TypeAttribute<MyGenericTypeAttribute<TT>>()
.Arg<int>("personId")
.Bind<string>("NikName").To(_ => "Nik")
.Bind<IPerson>().To<Person>()
.Bind().To(_ => new Uri("https://github.com/DevTeam/Pure.DI"))
.Bind("NikName").To(_ => "Nik")
.Bind().To<Person>()

// Composition root
.Root<IPerson>("Person");

var composition = new PersonComposition(personId: 123);
var person = composition.Person;
person.ToString().ShouldBe("123 Nik");
person.ToString().ShouldBe("123 Nik https://github.com/DevTeam/Pure.DI");
```

The following partial class will be generated:
Expand Down Expand Up @@ -82,9 +96,11 @@ partial class PersonComposition
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
Uri transientUri2 = new Uri("https://github.com/DevTeam/Pure.DI");
string transientString1 = "Nik";
Person transientPerson0 = new Person(transientString1);
transientPerson0.Id = _argPersonId;
transientPerson0.Initialize(transientUri2);
return transientPerson0;
}
}
Expand All @@ -100,17 +116,32 @@ classDiagram
+IPerson Person
}
class Int32
Uri --|> ISpanFormattable
Uri --|> IFormattable
Uri --|> ISerializable
class Uri
class String
Person --|> IPerson
class Person {
+Person(String name)
~Object Id
+Initialize(Object state) : Void
}
class ISpanFormattable {
<<interface>>
}
class IFormattable {
<<interface>>
}
class ISerializable {
<<interface>>
}
class IPerson {
<<interface>>
}
PersonComposition ..> Person : IPerson Person
Person *-- String : "NikName" String
Person o-- Int32 : Argument "personId"
Person *-- Uri : Uri
```

123 changes: 123 additions & 0 deletions readme/custom-universal-attribute.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#### Custom universal attribute

[![CSharp](https://img.shields.io/badge/C%23-code-blue.svg)](../tests/Pure.DI.UsageTests/Attributes/CustomUniversalAttributeScenario.cs)

You can use a combined attribute, and each method in the list above has an optional parameter that defines the argument number (the default is 0) from where to get the appropriate metadata for _tag_, _ordinal_, or _type_.


```c#
[AttributeUsage(
AttributeTargets.Constructor
| AttributeTargets.Method
| AttributeTargets.Parameter
| AttributeTargets.Property
| AttributeTargets.Field)]
class InjectAttribute<T>(object? tag = null, int ordinal = 0) : Attribute;

interface IPerson;

class Person([Inject<string>("NikName")] string name) : IPerson
{
private object? _state;

[Inject<int>(ordinal: 1)]
internal object Id = "";

public void Initialize([Inject<Uri>] object state) =>
_state = state;

public override string ToString() => $"{Id} {name} {_state}";
}

DI.Setup(nameof(PersonComposition))
.TagAttribute<InjectAttribute<TT>>()
.OrdinalAttribute<InjectAttribute<TT>>(1)
.TypeAttribute<InjectAttribute<TT>>()
.Arg<int>("personId")
.Bind().To(_ => new Uri("https://github.com/DevTeam/Pure.DI"))
.Bind("NikName").To(_ => "Nik")
.Bind().To<Person>()

// Composition root
.Root<IPerson>("Person");

var composition = new PersonComposition(personId: 123);
var person = composition.Person;
person.ToString().ShouldBe("123 Nik https://github.com/DevTeam/Pure.DI");
```

The following partial class will be generated:

```c#
partial class PersonComposition
{
private readonly PersonComposition _root;

private readonly int _argPersonId;

public PersonComposition(int personId)
{
_argPersonId = personId;
_root = this;
}

internal PersonComposition(PersonComposition parentScope)
{
_root = (parentScope ?? throw new ArgumentNullException(nameof(parentScope)))._root;
_argPersonId = _root._argPersonId;
}

public IPerson Person
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
Uri transientUri2 = new Uri("https://github.com/DevTeam/Pure.DI");
string transientString1 = "Nik";
Person transientPerson0 = new Person(transientString1);
transientPerson0.Initialize(transientUri2);
transientPerson0.Id = _argPersonId;
return transientPerson0;
}
}
}
```

Class diagram:

```mermaid
classDiagram
class PersonComposition {
<<partial>>
+IPerson Person
}
class Int32
Uri --|> ISpanFormattable
Uri --|> IFormattable
Uri --|> ISerializable
class Uri
class String
Person --|> IPerson
class Person {
+Person(String name)
~Object Id
+Initialize(Object state) : Void
}
class ISpanFormattable {
<<interface>>
}
class IFormattable {
<<interface>>
}
class ISerializable {
<<interface>>
}
class IPerson {
<<interface>>
}
PersonComposition ..> Person : IPerson Person
Person *-- String : "NikName" String
Person o-- Int32 : Argument "personId"
Person *-- Uri : Uri
```

2 changes: 1 addition & 1 deletion readme/member-ordinal-attribute.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ DI.Setup(nameof(PersonComposition))
.Arg<int>("personId")
.Arg<string>("personName")
.Arg<DateTime>("personBirthday")
.Bind<IPerson>().To<Person>()
.Bind().To<Person>()

// Composition root
.Root<IPerson>("Person");
Expand Down
6 changes: 3 additions & 3 deletions readme/tag-attribute.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ class Service(
}

DI.Setup(nameof(Composition))
.Bind<IDependency>("Abc").To<AbcDependency>()
.Bind<IDependency>("Xyz").To<XyzDependency>()
.Bind<IService>().To<Service>()
.Bind("Abc").To<AbcDependency>()
.Bind("Xyz").To<XyzDependency>()
.Bind().To<Service>()

// Composition root
.Root<IService>("Root");
Expand Down
2 changes: 1 addition & 1 deletion readme/type-attribute.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class Service(
}

DI.Setup(nameof(Composition))
.Bind<IService>().To<Service>()
.Bind().To<Service>()

// Composition root
.Root<IService>("Root");
Expand Down
51 changes: 43 additions & 8 deletions src/Pure.DI.Core/Core/ApiInvocationProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -220,25 +220,40 @@ public void ProcessInvocation(
break;

case nameof(IConfiguration.TypeAttribute):
if (genericName.TypeArgumentList.Arguments is [{ } typeAttributeType])
if (TryGetAttributeType(genericName, semanticModel, out var typeAttributeType))
{
metadataVisitor.VisitTypeAttribute(new MdTypeAttribute(semanticModel, invocation.ArgumentList, semanticModel.GetTypeSymbol<ITypeSymbol>(typeAttributeType, cancellationToken), BuildConstantArgs<object>(semanticModel, invocation.ArgumentList.Arguments) is [int positionVal] ? positionVal : 0));
var attr = new MdTypeAttribute(
semanticModel,
invocation.ArgumentList,
typeAttributeType,
BuildConstantArgs<object>(semanticModel, invocation.ArgumentList.Arguments) is [int positionVal] ? positionVal : 0);
metadataVisitor.VisitTypeAttribute(attr);
}

break;

case nameof(IConfiguration.TagAttribute):
if (genericName.TypeArgumentList.Arguments is [{ } tagAttributeType])
if (TryGetAttributeType(genericName, semanticModel, out var tagAttributeType))
{
metadataVisitor.VisitTagAttribute(new MdTagAttribute(semanticModel, invocation.ArgumentList, semanticModel.GetTypeSymbol<ITypeSymbol>(tagAttributeType, cancellationToken), BuildConstantArgs<object>(semanticModel, invocation.ArgumentList.Arguments) is [int positionVal] ? positionVal : 0));
var attr = new MdTagAttribute(
semanticModel,
invocation.ArgumentList,
tagAttributeType,
BuildConstantArgs<object>(semanticModel, invocation.ArgumentList.Arguments) is [int positionVal] ? positionVal : 0);
metadataVisitor.VisitTagAttribute(attr);
}

break;

case nameof(IConfiguration.OrdinalAttribute):
if (genericName.TypeArgumentList.Arguments is [{ } ordinalAttributeType])
if (TryGetAttributeType(genericName, semanticModel, out var ordinalAttributeType))
{
metadataVisitor.VisitOrdinalAttribute(new MdOrdinalAttribute(semanticModel, invocation.ArgumentList, semanticModel.GetTypeSymbol<ITypeSymbol>(ordinalAttributeType, cancellationToken), BuildConstantArgs<object>(semanticModel, invocation.ArgumentList.Arguments) is [int positionVal] ? positionVal : 0));
var attr = new MdOrdinalAttribute(
semanticModel,
invocation.ArgumentList,
ordinalAttributeType,
BuildConstantArgs<object>(semanticModel, invocation.ArgumentList.Arguments) is [int positionVal] ? positionVal : 0);
metadataVisitor.VisitOrdinalAttribute(attr);
}

break;
Expand Down Expand Up @@ -272,6 +287,26 @@ public void ProcessInvocation(
}
}

private bool TryGetAttributeType(
GenericNameSyntax genericName,
SemanticModel semanticModel,
[NotNullWhen(true)] out INamedTypeSymbol? type)
{
if (genericName.TypeArgumentList.Arguments is not [{ } attributeTypeSyntax])
{
type = default;
return false;
}

type = semanticModel.GetTypeSymbol<INamedTypeSymbol>(attributeTypeSyntax, cancellationToken);
if (type.IsGenericType)
{
type = type.ConstructUnboundGenericType();
}

return true;
}

private static void VisitRoot(
IArguments arguments,
IMetadataVisitor metadataVisitor,
Expand Down
10 changes: 8 additions & 2 deletions src/Pure.DI.Core/Core/CompilationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@ namespace Pure.DI.Core;
internal static class CompilationExtensions
{
[SuppressMessage("ReSharper", "HeapView.ClosureAllocation")]
public static IReadOnlyList<AttributeData> GetAttributes(this ISymbol member, ITypeSymbol attributeType) =>
public static IReadOnlyList<AttributeData> GetAttributes(this ISymbol member, INamedTypeSymbol attributeType) =>
member
.GetAttributes()
.Where(attr => attr.AttributeClass != null && SymbolEqualityComparer.Default.Equals(attr.AttributeClass, attributeType))
.Where(attr => attr.AttributeClass != null && SymbolEqualityComparer.Default.Equals(GetUnboundTypeSymbol(attr.AttributeClass), attributeType))
.ToArray();

public static LanguageVersion GetLanguageVersion(this Compilation compilation) =>
compilation is CSharpCompilation sharpCompilation
? sharpCompilation.LanguageVersion
: LanguageVersion.Default;

private static INamedTypeSymbol? GetUnboundTypeSymbol(INamedTypeSymbol? typeSymbol) =>
typeSymbol is null
? typeSymbol : typeSymbol.IsGenericType
? typeSymbol.ConstructUnboundGenericType()
: typeSymbol;
}
Loading

0 comments on commit 31c8f55

Please sign in to comment.