|
| 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