Skip to content

Commit 2b7cac8

Browse files
authored
[FS-1143] Generic Attributes
[FS-1143] Generic Attributes
2 parents f13b063 + 8d2e37b commit 2b7cac8

File tree

1 file changed

+153
-0
lines changed

1 file changed

+153
-0
lines changed

RFCs/FS-1143-Generic-Attributes.md

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# F# RFC FS-1143 - Generic Attributes
2+
3+
NOTE: new sections have been added to this template! Please use this template rather than copying an existing RFC.
4+
5+
The design suggestion [Generic attributes (965)](https://github.com/fsharp/fslang-suggestions/issues/965) has been marked "approved in principle".
6+
7+
This RFC covers the detailed proposal for this suggestion.
8+
9+
- [x] [Suggestion](https://github.com/fsharp/fslang-suggestions/issues/965)
10+
- [x] Approved in principle
11+
- [ ] [Implementation](https://github.com/dotnet/fsharp/pull/17258)
12+
- [ ] Design Review Meeting(s) with @dsyme and others invitees
13+
14+
# Summary
15+
16+
This language feature all type arguments in the usage of attributes, e.g. `[<SomeAttribute<int>>]`
17+
18+
# Motivation
19+
20+
1. Expanding the functionality of F# to ensure it more fully utilizes the features of the CIL, as well as maximizing interop with C#.
21+
2. Enriching reflection information for better contextualization, and more ergonomic access of custom attributes.
22+
23+
24+
# Detailed design
25+
26+
The storage of complex type information (via a System.Type) is currently only achievable through passing a `typeof<SomeType>` as a method argument to the attribute being used. By implementing generic attributes, the process of storing this information in attributes becomes much more ergonomic.
27+
28+
While this information is currently accessible by default for constant expressions/etc for enums, primitives, etc., the _only_ way to store Type information for things not currently permitted as attribute arguments is via a `typeof` call.
29+
30+
One of the greatest benefits is to the use of reflection:
31+
32+
```fsharp
33+
[<AClassAttribute<SomeTypeA>>]
34+
[<AClassAttribute<SomeTypeB>>]
35+
type Example =
36+
class end
37+
38+
typeof<Example>.GetCustomAttributes(typeof<AClassAttribute<SomeTypeA>>)
39+
//gives like [|AClassAttribute<SomeTypeA>|]
40+
typeof<Example>.GetCustomAttributes(typeof<AClassAttribute<SomeTypeB>>)
41+
//gives like [|AClassAttribute<SomeTypeB>|]
42+
```
43+
44+
whereas currently, you might do something like:
45+
46+
```fsharp
47+
[<AClassAttribute(typeof<SomeTypeA>)>]
48+
[<AClassAttribute(typeof<SomeTypeB>)>]
49+
type Example =
50+
class end
51+
52+
//assuming AClassAttribute has a property "_.TheType" where we store the single argument we pass above:
53+
typeof<Example>.GetCustomAttributes(typeof<AClassAttribute>)
54+
|> Array.filter (fun x -> (x :?> AClassAttribute |> _.TheType) = typeof<SomeTypeA>)
55+
//gives like [|AClassAttribute|]
56+
typeof<Example>.GetCustomAttributes(typeof<AClassAttribute>)
57+
|> Array.filter (fun x -> (x :?> AClassAttribute |> _.TheType) = typeof<SomeTypeA>)
58+
//gives like [|AClassAttribute|]
59+
```
60+
61+
# Drawbacks
62+
63+
This feature will increase the complexity of attribute handling throughout the compiler.
64+
65+
# Alternatives
66+
67+
While the benefit to reflection seems to have no alternative, the actual storage of type information in the instance of an attribute is currently achievable via passing a typeof<_> argument to the attribute ctor itself.
68+
69+
Given that the core purpose of this RFC is to increase both the ergonomics of F# and the interop between F# and C#, there is no real alternative on that end.
70+
71+
# Compatibility
72+
73+
Please address all necessary compatibility questions:
74+
75+
* Is this a breaking change?
76+
* No
77+
* What happens when previous versions of the F# compiler encounter this design addition as source code?
78+
* Unexpected postfix token error
79+
* What happens when previous versions of the F# compiler encounter this design addition in compiled binaries?
80+
* Attributes that require type arguments would be unusable. Reflection should be unaffected (as the GetCustomAttributes functionality relies on type specs.)
81+
* If this is a change or extension to FSharp.Core, what happens when previous versions of the F# compiler encounter this construct?
82+
* Not a change to Core.
83+
84+
# Pragmatics
85+
86+
## Diagnostics
87+
88+
Please list the reasonable expectations for diagnostics for misuse of this feature.
89+
90+
## Tooling
91+
92+
Please list the reasonable expectations for tooling for this feature, including any of these:
93+
94+
* Debugging
95+
* Breakpoints/stepping
96+
* Expression evaluator
97+
* Data displays for locals and hover tips
98+
* Auto-complete
99+
* Tooltips
100+
* Navigation and Go To Definition
101+
* Colorization
102+
* Brace/parenthesis matching
103+
104+
## Performance
105+
106+
Please list any notable concerns for impact on the performance of compilation and/or generated code
107+
108+
* For existing code
109+
* For the new features
110+
111+
## Scaling
112+
113+
Please list the dimensions that describe the inputs for this new feature, e.g. "number of widgets" etc. For each, estimate a reasonable upper bound for the expected size in human-written code and machine-generated code that the compiler will accept.
114+
115+
For example
116+
117+
* Expected maximum number of widgets in reasonable hand-written code: 100
118+
* Expected reasonable upper bound for number of widgets accepted: 500
119+
120+
Testing should particularly check that compilation is linear (or log-linear or similar) along these dimensions. If quadratic or worse this should ideally be noted in the RFC.
121+
122+
## Culture-aware formatting/parsing
123+
124+
Does the proposed RFC interact with culture-aware formatting and parsing of numbers, dates and currencies? For example, if the RFC includes plaintext outputs, are these outputs specified to be culture-invariant or current-culture.
125+
126+
# Unresolved questions
127+
128+
In what instances will type inferencing be possible? Presumably, the following will be a (relatively) trivial case:
129+
130+
```fsharp
131+
type MyAttribute<^T>(context : ^T)=
132+
inherit Attribute()
133+
134+
[<MyAttribute(24)>]
135+
let x = 1
136+
```
137+
138+
Though it may be less common, could we also support inferencing from the surrounding environment:
139+
140+
```fsharp
141+
type MyOtherAttribute<^T>()=
142+
inherit Attribute()
143+
144+
type SomeClass<^T,^S>(arg1, arg2)=
145+
146+
[<MyOtherAttribute<^T>>]
147+
member _.Field1 = arg1
148+
149+
[<MyOtherAttribute<^S>>]
150+
member _.Field1 = arg1
151+
```
152+
153+
This may be completely incongruent with how attributes are currently handled in F# (or dotnet entirely), but given that whenever we talk about `SomeClass` we would do so while also contextualizing `^T` and `^S`, it seems like something like this might be able to work.

0 commit comments

Comments
 (0)