Skip to content

Commit ae7ee86

Browse files
authored
opt-in warning attribute not valid for union case with fields (#18532)
1 parent ec503f9 commit ae7ee86

File tree

20 files changed

+210
-84
lines changed

20 files changed

+210
-84
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
### Added
2+
* Add opt-in warning attribute not valid for union case with fields [PR #18532](https://github.com/dotnet/fsharp/pull/18532))
3+
14
### Fixed
25

36
* Fix parsing errors using anonymous records and units of measures ([PR #18543](https://github.com/dotnet/fsharp/pull/18543))

src/Compiler/Checking/CheckDeclarations.fs

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -576,22 +576,33 @@ module TcRecdUnionAndEnumDeclarations =
576576

577577
let checkXmlDocs = cenv.diagnosticOptions.CheckXmlDocs
578578
let xmlDoc = xmldoc.ToXmlDoc(checkXmlDocs, Some names)
579-
let attrs =
580-
(*
581-
The attributes of a union case decl get attached to the generated "static factory" method.
582-
Enforce union-cases AttributeTargets:
583-
- AttributeTargets.Method
584-
type SomeUnion =
585-
| Case1 of int // Compiles down to a static method
586-
- AttributeTargets.Property
587-
type SomeUnion =
588-
| Case1 // Compiles down to a static property
589-
*)
590-
if g.langVersion.SupportsFeature(LanguageFeature.EnforceAttributeTargets) then
591-
let target = if rfields.IsEmpty then AttributeTargets.Property else AttributeTargets.Method
592-
TcAttributes cenv env target synAttrs
593-
else
594-
TcAttributes cenv env AttributeTargets.UnionCaseDecl synAttrs
579+
let attrs = TcAttributes cenv env AttributeTargets.UnionCaseDecl synAttrs
580+
(*
581+
The attributes of a union case decl get attached to the generated "static factory" method.
582+
Enforce union-cases AttributeTargets:
583+
- AttributeTargets.Method
584+
type SomeUnion =
585+
| Case1 of int // Compiles down to a static method
586+
- AttributeTargets.Property
587+
type SomeUnion =
588+
| Case1 // Compiles down to a static property
589+
*)
590+
if g.langVersion.SupportsFeature(LanguageFeature.EnforceAttributeTargets) then
591+
let attrTargets =
592+
attrs
593+
|> List.collect (fun attr ->
594+
attr.TyconRef.Attribs
595+
|> List.choose (fun attr ->
596+
match attr with
597+
| Attrib(unnamedArgs = [ AttribInt32Arg validOn ]) -> Some validOn
598+
| _ -> None))
599+
600+
attrTargets
601+
|> List.iter (fun target ->
602+
// If the union case has fields, and the target is not AttributeTargets.Method || AttributeTargets.All. Then we raise a separate opt-in warning
603+
let hasNotMethodTarget = (enum target &&& AttributeTargets.Method) = enum 0
604+
if hasNotMethodTarget then
605+
warning(Error(FSComp.SR.tcAttributeIsNotValidForUnionCaseWithFields(), id.idRange)))
595606

596607
Construct.NewUnionCase id rfields recordTy attrs xmlDoc vis
597608

src/Compiler/Checking/Expressions/CheckExpressions.fs

Lines changed: 73 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -11288,6 +11288,75 @@ and TcNonRecursiveBinding declKind cenv env tpenv ty binding =
1128811288
let explicitTyparInfo, tpenv = TcNonrecBindingTyparDecls cenv env tpenv binding
1128911289
TcNormalizedBinding declKind cenv env tpenv ty None NoSafeInitInfo ([], explicitTyparInfo) binding
1129011290

11291+
and ResolveAttributeType (cenv: cenv) (env: TcEnv) (mAttr: range) (tycon: Ident list) =
11292+
let tpenv = emptyUnscopedTyparEnv
11293+
let ad = env.eAccessRights
11294+
11295+
let tyPath, tyId = List.frontAndBack tycon
11296+
11297+
let try1 n =
11298+
let tyid = mkSynId tyId.idRange n
11299+
let tycon = (tyPath @ [tyid])
11300+
11301+
match ResolveTypeLongIdent cenv.tcSink cenv.nameResolver ItemOccurrence.UseInAttribute OpenQualified env.eNameResEnv ad tycon TypeNameResolutionStaticArgsInfo.DefiniteEmpty PermitDirectReferenceToGeneratedType.No with
11302+
| Exception err -> raze err
11303+
| Result(tinstEnclosing, tcref, inst) -> success(TcTypeApp cenv NoNewTypars CheckCxs ItemOccurrence.UseInAttribute env tpenv mAttr tcref tinstEnclosing [] inst)
11304+
11305+
ForceRaise ((try1 (tyId.idText + "Attribute")) |> otherwise (fun () -> (try1 tyId.idText)))
11306+
11307+
and CheckAttributeUsage (g: TcGlobals) (mAttr: range) (tcref: TyconRef) (attrTgt: AttributeTargets) (targetIndicator: Ident option) (attrEx: AttributeTargets) =
11308+
// REVIEW: take notice of inherited?
11309+
let validOn, _inherited =
11310+
let validOnDefault = 0x7fff
11311+
let inheritedDefault = true
11312+
if tcref.IsILTycon then
11313+
let tdef = tcref.ILTyconRawMetadata
11314+
let tref = g.attrib_AttributeUsageAttribute.TypeRef
11315+
11316+
match TryDecodeILAttribute tref tdef.CustomAttrs with
11317+
| Some ([ILAttribElem.Int32 validOn ], named) ->
11318+
let inherited =
11319+
match List.tryPick (function "Inherited", _, _, ILAttribElem.Bool res -> Some res | _ -> None) named with
11320+
| None -> inheritedDefault
11321+
| Some x -> x
11322+
(validOn, inherited)
11323+
| Some ([ILAttribElem.Int32 validOn; ILAttribElem.Bool _allowMultiple; ILAttribElem.Bool inherited ], _) ->
11324+
(validOn, inherited)
11325+
| _ ->
11326+
(validOnDefault, inheritedDefault)
11327+
else
11328+
match (TryFindFSharpAttribute g g.attrib_AttributeUsageAttribute tcref.Attribs) with
11329+
| Some(Attrib(unnamedArgs = [ AttribInt32Arg validOn ])) ->
11330+
validOn, inheritedDefault
11331+
| Some(Attrib(unnamedArgs = [ AttribInt32Arg validOn; AttribBoolArg(_allowMultiple); AttribBoolArg inherited])) ->
11332+
validOn, inherited
11333+
| Some _ ->
11334+
warning(Error(FSComp.SR.tcUnexpectedConditionInImportedAssembly(), mAttr))
11335+
validOnDefault, inheritedDefault
11336+
| _ ->
11337+
validOnDefault, inheritedDefault
11338+
11339+
// Determine valid attribute targets
11340+
let attributeTargets = enum validOn &&& attrTgt
11341+
let directedTargets =
11342+
match targetIndicator with
11343+
| LongFormAttrTarget attrTarget -> attrTarget
11344+
| UnrecognizedLongAttrTarget attrTarget ->
11345+
errorR(Error(FSComp.SR.tcUnrecognizedAttributeTarget(), attrTarget.idRange))
11346+
attributeTargets
11347+
| ShortFormAttributeTarget -> attributeTargets &&& ~~~ attrEx
11348+
11349+
let constrainedTargets = attributeTargets &&& directedTargets
11350+
11351+
// Check if attribute is valid for the target
11352+
if constrainedTargets = enum 0 then
11353+
if (directedTargets = AttributeTargets.Assembly || directedTargets = AttributeTargets.Module) then
11354+
errorR(Error(FSComp.SR.tcAttributeIsNotValidForLanguageElementUseDo(), mAttr))
11355+
else
11356+
warning(Error(FSComp.SR.tcAttributeIsNotValidForLanguageElement(), mAttr))
11357+
11358+
constrainedTargets
11359+
1129111360
//-------------------------------------------------------------------------
1129211361
// TcAttribute*
1129311362
// *Ex means the function accepts attribute targets that must be explicit
@@ -11302,24 +11371,13 @@ and TcAttributeEx canFail (cenv: cenv) (env: TcEnv) attrTgt attrEx (synAttr: Syn
1130211371
let targetIndicator = synAttr.Target
1130311372
let isAppliedToGetterOrSetter = synAttr.AppliesToGetterAndSetter
1130411373
let mAttr = synAttr.Range
11305-
let typath, tyid = List.frontAndBack tycon
11306-
let tpenv = emptyUnscopedTyparEnv
11374+
let _, tyId = List.frontAndBack tycon
1130711375
let ad = env.eAccessRights
1130811376

1130911377
// if we're checking an attribute that was applied directly to a getter or a setter, then
1131011378
// what we're really checking against is a method, not a property
1131111379
let attrTgt = if isAppliedToGetterOrSetter then ((attrTgt ^^^ AttributeTargets.Property) ||| AttributeTargets.Method) else attrTgt
11312-
let ty, tpenv =
11313-
let try1 n =
11314-
let tyid = mkSynId tyid.idRange n
11315-
let tycon = (typath @ [tyid])
11316-
11317-
match ResolveTypeLongIdent cenv.tcSink cenv.nameResolver ItemOccurrence.UseInAttribute OpenQualified env.eNameResEnv ad tycon TypeNameResolutionStaticArgsInfo.DefiniteEmpty PermitDirectReferenceToGeneratedType.No with
11318-
| Exception err -> raze err
11319-
| Result(tinstEnclosing, tcref, inst) -> success(TcTypeApp cenv NoNewTypars CheckCxs ItemOccurrence.UseInAttribute env tpenv mAttr tcref tinstEnclosing [] inst)
11320-
11321-
ForceRaise ((try1 (tyid.idText + "Attribute")) |> otherwise (fun () -> (try1 tyid.idText)))
11322-
11380+
let ty, tpenv = ResolveAttributeType cenv env mAttr tycon
1132311381
if not (IsTypeAccessible g cenv.amap mAttr ad ty) then errorR(Error(FSComp.SR.tcTypeIsInaccessible(), mAttr))
1132411382

1132511383
let tcref = tcrefOfAppTy g ty
@@ -11330,53 +11388,7 @@ and TcAttributeEx canFail (cenv: cenv) (env: TcEnv) attrTgt attrEx (synAttr: Syn
1133011388
| Some d, Some defines when not (List.contains d defines) ->
1133111389
[], false
1133211390
| _ ->
11333-
// REVIEW: take notice of inherited?
11334-
let validOn, _inherited =
11335-
let validOnDefault = 0x7fff
11336-
let inheritedDefault = true
11337-
if tcref.IsILTycon then
11338-
let tdef = tcref.ILTyconRawMetadata
11339-
let tref = g.attrib_AttributeUsageAttribute.TypeRef
11340-
11341-
match TryDecodeILAttribute tref tdef.CustomAttrs with
11342-
| Some ([ILAttribElem.Int32 validOn ], named) ->
11343-
let inherited =
11344-
match List.tryPick (function "Inherited", _, _, ILAttribElem.Bool res -> Some res | _ -> None) named with
11345-
| None -> inheritedDefault
11346-
| Some x -> x
11347-
(validOn, inherited)
11348-
| Some ([ILAttribElem.Int32 validOn; ILAttribElem.Bool _allowMultiple; ILAttribElem.Bool inherited ], _) ->
11349-
(validOn, inherited)
11350-
| _ ->
11351-
(validOnDefault, inheritedDefault)
11352-
else
11353-
match (TryFindFSharpAttribute g g.attrib_AttributeUsageAttribute tcref.Attribs) with
11354-
| Some(Attrib(_, _, [ AttribInt32Arg validOn ], _, _, _, _)) ->
11355-
(validOn, inheritedDefault)
11356-
| Some(Attrib(_, _, [ AttribInt32Arg validOn
11357-
AttribBoolArg(_allowMultiple)
11358-
AttribBoolArg inherited], _, _, _, _)) ->
11359-
(validOn, inherited)
11360-
| Some _ ->
11361-
warning(Error(FSComp.SR.tcUnexpectedConditionInImportedAssembly(), mAttr))
11362-
(validOnDefault, inheritedDefault)
11363-
| _ ->
11364-
(validOnDefault, inheritedDefault)
11365-
let attributeTargets = enum validOn &&& attrTgt
11366-
let directedTargets =
11367-
match targetIndicator with
11368-
| LongFormAttrTarget attrTarget -> attrTarget
11369-
| UnrecognizedLongAttrTarget attrTarget ->
11370-
errorR(Error(FSComp.SR.tcUnrecognizedAttributeTarget(), attrTarget.idRange))
11371-
attributeTargets
11372-
| ShortFormAttributeTarget -> attributeTargets &&& ~~~ attrEx
11373-
11374-
let constrainedTargets = attributeTargets &&& directedTargets
11375-
if constrainedTargets = enum 0 then
11376-
if (directedTargets = AttributeTargets.Assembly || directedTargets = AttributeTargets.Module) then
11377-
error(Error(FSComp.SR.tcAttributeIsNotValidForLanguageElementUseDo(), mAttr))
11378-
else
11379-
warning(Error(FSComp.SR.tcAttributeIsNotValidForLanguageElement(), mAttr))
11391+
let constrainedTargets = CheckAttributeUsage g mAttr tcref attrTgt targetIndicator attrEx
1138011392

1138111393
match ResolveObjectConstructor cenv.nameResolver env.DisplayEnv mAttr ad ty with
1138211394
| Exception _ when canFail = TcCanFail.IgnoreAllErrors || canFail = TcCanFail.IgnoreMemberResoutionError -> [ ], true
@@ -11391,7 +11403,7 @@ and TcAttributeEx canFail (cenv: cenv) (env: TcEnv) attrTgt attrEx (synAttr: Syn
1139111403
match item with
1139211404
| Item.CtorGroup(methodName, minfos) ->
1139311405
let meths = minfos |> List.map (fun minfo -> minfo, None)
11394-
let afterResolution = ForNewConstructors cenv.tcSink env tyid.idRange methodName minfos
11406+
let afterResolution = ForNewConstructors cenv.tcSink env tyId.idRange methodName minfos
1139511407
let (expr, attributeAssignedNamedItems, _), _ =
1139611408
TcMethodApplication true cenv env tpenv None [] mAttr mAttr methodName None ad PossiblyMutates false meths afterResolution NormalValUse [arg] (MustEqual ty) None []
1139711409

src/Compiler/Checking/Expressions/CheckExpressions.fsi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -955,6 +955,8 @@ module AttributeTargets =
955955
val FieldDeclRestricted: AttributeTargets
956956

957957
/// The allowed attribute targets for an F# union case declaration
958+
/// - AttributeTargets.Method: union case with fields
959+
/// - AttributeTargets.Property: union case with no fields
958960
val UnionCaseDecl: AttributeTargets
959961

960962
/// The allowed attribute targets for an F# type declaration

src/Compiler/Driver/CompilerDiagnostics.fs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,7 @@ type PhasedDiagnostic with
402402
| 3579 -> false // alwaysUseTypedStringInterpolation - off by default
403403
| 3582 -> false // infoIfFunctionShadowsUnionCase - off by default
404404
| 3570 -> false // tcAmbiguousDiscardDotLambda - off by default
405+
| 3878 -> false // tcAttributeIsNotValidForUnionCaseWithFields - off by default
405406
| _ ->
406407
match x.Exception with
407408
| DiagnosticEnabledWithLanguageFeature(_, _, _, enabled) -> enabled

src/Compiler/FSComp.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1806,4 +1806,5 @@ featureAllowLetOrUseBangTypeAnnotationWithoutParens,"Allow let! and use! type an
18061806
3874,lexWarnDirectiveMustBeFirst,"#nowarn/#warnon directives must appear as the first non-whitespace characters on a line"
18071807
3875,lexWarnDirectiveMustHaveArgs,"Warn directives must have warning number(s) as argument(s)"
18081808
3876,lexWarnDirectivesMustMatch,"There is another %s for this warning already in line %d."
1809-
3877,lexLineDirectiveMappingIsNotUnique,"The file '%s' was also pointed to in a line directive in '%s'. Proper warn directive application may not be possible."
1809+
3877,lexLineDirectiveMappingIsNotUnique,"The file '%s' was also pointed to in a line directive in '%s'. Proper warn directive application may not be possible."
1810+
3878,tcAttributeIsNotValidForUnionCaseWithFields,"This attribute is not valid for use on union cases with fields."

src/Compiler/xlf/FSComp.txt.cs.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compiler/xlf/FSComp.txt.de.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compiler/xlf/FSComp.txt.es.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Compiler/xlf/FSComp.txt.fr.xlf

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)