Skip to content

Commit 527943d

Browse files
author
Immo Landwerth
authored
Merge pull request #220 from terrajobst/preview-compiler-enforcement
Remove compiler enforcement and let the analyzer handle it
2 parents 873c886 + 4d2f8bc commit 527943d

File tree

1 file changed

+103
-69
lines changed

1 file changed

+103
-69
lines changed

accepted/2021/preview-features/preview-features.md

Lines changed: 103 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -139,29 +139,6 @@ namespace System
139139
}
140140
```
141141

142-
> What will the compiler do when the attribute is not applied at the assembly level?
143-
144-
I don't think the compiler/analyzer should do anything in this case. The SDK
145-
will generate the assembly level attribute based on the project setting, but
146-
this can be turned, off which is what we will do to build the BCL. For obvious
147-
reasons we don't want `System.Runtime` as whole to have an assembly level
148-
attribute -- we only want this on specific types/members.
149-
150-
> If assembly A has a method M marked with [RequiresPreviewFeatures], but no
151-
> assembly level attribute, and it references assembly B that has the assembly
152-
> level attribute, will the compiler warn?
153-
154-
Yes. The assembly level attribute is meant to protect the user that just uses
155-
the project level setting. Advanced users (like us) can turn off the generation
156-
of the assembly level attribute and selectively apply this attribute to
157-
particular parts of the library. In this case it will only warn users that uses
158-
those APIs.
159-
160-
The advanced user is responsible for making sure that they very well understand
161-
which parts are using preview features and ensure they get the attribution
162-
right. Hence, it doesn't make sense for such as user to depend on an assembly
163-
that didn't do that due diligence because all bets are off at that point.
164-
165142
## Requirements
166143

167144
### Goals
@@ -264,48 +241,59 @@ that the runtime can use this information if ever needed:
264241
}
265242
```
266243

267-
### Compilation context
244+
### Compiler tracking
245+
246+
*We propose that the compiler does no tracking of preview features at this
247+
point.*
268248

269249
When `EnablePreviewFeatures` is true, the `LangVersion` property is set to
270250
`Preview` (unless the customer has explicitly set `LangVersion` in their project
271251
file already). This avoids customers having to turn on preview features for the
272252
language separately.
273253

274-
In addition, the property `EnablePreviewFeatures` should be passed to the
275-
compilation context (akin to how `TargetFramework` is passed in today). An
276-
analyzer will use that to block use of APIs that are marked as preview as basing
277-
this off the language preview mode is nonsensical.
278-
279254
The way the compiler knows which features a targeted runtime supports is by
280255
looking at the `RuntimeFeature` type. Each runtime feature corresponds to a
281256
specific field on that type. If the type doesn't have the field, the runtime is
282-
considered as not-supporting the feature. To indicate that a feature is there
283-
but requires turning on preview mode, we'll simply mark the field with the
284-
`[RequiresPreviewFeatures]` attribute, just like any other preview API.
285-
286-
This solves two problems:
287-
288-
* The customer is using a future version of C# where a given language feature is
289-
no longer considered "preview" and thus doesn't require `LangVersion` to be
290-
set to preview but in the targeted runtime the feature is still preview. When
291-
a language feature is used that requires the given runtime feature, the
292-
compiler should check if the field is marked as preview and fail unless the
293-
customer has turned preview mode on.
294-
295-
* The customer has manually set `LangVersion` to `Preview` but
296-
`EnablePreviewFeatures` is not configured (thus defaulting to `False`).
297-
Similar case as above, the customer can use any language feature that doesn't
298-
require runtime changes but as soon as a feature is used that requires a
299-
preview runtime feature, the compiler will report an error, demanding that
300-
preview mode needs to be turned on.
301-
302-
***Note:*** F# doesn't have the capability to use analyzers today. We can
303-
decided to make this a compiler feature for F#.
304-
305-
### API Analyzer
306-
307-
We'll need an attribute to record whether or not the project was
308-
built with preview features turned on:
257+
considered as not-supporting the feature.
258+
259+
We discussed extending the compiler to treat `RuntimeFeature` fields marked with
260+
`[RequiresPreviewFeatures]` differently, but this has the complication that this
261+
effectively means that the compiler needs to track the use of runtime preview
262+
features. We considered having a split where an analyzer would handle the use of
263+
preview APIs with the compiler tracking the use of language features that
264+
require preview runtime features.
265+
266+
The challenge is that this means we need to formalize how the compiler should
267+
enforce it. At this point, it's not clear to us that we actually want a sound
268+
analysis in this space because we likely need to allow the developer "to cheat",
269+
that is, the author of an API needs to be able to say "I want to ship an RTM
270+
version of my API that doesn't require consumers to opt-into preview" while
271+
still being able to use preview feature in other parts of the code.
272+
273+
The trick is to ensure that parts depending on preview features are annotated
274+
while ensuring that the parts that don't aren't, especially when those already
275+
exist. However, with the upcoming statics in interfaces feature we run into
276+
challenges where we want to be able to do the following things:
277+
278+
1. Define new interfaces that have statics on them. Those interfaces will be marked
279+
with `[RequiresPreviewFeatures]`.
280+
2. Implement those interfaces on existing types, such as `Int32` and `Single`. Those
281+
types cannot be marked with `[RequiresPreviewFeatures]`.
282+
3. We will implement many of the interface methods explicitly but not all of
283+
them will or even can. For cases where the static member already exists (for
284+
example, some of `Decimal`'s operators) those can't be required to be marked
285+
with `[RequiresPreviewFeatures]`.
286+
287+
At this point, it seems to us that its better to keep the logic in an analyzer,
288+
potentially special-casing certain relationships of language features, runtime
289+
support, and core APIs in order to deliver a good customer experience. Once we
290+
understand the desired rules more, we can potentially formalize them and make
291+
them a proper compiler feature.
292+
293+
### Analyzer
294+
295+
We'll need an attribute to record whether or not the a given assembly or API
296+
requires preview features:
309297

310298
```C#
311299
namespace System.Runtime.Versioning
@@ -329,20 +317,68 @@ namespace System.Runtime.Versioning
329317
}
330318
```
331319

332-
We'll ship a built-in analyzer that will report diagnostics when an API is being
333-
used that is marked `[RequiresPreviewFeatures]` but the consumer (member, type,
334-
module, assembly) isn't marked. The default severity is `error`.
320+
We'll ship a built-in analyzer that will report diagnostics when code depends on
321+
preview features without having opted-into preview features. The default
322+
severity is `error`.
323+
324+
Opting into preview features is done by marking the consuming member, type,
325+
module, or assembly with `[RequiresPreviewFeatures]`.
326+
327+
***Note:** The analyzer doesn't track the `<EnablePreviewFeatures>` property
328+
itself, it tracks the attribute `[RequiresPreviewFeatures]`. By default, setting
329+
`<EnablePreviewFeatures>` to `True` will mark the entire assembly with
330+
`[RequiresPreviewFeatures]`, but developers can turn this off which means that
331+
they now need to manually annotate parts of their code with
332+
`[RequiresPreviewFeatures]`.*
333+
334+
An API is considered marked as `[RequiresPreviewFeatures]` if it is directly
335+
marked with `[RequiresPreviewFeatures]` or if the containing type, module or
336+
assembly is marked with `[RequiresPreviewFeatures]`.
337+
338+
The following operations are considered use of preview features:
339+
340+
* Defining a static member in an interface, if, and only if, the
341+
`RuntimeFeature.VirtualStaticsInInterfaces` field is marked with
342+
`[RequiresPreviewFeatures]`
343+
* Referencing an assembly that is marked as `[RequiresPreviewFeatures]`
344+
* Deriving from a type marked as `[RequiresPreviewFeatures]`
345+
* Implementing an interface marked as `[RequiresPreviewFeatures]`
346+
* Overriding a member marked as `[RequiresPreviewFeatures]`
347+
* Calling a method marked as `[RequiresPreviewFeatures]`
348+
* Reading or writing a field or property marked as `[RequiresPreviewFeatures]`
349+
* Subscribing or unsubscribing from an event marked as
350+
`[RequiresPreviewFeatures]`
351+
352+
This design ensures that using a future version of C# where statics in
353+
interfaces are no longer a language preview will still enforce marking the
354+
calling code as preview when building for a runtime where the corresponding
355+
runtime feature is still considered in preview.
356+
357+
We plan to use this analyzer when building the BCL itself. Given the rules
358+
above, we'll get a diagnostic when implementing the new interfaces on existing
359+
types, such as `Int32`. That is intentional. The idea is that we suppress those
360+
to indicate that we're OK with that:
335361

336-
This analyzer will also validate that if any referenced assembly has applied
337-
`[RequiresPreviewFeatures]`, that the consuming assembly must also have this
338-
attribute applied.
362+
```C#
363+
namespace System
364+
{
365+
public struct Int32 : // ...
366+
#pragma warning disable CAXXXX // Use of preview features
367+
INumber<Int32>
368+
#pragma warning restore CAXXXX // Use of preview features
369+
{
370+
// ...
371+
}
372+
}
373+
```
339374

340-
User code will typically only have this attribute applied at the assembly-level.
341-
However, the framework will typically only use this attribute on types and
342-
members without putting it on the assembly itself.
375+
However, since this analyzer will also be used by 3rd parties, we believe it's
376+
generally good if the analyzer is conservative with telling the developer when
377+
they use preview features from non-preview code and leave it up to the developer
378+
to suppress it when it's considered acceptable.
343379

344-
***Note:*** Since F# doesn't support analyzers, we would have to add this
345-
capability to the compiler itself.
380+
***Note:** Since F# doesn't support analyzers, we need to consider how preview
381+
feature enforcement affects F# users.*
346382

347383
### Reflection usage
348384

@@ -371,8 +407,6 @@ I'd argue that whenever reflection is used, our ability to provide guardrails is
371407
going down (e.g. no support for analyzers, no warnings for using obsolete APIs
372408
etc). Calling preview APIs is really not much different from that.
373409

374-
**OPEN ISSUE** Should we block calls to preview APIs from reflection?
375-
376410
## Q & A
377411

378412
### What about runtime-only preview features?

0 commit comments

Comments
 (0)