Skip to content

Commit 8e773e7

Browse files
authored
Add support for C# Experimental attribute (#18253)
1 parent b64ea2b commit 8e773e7

33 files changed

+594
-211
lines changed

docs/release-notes/.FSharp.Compiler.Service/9.0.300.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
### Added
1313
* Added missing type constraints in FCS. ([PR #18241](https://github.com/dotnet/fsharp/pull/18241))
1414
* The 'use' keyword can be used on IDisposable|null without nullness warnings ([PR #18262](https://github.com/dotnet/fsharp/pull/18262))
15+
* Add support for C# `Experimental` attribute. ([PR #18253](https://github.com/dotnet/fsharp/pull/18253))
1516
* Nullness warnings are issued for signature<>implementation conformance ([PR #18186](https://github.com/dotnet/fsharp/pull/18186))
1617
* Symbols: Add FSharpAssembly.IsFSharp ([PR #18290](https://github.com/dotnet/fsharp/pull/18290))
1718

src/Compiler/Checking/AttributeChecking.fs

+67-51
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,6 @@ open FSharp.Compiler.TypeProviders
2424
open FSharp.Core.CompilerServices
2525
#endif
2626

27-
exception ObsoleteDiagnostic of
28-
isError: bool *
29-
diagnosticId: string *
30-
message: string *
31-
urlFormat: string *
32-
range: range
33-
3427
let fail() = failwith "This custom attribute has an argument that cannot yet be converted using this API"
3528

3629
let rec private evalILAttribElem elem =
@@ -247,44 +240,68 @@ let private CheckCompilerFeatureRequiredAttribute (g: TcGlobals) cattrs msg m =
247240
| Some([ILAttribElem.String (Some featureName) ], _) when featureName = "RequiredMembers" ->
248241
CompleteD
249242
| _ ->
250-
ErrorD (ObsoleteDiagnostic(true, "", msg, "", m))
243+
ErrorD (ObsoleteDiagnostic(true, None, msg, None, m))
244+
245+
let private extractILAttribValueFrom name namedArgs =
246+
match namedArgs with
247+
| ExtractILAttributeNamedArg name (AttribElemStringArg v) -> Some v
248+
| _ -> None
251249

252-
let private extractILObsoleteAttributeInfo namedArgs =
253-
let extractILAttribValueFrom name namedArgs =
254-
match namedArgs with
255-
| ExtractILAttributeNamedArg name (AttribElemStringArg v) -> v
256-
| _ -> ""
250+
let private extractILAttributeInfo namedArgs =
257251
let diagnosticId = extractILAttribValueFrom "DiagnosticId" namedArgs
258252
let urlFormat = extractILAttribValueFrom "UrlFormat" namedArgs
259253
(diagnosticId, urlFormat)
260254

255+
let private CheckILExperimentalAttributes (g: TcGlobals) cattrs m =
256+
let (AttribInfo(tref,_)) = g.attrib_IlExperimentalAttribute
257+
match TryDecodeILAttribute tref cattrs with
258+
// [Experimental("DiagnosticId")]
259+
// [Experimental(diagnosticId: "DiagnosticId")]
260+
// [Experimental("DiagnosticId", UrlFormat = "UrlFormat")]
261+
// [Experimental(diagnosticId = "DiagnosticId", UrlFormat = "UrlFormat")]
262+
// Constructors deciding on DiagnosticId and UrlFormat properties.
263+
| Some ([ attribElement ], namedArgs) ->
264+
let diagnosticId =
265+
match attribElement with
266+
| ILAttribElem.String (Some msg) -> Some msg
267+
| ILAttribElem.String None
268+
| _ -> None
269+
270+
let message = extractILAttribValueFrom "Message" namedArgs
271+
let urlFormat = extractILAttribValueFrom "UrlFormat" namedArgs
272+
273+
WarnD(Experimental(message, diagnosticId, urlFormat, m))
274+
// Empty constructor or only UrlFormat property are not allowed.
275+
| Some _
276+
| None -> CompleteD
277+
261278
let private CheckILObsoleteAttributes (g: TcGlobals) isByrefLikeTyconRef cattrs m =
262279
if isByrefLikeTyconRef then
263280
CompleteD
264281
else
265282
let (AttribInfo(tref,_)) = g.attrib_SystemObsolete
266283
match TryDecodeILAttribute tref cattrs with
267-
// [<Obsolete>]
268-
// [<Obsolete("Message")>]
269-
// [<Obsolete("Message", true)>]
270-
// [<Obsolete("Message", DiagnosticId = "DiagnosticId")>]
271-
// [<Obsolete("Message", DiagnosticId = "DiagnosticId", UrlFormat = "UrlFormat")>]
272-
// [<Obsolete(DiagnosticId = "DiagnosticId")>]
273-
// [<Obsolete(DiagnosticId = "DiagnosticId", UrlFormat = "UrlFormat")>]
274-
// [<Obsolete("Message", true, DiagnosticId = "DiagnosticId")>]
275-
// [<Obsolete("Message", true, DiagnosticId = "DiagnosticId", UrlFormat = "UrlFormat")>]
284+
// [Obsolete]
285+
// [Obsolete("Message")]
286+
// [Obsolete("Message", true)]
287+
// [Obsolete("Message", DiagnosticId = "DiagnosticId")]
288+
// [Obsolete("Message", DiagnosticId = "DiagnosticId", UrlFormat = "UrlFormat")]
289+
// [Obsolete(DiagnosticId = "DiagnosticId")]
290+
// [Obsolete(DiagnosticId = "DiagnosticId", UrlFormat = "UrlFormat")]
291+
// [Obsolete("Message", true, DiagnosticId = "DiagnosticId")]
292+
// [Obsolete("Message", true, DiagnosticId = "DiagnosticId", UrlFormat = "UrlFormat")]
276293
// Constructors deciding on IsError and Message properties.
277294
| Some ([ attribElement ], namedArgs) ->
278-
let diagnosticId, urlFormat = extractILObsoleteAttributeInfo namedArgs
295+
let diagnosticId, urlFormat = extractILAttributeInfo namedArgs
279296
let msg =
280297
match attribElement with
281-
| ILAttribElem.String (Some msg) -> msg
298+
| ILAttribElem.String (Some msg) -> Some msg
282299
| ILAttribElem.String None
283-
| _ -> ""
300+
| _ -> None
284301

285302
WarnD (ObsoleteDiagnostic(false, diagnosticId, msg, urlFormat, m))
286-
| Some ([ILAttribElem.String (Some msg); ILAttribElem.Bool isError ], namedArgs) ->
287-
let diagnosticId, urlFormat = extractILObsoleteAttributeInfo namedArgs
303+
| Some ([ILAttribElem.String msg; ILAttribElem.Bool isError ], namedArgs) ->
304+
let diagnosticId, urlFormat = extractILAttributeInfo namedArgs
288305
if isError then
289306
if g.langVersion.SupportsFeature(LanguageFeature.RequiredPropertiesSupport) then
290307
CheckCompilerFeatureRequiredAttribute g cattrs msg m
@@ -294,24 +311,23 @@ let private CheckILObsoleteAttributes (g: TcGlobals) isByrefLikeTyconRef cattrs
294311
WarnD (ObsoleteDiagnostic(false, diagnosticId, msg, urlFormat, m))
295312
// Only DiagnosticId, UrlFormat
296313
| Some (_, namedArgs) ->
297-
let diagnosticId, urlFormat = extractILObsoleteAttributeInfo namedArgs
298-
WarnD(ObsoleteDiagnostic(false, diagnosticId, "", urlFormat, m))
314+
let diagnosticId, urlFormat = extractILAttributeInfo namedArgs
315+
WarnD(ObsoleteDiagnostic(false, diagnosticId, None, urlFormat, m))
299316
// No arguments
300317
| None -> CompleteD
301318

302-
/// Check IL attributes for 'ObsoleteAttribute', returning errors and warnings as data
319+
/// Check IL attributes for Experimental, warnings as data
303320
let private CheckILAttributes (g: TcGlobals) isByrefLikeTyconRef cattrs m =
304321
trackErrors {
305322
do! CheckILObsoleteAttributes g isByrefLikeTyconRef cattrs m
323+
do! CheckILExperimentalAttributes g cattrs m
306324
}
307325

308-
let langVersionPrefix = "--langversion:preview"
309-
310326
let private extractObsoleteAttributeInfo namedArgs =
311327
let extractILAttribValueFrom name namedArgs =
312328
match namedArgs with
313-
| ExtractAttribNamedArg name (AttribStringArg v) -> v
314-
| _ -> ""
329+
| ExtractAttribNamedArg name (AttribStringArg v) -> Some v
330+
| _ -> None
315331
let diagnosticId = extractILAttribValueFrom "DiagnosticId" namedArgs
316332
let urlFormat = extractILAttribValueFrom "UrlFormat" namedArgs
317333
(diagnosticId, urlFormat)
@@ -331,17 +347,17 @@ let private CheckObsoleteAttributes g attribs m =
331347
// Constructors deciding on IsError and Message properties.
332348
| Some(Attrib(unnamedArgs= [ AttribStringArg s ]; propVal= namedArgs)) ->
333349
let diagnosticId, urlFormat = extractObsoleteAttributeInfo namedArgs
334-
do! WarnD(ObsoleteDiagnostic(false, diagnosticId, s, urlFormat, m))
350+
do! WarnD(ObsoleteDiagnostic(false, diagnosticId, Some s, urlFormat, m))
335351
| Some(Attrib(unnamedArgs= [ AttribStringArg s; AttribBoolArg(isError) ]; propVal= namedArgs)) ->
336352
let diagnosticId, urlFormat = extractObsoleteAttributeInfo namedArgs
337353
if isError then
338-
do! ErrorD (ObsoleteDiagnostic(true, diagnosticId, s, urlFormat, m))
354+
do! ErrorD (ObsoleteDiagnostic(true, diagnosticId, Some s, urlFormat, m))
339355
else
340-
do! WarnD (ObsoleteDiagnostic(false, diagnosticId, s, urlFormat, m))
356+
do! WarnD (ObsoleteDiagnostic(false, diagnosticId, Some s, urlFormat, m))
341357
// Only DiagnosticId, UrlFormat
342358
| Some(Attrib(propVal= namedArgs)) ->
343359
let diagnosticId, urlFormat = extractObsoleteAttributeInfo namedArgs
344-
do! WarnD(ObsoleteDiagnostic(false, diagnosticId, "", urlFormat, m))
360+
do! WarnD(ObsoleteDiagnostic(false, diagnosticId, None, urlFormat, m))
345361
| None -> ()
346362
}
347363

@@ -366,21 +382,21 @@ let private CheckCompilerMessageAttribute g attribs m =
366382
()
367383
}
368384

369-
let private CheckExperimentalAttribute g attribs m =
385+
let private CheckFSharpExperimentalAttribute g attribs m =
370386
trackErrors {
371387
match TryFindFSharpAttribute g g.attrib_ExperimentalAttribute attribs with
388+
// [<Experimental("Message")>]
372389
| Some(Attrib(unnamedArgs= [ AttribStringArg(s) ])) ->
373390
let isExperimentalAttributeDisabled (s:string) =
374391
if g.compilingFSharpCore then
375392
true
376393
else
377-
g.langVersion.IsPreviewEnabled && (s.IndexOf(langVersionPrefix, StringComparison.OrdinalIgnoreCase) >= 0)
394+
g.langVersion.IsPreviewEnabled && (s.IndexOf("--langversion:preview", StringComparison.OrdinalIgnoreCase) >= 0)
378395
if not (isExperimentalAttributeDisabled s) then
379-
do! WarnD(Experimental(s, m))
380-
| Some _ ->
381-
do! WarnD(Experimental(FSComp.SR.experimentalConstruct (), m))
382-
| _ ->
383-
()
396+
do! WarnD(Experimental(Some s, None, None, m))
397+
// Empty constructor is not allowed.
398+
| Some _
399+
| _ -> ()
384400
}
385401

386402
let private CheckUnverifiableAttribute g attribs m =
@@ -399,7 +415,7 @@ let CheckFSharpAttributes (g:TcGlobals) attribs m =
399415
trackErrors {
400416
do! CheckObsoleteAttributes g attribs m
401417
do! CheckCompilerMessageAttribute g attribs m
402-
do! CheckExperimentalAttribute g attribs m
418+
do! CheckFSharpExperimentalAttribute g attribs m
403419
do! CheckUnverifiableAttribute g attribs m
404420
}
405421

@@ -408,16 +424,16 @@ let CheckFSharpAttributes (g:TcGlobals) attribs m =
408424
let private CheckProvidedAttributes (g: TcGlobals) m (provAttribs: Tainted<IProvidedCustomAttributeProvider>) =
409425
let (AttribInfo(tref, _)) = g.attrib_SystemObsolete
410426
match provAttribs.PUntaint((fun a -> a.GetAttributeConstructorArgs(provAttribs.TypeProvider.PUntaintNoFailure(id), tref.FullName)), m) with
411-
| Some ([ Some (:? string as msg) ], _) -> WarnD(ObsoleteDiagnostic(false, "", msg, "", m))
427+
| Some ([ Some (:? string as msg) ], _) -> WarnD(ObsoleteDiagnostic(false, None, Some msg, None, m))
412428
| Some ([ Some (:? string as msg); Some (:?bool as isError) ], _) ->
413429
if isError then
414-
ErrorD (ObsoleteDiagnostic(true, "", msg, "", m))
430+
ErrorD (ObsoleteDiagnostic(true, None, Some msg, None, m))
415431
else
416-
WarnD (ObsoleteDiagnostic(false, "", msg, "", m))
432+
WarnD (ObsoleteDiagnostic(false, None, Some msg, None, m))
417433
| Some ([ None ], _) ->
418-
WarnD(ObsoleteDiagnostic(false, "", "", "", m))
434+
WarnD(ObsoleteDiagnostic(false, None, None, None, m))
419435
| Some _ ->
420-
WarnD(ObsoleteDiagnostic(false, "", "", "", m))
436+
WarnD(ObsoleteDiagnostic(false, None, None, None, m))
421437
| None ->
422438
CompleteD
423439
#endif

src/Compiler/Checking/AttributeChecking.fsi

-7
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,6 @@ open FSharp.Compiler.TcGlobals
1313
open FSharp.Compiler.Text
1414
open FSharp.Compiler.TypedTree
1515

16-
exception ObsoleteDiagnostic of
17-
isError: bool *
18-
diagnosticId: string *
19-
message: string *
20-
urlFormat: string *
21-
range: range
22-
2316
type AttribInfo =
2417
| FSAttribInfo of TcGlobals * Attrib
2518
| ILAttribInfo of TcGlobals * Import.ImportMap * ILScopeRef * ILAttribute * range

src/Compiler/Checking/PostInferenceChecks.fs

+1-1
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ let WarnOnWrongTypeForAccess (cenv: cenv) env objName valAcc m ty =
551551
if isLessAccessible tyconAcc valAcc then
552552
let errorText = FSComp.SR.chkTypeLessAccessibleThanType(tcref.DisplayName, (objName())) |> snd
553553
let warningText = errorText + Environment.NewLine + FSComp.SR.tcTypeAbbreviationsCheckedAtCompileTime()
554-
warning(AttributeChecking.ObsoleteDiagnostic(false, "", warningText, "", m))
554+
warning(ObsoleteDiagnostic(false, None, Some warningText, None, m))
555555

556556
CheckTypeDeep cenv (visitType, None, None, None, None) cenv.g env NoInfo ty
557557

src/Compiler/Driver/CompilerDiagnostics.fs

+16-7
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ open Internal.Utilities.Library
1414
open Internal.Utilities.Text
1515

1616
open FSharp.Compiler
17-
open FSharp.Compiler.AttributeChecking
1817
open FSharp.Compiler.CheckExpressions
1918
open FSharp.Compiler.CheckDeclarations
2019
open FSharp.Compiler.CheckIncrementalClasses
@@ -149,7 +148,7 @@ type Exception with
149148
| ValueRestriction(_, _, _, _, m)
150149
| LetRecUnsound(_, _, m)
151150
| ObsoleteDiagnostic(_, _, _, _, m)
152-
| Experimental(_, m)
151+
| Experimental(range = m)
153152
| PossibleUnverifiableCode m
154153
| UserCompilerMessage(_, _, m)
155154
| Deprecated(_, m)
@@ -568,7 +567,9 @@ module OldStyleMessages =
568567
let ValNotLocalE () = Message("ValNotLocal", "")
569568
let Obsolete1E () = Message("Obsolete1", "")
570569
let Obsolete2E () = Message("Obsolete2", "%s")
571-
let ExperimentalE () = Message("Experimental", "%s")
570+
let Experimental1E () = Message("Experimental1", "")
571+
let Experimental2E () = Message("Experimental2", "%s")
572+
let Experimental3E () = Message("Experimental3", "")
572573
let PossibleUnverifiableCodeE () = Message("PossibleUnverifiableCode", "")
573574
let DeprecatedE () = Message("Deprecated", "%s")
574575
let LibraryUseOnlyE () = Message("LibraryUseOnly", "")
@@ -1791,13 +1792,21 @@ type Exception with
17911792

17921793
| ValNotLocal _ -> os.AppendString(ValNotLocalE().Format)
17931794

1794-
| ObsoleteDiagnostic(message = s) ->
1795+
| ObsoleteDiagnostic(message = message) ->
17951796
os.AppendString(Obsolete1E().Format)
17961797

1797-
if s <> "" then
1798-
os.AppendString(Obsolete2E().Format s)
1798+
match message with
1799+
| Some message when message <> "" -> os.AppendString(Obsolete2E().Format message)
1800+
| _ -> ()
1801+
1802+
| Experimental(message = message) ->
1803+
os.AppendString(Experimental1E().Format)
1804+
1805+
match message with
1806+
| Some message when message <> "" -> os.AppendString(Experimental2E().Format message)
1807+
| _ -> ()
17991808

1800-
| Experimental(s, _) -> os.AppendString(ExperimentalE().Format s)
1809+
os.AppendString(Experimental3E().Format)
18011810

18021811
| PossibleUnverifiableCode _ -> os.AppendString(PossibleUnverifiableCodeE().Format)
18031812

src/Compiler/FSStrings.resx

+8-2
Original file line numberDiff line numberDiff line change
@@ -1029,8 +1029,14 @@
10291029
<data name="Obsolete2" xml:space="preserve">
10301030
<value>. {0}</value>
10311031
</data>
1032-
<data name="Experimental" xml:space="preserve">
1033-
<value>{0}. This warning can be disabled using '--nowarn:57' or '#nowarn "57"'.</value>
1032+
<data name="Experimental1" xml:space="preserve">
1033+
<value>This construct is experimental</value>
1034+
</data>
1035+
<data name="Experimental2" xml:space="preserve">
1036+
<value>. {0}</value>
1037+
</data>
1038+
<data name="Experimental3" xml:space="preserve">
1039+
<value>. This warning can be disabled using '--nowarn:57' or '#nowarn "57"'.</value>
10341040
</data>
10351041
<data name="PossibleUnverifiableCode" xml:space="preserve">
10361042
<value>Uses of this construct may result in the generation of unverifiable .NET IL code. This warning can be disabled using '--nowarn:9' or '#nowarn "9"'.</value>

src/Compiler/Facilities/DiagnosticsLogger.fs

+9-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ exception LibraryUseOnly of range: range
108108

109109
exception Deprecated of message: string * range: range
110110

111-
exception Experimental of message: string * range: range
111+
exception Experimental of message: string option * diagnosticId: string option * urlFormat: string option * range: range
112112

113113
exception PossibleUnverifiableCode of range: range
114114

@@ -133,6 +133,14 @@ exception DiagnosticWithSuggestions of number: int * message: string * range: ra
133133
/// A diagnostic that is raised when enabled manually, or by default with a language feature
134134
exception DiagnosticEnabledWithLanguageFeature of number: int * message: string * range: range * enabledByLangFeature: bool
135135

136+
/// A diagnostic that is raised when a diagnostic is obsolete
137+
exception ObsoleteDiagnostic of
138+
isError: bool *
139+
diagnosticId: string option *
140+
message: string option *
141+
urlFormat: string option *
142+
range: range
143+
136144
/// The F# compiler code currently uses 'Error(...)' in many places to create
137145
/// an DiagnosticWithText as an exception even if it's a warning.
138146
///

src/Compiler/Facilities/DiagnosticsLogger.fsi

+8-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ exception LibraryUseOnly of range: range
6868

6969
exception Deprecated of message: string * range: range
7070

71-
exception Experimental of message: string * range: range
71+
exception Experimental of message: string option * diagnosticId: string option * urlFormat: string option * range: range
7272

7373
exception PossibleUnverifiableCode of range: range
7474

@@ -87,6 +87,13 @@ exception DiagnosticWithSuggestions of
8787
identifier: string *
8888
suggestions: Suggestions
8989

90+
exception ObsoleteDiagnostic of
91+
isError: bool *
92+
diagnosticId: string option *
93+
message: string option *
94+
urlFormat: string option *
95+
range: range
96+
9097
/// Creates a DiagnosticWithSuggestions whose text comes via SR.*
9198
val ErrorWithSuggestions: (int * string) * range * string * Suggestions -> exn
9299

0 commit comments

Comments
 (0)