Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add generic attributes proposal #4936

Merged
merged 2 commits into from
Jul 20, 2021
Merged
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
61 changes: 61 additions & 0 deletions proposals/generic-attributes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Generic Attributes

## Summary
[summary]: #summary

When generics were introduced in C# 2.0, attribute classes were not allowed to participate. We can make the language more composable by removing (rather, loosening) this restriction. The .NET Core runtime has added support for generic attributes. Now, all that's missing is support for generic attributes in the compiler.

## Motivation
[motivation]: #motivation

Currently attribute authors can take a `System.Type` as a parameter and have users pass a `typeof` expression to provide the attribute with types that it needs. However, outside of analyzers, there's no way for an attribute author to constrain what types are allowed to be passed to an attribute via `typeof`. If attributes could be generic, then attribute authors could use the existing system of type parameter constraints to express the requirements for the types they take as input.

## Detailed design
[design]: #detailed-design

The following section is amended: https://github.com/dotnet/csharplang/blob/main/spec/classes.md#base-classes

> The direct base class of a class type must not be any of the following types: System.Array, System.Delegate, System.MulticastDelegate, System.Enum, or System.ValueType. ~~Furthermore, a generic class declaration cannot use System.Attribute as a direct or indirect base class.~~

One important note is that the following section of the spec is *unaffected* when referencing the point of usage of an attribute, i.e. within an attribute list: https://github.com/dotnet/csharplang/blob/main/spec/types.md#type-parameters

> A type parameter cannot be used anywhere within an attribute.
Copy link
Contributor

@Joe4evr Joe4evr Jul 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something about this line had me confused for a bit (as well as once before ~a year ago?), so I'm wondering if the spec needs to be rephrased to emphasize it refers to the attribute placement and not its definition.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It does seem like it would be a little more clear to adjust the terminology to make attribute usages more distinct from their definitions.


This means that when a generic attribute is used, its construction needs to be fully "closed", i.e. not containing any type parameters, which means the following is still disallowed:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is "closed" the correct term? "Opened" for generic types could refer to Type<>. I didn't think that Type<T> would be considered "open".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so: https://github.com/dotnet/csharplang/blob/main/spec/types.md#open-and-closed-types

I think Type<> is an unbound generic type while Type<T> is an open generic type.


```cs
using System;
using System.Collections.Generic;

public class Attr<T1> : Attribute { }

public class Program<T2>
{
[Attr<T2>] // error
[Attr<List<T2>>] // error
void M() { }
}
```

When a generic attribute is used in an attribute list, its type arguments have the same restrictions that `typeof` has on its argument. For example, `[Attr<dynamic>]` is an error. This is because "attribute-dependent" types like `dynamic`, `List<string?>`, `nint`, and so on can't be fully represented in the final IL for an attribute type argument, because there isn't a symbol to "attach" the `DynamicAttribute` or other well-known attribute to.

## Drawbacks
[drawbacks]: #drawbacks

Removing the restriction, reasoning out the implications, and adding the appropriate tests is work.

## Alternatives
[alternatives]: #alternatives

Attribute authors who want users to be able to discover the requirements for the types they provide to attributes need to write analyzers and guide their users to use those analyzers in their builds.

## Unresolved questions
[unresolved]: #unresolved-questions

- [x] What does `AllowMultiple = false` mean on a generic attribute? If we have `[Attr<string>]` and `[Attr<object>]` both used on a symbol, does that mean "multiple" of the attribute are in use?
- For now we are inclined to take the more restrictive route here and consider the attribute class's original definition when deciding whether multiple of it have been applied. In other words, `[Attr<string>]` and `[Attr<object>]` applied together is incompatible with `AllowMultiple = false`.

## Design meetings

- https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-02-21.md#generic-attributes
- At the time there was a concern that we would have to gate the feature on whether the target runtime supports it. (However, we now only support C# 10 on .NET 6. It would be nice to for the implementation to be aware of what minimum target framework supports the feature, but seems less essential today.)