Skip to content

Commit 4b676b4

Browse files
authored
Update Obsolete attribute checking to account for diagnosticId and urlFormat properties (#18224)
1 parent 5bfbc6d commit 4b676b4

File tree

12 files changed

+512
-93
lines changed

12 files changed

+512
-93
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
### Fixed
2-
32
* Fix Realsig+ generates nested closures with incorrect Generic ([Issue #17797](https://github.com/dotnet/fsharp/issues/17797), [PR #17877](https://github.com/dotnet/fsharp/pull/17877))
43
* Fix internal error when missing measure attribute in an unsolved measure typar. ([Issue #7491](https://github.com/dotnet/fsharp/issues/7491), [PR #18234](https://github.com/dotnet/fsharp/pull/18234)==
54
* Set `Cancellable.token` from async computation ([Issue #18235](https://github.com/dotnet/fsharp/issues/18235), [PR #18238](https://github.com/dotnet/fsharp/pull/18238))
65

76
### Added
8-
97
* Added missing type constraints in FCS. ([PR #18241](https://github.com/dotnet/fsharp/pull/18241))
108

119
### Changed
12-
10+
* Update `Obsolete` attribute checking to account for `DiagnosticId` and `UrlFormat` properties. ([PR #18224](https://github.com/dotnet/fsharp/pull/18224))
1311

1412
### Breaking Changes
15-

src/Compiler/Checking/AttributeChecking.fs

100644100755
+153-77
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,12 @@ open FSharp.Compiler.TypeProviders
2424
open FSharp.Core.CompilerServices
2525
#endif
2626

27-
exception ObsoleteWarning of string * range
28-
exception ObsoleteError of string * range
27+
exception ObsoleteDiagnostic of
28+
isError: bool *
29+
diagnosticId: string *
30+
message: string *
31+
urlFormat: string *
32+
range: range
2933

3034
let fail() = failwith "This custom attribute has an argument that cannot yet be converted using this API"
3135

@@ -234,7 +238,6 @@ let MethInfoHasAttribute g m attribSpec minfo =
234238
(fun _ -> Some ())
235239
|> Option.isSome
236240

237-
238241
let private CheckCompilerFeatureRequiredAttribute (g: TcGlobals) cattrs msg m =
239242
// In some cases C# will generate both ObsoleteAttribute and CompilerFeatureRequiredAttribute.
240243
// Specifically, when default constructor is generated for class with any required members in them.
@@ -244,104 +247,177 @@ let private CheckCompilerFeatureRequiredAttribute (g: TcGlobals) cattrs msg m =
244247
| Some([ILAttribElem.String (Some featureName) ], _) when featureName = "RequiredMembers" ->
245248
CompleteD
246249
| _ ->
247-
ErrorD (ObsoleteError(msg, m))
250+
ErrorD (ObsoleteDiagnostic(true, "", msg, "", m))
251+
252+
let private extractILObsoleteAttributeInfo namedArgs =
253+
let extractILAttribValueFrom name namedArgs =
254+
match namedArgs with
255+
| ExtractILAttributeNamedArg name (AttribElemStringArg v) -> v
256+
| _ -> ""
257+
let diagnosticId = extractILAttribValueFrom "DiagnosticId" namedArgs
258+
let urlFormat = extractILAttribValueFrom "UrlFormat" namedArgs
259+
(diagnosticId, urlFormat)
260+
261+
let private CheckILObsoleteAttributes (g: TcGlobals) isByrefLikeTyconRef cattrs m =
262+
if isByrefLikeTyconRef then
263+
CompleteD
264+
else
265+
let (AttribInfo(tref,_)) = g.attrib_SystemObsolete
266+
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")>]
276+
// Constructors deciding on IsError and Message properties.
277+
| Some ([ attribElement ], namedArgs) ->
278+
let diagnosticId, urlFormat = extractILObsoleteAttributeInfo namedArgs
279+
let msg =
280+
match attribElement with
281+
| ILAttribElem.String (Some msg) -> msg
282+
| ILAttribElem.String None
283+
| _ -> ""
284+
285+
WarnD (ObsoleteDiagnostic(false, diagnosticId, msg, urlFormat, m))
286+
| Some ([ILAttribElem.String (Some msg); ILAttribElem.Bool isError ], namedArgs) ->
287+
let diagnosticId, urlFormat = extractILObsoleteAttributeInfo namedArgs
288+
if isError then
289+
if g.langVersion.SupportsFeature(LanguageFeature.RequiredPropertiesSupport) then
290+
CheckCompilerFeatureRequiredAttribute g cattrs msg m
291+
else
292+
ErrorD (ObsoleteDiagnostic(true, diagnosticId, msg, urlFormat, m))
293+
else
294+
WarnD (ObsoleteDiagnostic(false, diagnosticId, msg, urlFormat, m))
295+
// Only DiagnosticId, UrlFormat
296+
| Some (_, namedArgs) ->
297+
let diagnosticId, urlFormat = extractILObsoleteAttributeInfo namedArgs
298+
WarnD(ObsoleteDiagnostic(false, diagnosticId, "", urlFormat, m))
299+
// No arguments
300+
| None -> CompleteD
248301

249302
/// Check IL attributes for 'ObsoleteAttribute', returning errors and warnings as data
250303
let private CheckILAttributes (g: TcGlobals) isByrefLikeTyconRef cattrs m =
251-
let (AttribInfo(tref,_)) = g.attrib_SystemObsolete
252-
match TryDecodeILAttribute tref cattrs with
253-
| Some ([ILAttribElem.String (Some msg) ], _) when not isByrefLikeTyconRef ->
254-
WarnD(ObsoleteWarning(msg, m))
255-
| Some ([ILAttribElem.String (Some msg); ILAttribElem.Bool isError ], _) when not isByrefLikeTyconRef ->
256-
if isError then
257-
if g.langVersion.SupportsFeature(LanguageFeature.RequiredPropertiesSupport) then
258-
CheckCompilerFeatureRequiredAttribute g cattrs msg m
259-
else
260-
ErrorD (ObsoleteError(msg, m))
261-
else
262-
WarnD (ObsoleteWarning(msg, m))
263-
| Some ([ILAttribElem.String None ], _) when not isByrefLikeTyconRef ->
264-
WarnD(ObsoleteWarning("", m))
265-
| Some _ when not isByrefLikeTyconRef ->
266-
WarnD(ObsoleteWarning("", m))
267-
| _ ->
268-
CompleteD
304+
trackErrors {
305+
do! CheckILObsoleteAttributes g isByrefLikeTyconRef cattrs m
306+
}
269307

270308
let langVersionPrefix = "--langversion:preview"
271309

310+
let private extractObsoleteAttributeInfo namedArgs =
311+
let extractILAttribValueFrom name namedArgs =
312+
match namedArgs with
313+
| ExtractAttribNamedArg name (AttribStringArg v) -> v
314+
| _ -> ""
315+
let diagnosticId = extractILAttribValueFrom "DiagnosticId" namedArgs
316+
let urlFormat = extractILAttribValueFrom "UrlFormat" namedArgs
317+
(diagnosticId, urlFormat)
318+
319+
let private CheckObsoleteAttributes g attribs m =
320+
trackErrors {
321+
match TryFindFSharpAttribute g g.attrib_SystemObsolete attribs with
322+
// [<Obsolete>]
323+
// [<Obsolete("Message")>]
324+
// [<Obsolete("Message", true)>]
325+
// [<Obsolete("Message", DiagnosticId = "DiagnosticId")>]
326+
// [<Obsolete("Message", DiagnosticId = "DiagnosticId", UrlFormat = "UrlFormat")>]
327+
// [<Obsolete(DiagnosticId = "DiagnosticId")>]
328+
// [<Obsolete(DiagnosticId = "DiagnosticId", UrlFormat = "UrlFormat")>]
329+
// [<Obsolete("Message", true, DiagnosticId = "DiagnosticId")>]
330+
// [<Obsolete("Message", true, DiagnosticId = "DiagnosticId", UrlFormat = "UrlFormat")>]
331+
// Constructors deciding on IsError and Message properties.
332+
| Some(Attrib(unnamedArgs= [ AttribStringArg s ]; propVal= namedArgs)) ->
333+
let diagnosticId, urlFormat = extractObsoleteAttributeInfo namedArgs
334+
do! WarnD(ObsoleteDiagnostic(false, diagnosticId, s, urlFormat, m))
335+
| Some(Attrib(unnamedArgs= [ AttribStringArg s; AttribBoolArg(isError) ]; propVal= namedArgs)) ->
336+
let diagnosticId, urlFormat = extractObsoleteAttributeInfo namedArgs
337+
if isError then
338+
do! ErrorD (ObsoleteDiagnostic(true, diagnosticId, s, urlFormat, m))
339+
else
340+
do! WarnD (ObsoleteDiagnostic(false, diagnosticId, s, urlFormat, m))
341+
// Only DiagnosticId, UrlFormat
342+
| Some(Attrib(propVal= namedArgs)) ->
343+
let diagnosticId, urlFormat = extractObsoleteAttributeInfo namedArgs
344+
do! WarnD(ObsoleteDiagnostic(false, diagnosticId, "", urlFormat, m))
345+
| None -> ()
346+
}
347+
348+
let private CheckCompilerMessageAttribute g attribs m =
349+
trackErrors {
350+
match TryFindFSharpAttribute g g.attrib_CompilerMessageAttribute attribs with
351+
| Some(Attrib(unnamedArgs= [ AttribStringArg s ; AttribInt32Arg n ]; propVal= namedArgs)) ->
352+
let msg = UserCompilerMessage(s, n, m)
353+
let isError =
354+
match namedArgs with
355+
| ExtractAttribNamedArg "IsError" (AttribBoolArg v) -> v
356+
| _ -> false
357+
// If we are using a compiler that supports nameof then error 3501 is always suppressed.
358+
// See attribute on FSharp.Core 'nameof'
359+
if n = 3501 then
360+
()
361+
elif isError && (not g.compilingFSharpCore || n <> 1204) then
362+
do! ErrorD msg
363+
else
364+
do! WarnD msg
365+
| _ ->
366+
()
367+
}
368+
369+
let private CheckExperimentalAttribute g attribs m =
370+
trackErrors {
371+
match TryFindFSharpAttribute g g.attrib_ExperimentalAttribute attribs with
372+
| Some(Attrib(unnamedArgs= [ AttribStringArg(s) ])) ->
373+
let isExperimentalAttributeDisabled (s:string) =
374+
if g.compilingFSharpCore then
375+
true
376+
else
377+
g.langVersion.IsPreviewEnabled && (s.IndexOf(langVersionPrefix, StringComparison.OrdinalIgnoreCase) >= 0)
378+
if not (isExperimentalAttributeDisabled s) then
379+
do! WarnD(Experimental(s, m))
380+
| Some _ ->
381+
do! WarnD(Experimental(FSComp.SR.experimentalConstruct (), m))
382+
| _ ->
383+
()
384+
}
385+
386+
let private CheckUnverifiableAttribute g attribs m =
387+
trackErrors {
388+
match TryFindFSharpAttribute g g.attrib_UnverifiableAttribute attribs with
389+
| Some _ ->
390+
do! WarnD(PossibleUnverifiableCode(m))
391+
| _ -> ()
392+
}
393+
272394
/// Check F# attributes for 'ObsoleteAttribute', 'CompilerMessageAttribute' and 'ExperimentalAttribute',
273395
/// returning errors and warnings as data
274396
let CheckFSharpAttributes (g:TcGlobals) attribs m =
275397
if isNil attribs then CompleteD
276398
else
277399
trackErrors {
278-
match TryFindFSharpAttribute g g.attrib_SystemObsolete attribs with
279-
| Some(Attrib(_, _, [ AttribStringArg s ], _, _, _, _)) ->
280-
do! WarnD(ObsoleteWarning(s, m))
281-
| Some(Attrib(_, _, [ AttribStringArg s; AttribBoolArg(isError) ], _, _, _, _)) ->
282-
if isError then
283-
do! ErrorD (ObsoleteError(s, m))
284-
else
285-
do! WarnD (ObsoleteWarning(s, m))
286-
| Some _ ->
287-
do! WarnD(ObsoleteWarning("", m))
288-
| None ->
289-
()
290-
291-
match TryFindFSharpAttribute g g.attrib_CompilerMessageAttribute attribs with
292-
| Some(Attrib(_, _, [ AttribStringArg s ; AttribInt32Arg n ], namedArgs, _, _, _)) ->
293-
let msg = UserCompilerMessage(s, n, m)
294-
let isError =
295-
match namedArgs with
296-
| ExtractAttribNamedArg "IsError" (AttribBoolArg v) -> v
297-
| _ -> false
298-
// If we are using a compiler that supports nameof then error 3501 is always suppressed.
299-
// See attribute on FSharp.Core 'nameof'
300-
if n = 3501 then
301-
()
302-
elif isError && (not g.compilingFSharpCore || n <> 1204) then
303-
do! ErrorD msg
304-
else
305-
do! WarnD msg
306-
| _ ->
307-
()
308-
309-
match TryFindFSharpAttribute g g.attrib_ExperimentalAttribute attribs with
310-
| Some(Attrib(_, _, [ AttribStringArg(s) ], _, _, _, _)) ->
311-
let isExperimentalAttributeDisabled (s:string) =
312-
if g.compilingFSharpCore then
313-
true
314-
else
315-
g.langVersion.IsPreviewEnabled && (s.IndexOf(langVersionPrefix, StringComparison.OrdinalIgnoreCase) >= 0)
316-
if not (isExperimentalAttributeDisabled s) then
317-
do! WarnD(Experimental(s, m))
318-
| Some _ ->
319-
do! WarnD(Experimental(FSComp.SR.experimentalConstruct (), m))
320-
| _ ->
321-
()
322-
323-
match TryFindFSharpAttribute g g.attrib_UnverifiableAttribute attribs with
324-
| Some _ ->
325-
do! WarnD(PossibleUnverifiableCode(m))
326-
| _ ->
327-
()
400+
do! CheckObsoleteAttributes g attribs m
401+
do! CheckCompilerMessageAttribute g attribs m
402+
do! CheckExperimentalAttribute g attribs m
403+
do! CheckUnverifiableAttribute g attribs m
328404
}
329405

330406
#if !NO_TYPEPROVIDERS
331407
/// Check a list of provided attributes for 'ObsoleteAttribute', returning errors and warnings as data
332408
let private CheckProvidedAttributes (g: TcGlobals) m (provAttribs: Tainted<IProvidedCustomAttributeProvider>) =
333409
let (AttribInfo(tref, _)) = g.attrib_SystemObsolete
334410
match provAttribs.PUntaint((fun a -> a.GetAttributeConstructorArgs(provAttribs.TypeProvider.PUntaintNoFailure(id), tref.FullName)), m) with
335-
| Some ([ Some (:? string as msg) ], _) -> WarnD(ObsoleteWarning(msg, m))
411+
| Some ([ Some (:? string as msg) ], _) -> WarnD(ObsoleteDiagnostic(false, "", msg, "", m))
336412
| Some ([ Some (:? string as msg); Some (:?bool as isError) ], _) ->
337413
if isError then
338-
ErrorD (ObsoleteError(msg, m))
414+
ErrorD (ObsoleteDiagnostic(true, "", msg, "", m))
339415
else
340-
WarnD (ObsoleteWarning(msg, m))
416+
WarnD (ObsoleteDiagnostic(false, "", msg, "", m))
341417
| Some ([ None ], _) ->
342-
WarnD(ObsoleteWarning("", m))
418+
WarnD(ObsoleteDiagnostic(false, "", "", "", m))
343419
| Some _ ->
344-
WarnD(ObsoleteWarning("", m))
420+
WarnD(ObsoleteDiagnostic(false, "", "", "", m))
345421
| None ->
346422
CompleteD
347423
#endif

src/Compiler/Checking/AttributeChecking.fsi

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

16-
exception ObsoleteWarning of string * range
17-
18-
exception ObsoleteError of string * range
16+
exception ObsoleteDiagnostic of
17+
isError: bool *
18+
diagnosticId: string *
19+
message: string *
20+
urlFormat: string *
21+
range: range
1922

2023
type AttribInfo =
2124
| FSAttribInfo of TcGlobals * Attrib

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.ObsoleteWarning(warningText, m))
554+
warning(AttributeChecking.ObsoleteDiagnostic(false, "", warningText, "", m))
555555

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

src/Compiler/Driver/CompilerDiagnostics.fs

+4-7
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,7 @@ type Exception with
148148
| IntfImplInExtrinsicAugmentation m
149149
| ValueRestriction(_, _, _, _, m)
150150
| LetRecUnsound(_, _, m)
151-
| ObsoleteError(_, m)
152-
| ObsoleteWarning(_, m)
151+
| ObsoleteDiagnostic(_, _, _, _, m)
153152
| Experimental(_, m)
154153
| PossibleUnverifiableCode m
155154
| UserCompilerMessage(_, _, m)
@@ -266,7 +265,7 @@ type Exception with
266265
| UnresolvedOverloading _ -> 41
267266
| LibraryUseOnly _ -> 42
268267
| ErrorFromAddingConstraint _ -> 43
269-
| ObsoleteWarning _ -> 44
268+
| ObsoleteDiagnostic(isError = false) -> 44
270269
| ReservedKeyword _ -> 46
271270
| SelfRefObjCtor _ -> 47
272271
| VirtualAugmentationOnNullValuedType _ -> 48
@@ -327,7 +326,7 @@ type Exception with
327326
| UnresolvedConversionOperator _ -> 93
328327

329328
// avoid 94-100 for safety
330-
| ObsoleteError _ -> 101
329+
| ObsoleteDiagnostic(isError = true) -> 101
331330
#if !NO_TYPEPROVIDERS
332331
| TypeProviders.ProvidedTypeResolutionNoRange _
333332
| TypeProviders.ProvidedTypeResolution _ -> 103
@@ -1790,9 +1789,7 @@ type Exception with
17901789

17911790
| ValNotLocal _ -> os.AppendString(ValNotLocalE().Format)
17921791

1793-
| ObsoleteError(s, _)
1794-
1795-
| ObsoleteWarning(s, _) ->
1792+
| ObsoleteDiagnostic(message = s) ->
17961793
os.AppendString(Obsolete1E().Format)
17971794

17981795
if s <> "" then

src/Compiler/Symbols/FSharpDiagnostic.fs

+14-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ namespace FSharp.Compiler.Diagnostics
99

1010
open System
1111

12+
open FSharp.Compiler.AttributeChecking
1213
open FSharp.Compiler.CheckExpressions
1314
open FSharp.Compiler.ConstraintSolver
1415
open FSharp.Compiler.SignatureConformance
@@ -18,7 +19,6 @@ open FSharp.Compiler.TypedTree
1819
open FSharp.Compiler.TypedTreeBasics
1920
open FSharp.Compiler.TypedTreeOps
2021
open Internal.Utilities.Library
21-
open Internal.Utilities.Library.Extras
2222

2323
open FSharp.Core.Printf
2424
open FSharp.Compiler
@@ -67,6 +67,17 @@ module ExtendedData =
6767
[<Interface; Experimental("This FCS API is experimental and subject to change.")>]
6868
type IFSharpDiagnosticExtendedData = interface end
6969

70+
/// Additional data for diagnostics about obsolete attributes.
71+
[<Class; Experimental("This FCS API is experimental and subject to change.")>]
72+
type ObsoleteDiagnosticExtendedData
73+
internal (diagnosticId: string, urlFormat: string) =
74+
interface IFSharpDiagnosticExtendedData
75+
/// Represents the DiagnosticId of the diagnostic
76+
member this.DiagnosticId: string = diagnosticId
77+
78+
/// Represents the URL format of the diagnostic
79+
member this.UrlFormat: string = urlFormat
80+
7081
[<Experimental("This FCS API is experimental and subject to change.")>]
7182
type TypeMismatchDiagnosticExtendedData
7283
internal (symbolEnv: SymbolEnv, dispEnv: DisplayEnv, expectedType: TType, actualType: TType, context: DiagnosticContextInfo) =
@@ -201,6 +212,8 @@ type FSharpDiagnostic(m: range, severity: FSharpDiagnosticSeverity, message: str
201212
| DefinitionsInSigAndImplNotCompatibleAbbreviationsDiffer(implTycon = implTycon; sigTycon = sigTycon) ->
202213
Some(DefinitionsInSigAndImplNotCompatibleAbbreviationsDifferExtendedData(sigTycon, implTycon))
203214

215+
| ObsoleteDiagnostic(diagnosticId= diagnosticId; urlFormat= urlFormat) ->
216+
Some(ObsoleteDiagnosticExtendedData(diagnosticId, urlFormat))
204217
| _ -> None
205218

206219
let msg =

0 commit comments

Comments
 (0)