Skip to content

opt-in warning attribute not valid for union case with fields #18532

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
4e85c3a
raise an opt-in warning attribute not valid for union case with fields
edgarfgp May 5, 2025
e254386
Merge branch 'main' into attr-targets-opt-in-warning
edgarfgp May 5, 2025
8201668
Merge branch 'main' into attr-targets-opt-in-warning
edgarfgp May 14, 2025
b0922ae
Merge branch 'main' into attr-targets-opt-in-warning
edgarfgp May 15, 2025
85aa7f3
release notes
edgarfgp May 16, 2025
c6a401f
Merge branch 'main' into attr-targets-opt-in-warning
edgarfgp May 16, 2025
0288f0a
Merge branch 'attr-targets-opt-in-warning' of github.com:edgarfgp/fsh…
edgarfgp May 16, 2025
7fd3f51
Update FSComp
edgarfgp May 16, 2025
995b7b6
3878 -> false
edgarfgp May 16, 2025
d136e47
Update test
edgarfgp May 16, 2025
5507d3a
Merge branch 'main' into attr-targets-opt-in-warning
edgarfgp May 16, 2025
fdc2ea9
add opt-in warning tests
edgarfgp May 17, 2025
40b8196
extract ResolveAttributeType and CheckAttributeUsage
edgarfgp May 17, 2025
d18e934
Merge branch 'main' into attr-targets-opt-in-warning
edgarfgp May 19, 2025
57b7bf1
Merge branch 'main' into attr-targets-opt-in-warning
edgarfgp May 20, 2025
7060ff8
use bit-logic to check if the bit for Method is set
edgarfgp May 21, 2025
47c5b15
Merge branch 'main' into attr-targets-opt-in-warning
edgarfgp May 22, 2025
a33c433
PR feedback
edgarfgp May 22, 2025
dcd93ff
Merge branch 'attr-targets-opt-in-warning' of github.com:edgarfgp/fsh…
edgarfgp May 22, 2025
7f6e4d9
Merge branch 'main' into attr-targets-opt-in-warning
edgarfgp May 22, 2025
66c9e49
Merge branch 'main' into attr-targets-opt-in-warning
edgarfgp May 22, 2025
1fb5a81
Merge branch 'main' into attr-targets-opt-in-warning
edgarfgp May 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/release-notes/.FSharp.Compiler.Service/10.0.100.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
### Added
* Add opt-in warning attribute not valid for union case with fields [PR #18532](https://github.com/dotnet/fsharp/pull/18532))

### Fixed

* Fix parsing errors using anonymous records and units of measures ([PR #18543](https://github.com/dotnet/fsharp/pull/18543))
Expand Down
43 changes: 27 additions & 16 deletions src/Compiler/Checking/CheckDeclarations.fs
Original file line number Diff line number Diff line change
Expand Up @@ -576,22 +576,33 @@ module TcRecdUnionAndEnumDeclarations =

let checkXmlDocs = cenv.diagnosticOptions.CheckXmlDocs
let xmlDoc = xmldoc.ToXmlDoc(checkXmlDocs, Some names)
let attrs =
(*
The attributes of a union case decl get attached to the generated "static factory" method.
Enforce union-cases AttributeTargets:
- AttributeTargets.Method
type SomeUnion =
| Case1 of int // Compiles down to a static method
- AttributeTargets.Property
type SomeUnion =
| Case1 // Compiles down to a static property
*)
if g.langVersion.SupportsFeature(LanguageFeature.EnforceAttributeTargets) then
let target = if rfields.IsEmpty then AttributeTargets.Property else AttributeTargets.Method
TcAttributes cenv env target synAttrs
else
TcAttributes cenv env AttributeTargets.UnionCaseDecl synAttrs
let attrs = TcAttributes cenv env AttributeTargets.UnionCaseDecl synAttrs
(*
The attributes of a union case decl get attached to the generated "static factory" method.
Enforce union-cases AttributeTargets:
- AttributeTargets.Method
type SomeUnion =
| Case1 of int // Compiles down to a static method
- AttributeTargets.Property
type SomeUnion =
| Case1 // Compiles down to a static property
*)
if g.langVersion.SupportsFeature(LanguageFeature.EnforceAttributeTargets) then
let attrTargets =
attrs
|> List.collect (fun attr ->
attr.TyconRef.Attribs
|> List.choose (fun attr ->
match attr with
| Attrib(unnamedArgs = [ AttribInt32Arg validOn ]) -> Some validOn
| _ -> None))

attrTargets
|> List.iter (fun target ->
// If the union case has fields, and the target is not AttributeTargets.Method || AttributeTargets.All. Then we raise a separate opt-in warning
let hasNotMethodTarget = (enum target &&& AttributeTargets.Method) = enum 0
if hasNotMethodTarget then
warning(Error(FSComp.SR.tcAttributeIsNotValidForUnionCaseWithFields(), id.idRange)))

Construct.NewUnionCase id rfields recordTy attrs xmlDoc vis

Expand Down
134 changes: 73 additions & 61 deletions src/Compiler/Checking/Expressions/CheckExpressions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -11288,6 +11288,75 @@ and TcNonRecursiveBinding declKind cenv env tpenv ty binding =
let explicitTyparInfo, tpenv = TcNonrecBindingTyparDecls cenv env tpenv binding
TcNormalizedBinding declKind cenv env tpenv ty None NoSafeInitInfo ([], explicitTyparInfo) binding

and ResolveAttributeType (cenv: cenv) (env: TcEnv) (mAttr: range) (tycon: Ident list) =
let tpenv = emptyUnscopedTyparEnv
let ad = env.eAccessRights

let tyPath, tyId = List.frontAndBack tycon

let try1 n =
let tyid = mkSynId tyId.idRange n
let tycon = (tyPath @ [tyid])

match ResolveTypeLongIdent cenv.tcSink cenv.nameResolver ItemOccurrence.UseInAttribute OpenQualified env.eNameResEnv ad tycon TypeNameResolutionStaticArgsInfo.DefiniteEmpty PermitDirectReferenceToGeneratedType.No with
| Exception err -> raze err
| Result(tinstEnclosing, tcref, inst) -> success(TcTypeApp cenv NoNewTypars CheckCxs ItemOccurrence.UseInAttribute env tpenv mAttr tcref tinstEnclosing [] inst)

ForceRaise ((try1 (tyId.idText + "Attribute")) |> otherwise (fun () -> (try1 tyId.idText)))

and CheckAttributeUsage (g: TcGlobals) (mAttr: range) (tcref: TyconRef) (attrTgt: AttributeTargets) (targetIndicator: Ident option) (attrEx: AttributeTargets) =
// REVIEW: take notice of inherited?
let validOn, _inherited =
let validOnDefault = 0x7fff
let inheritedDefault = true
if tcref.IsILTycon then
let tdef = tcref.ILTyconRawMetadata
let tref = g.attrib_AttributeUsageAttribute.TypeRef

match TryDecodeILAttribute tref tdef.CustomAttrs with
| Some ([ILAttribElem.Int32 validOn ], named) ->
let inherited =
match List.tryPick (function "Inherited", _, _, ILAttribElem.Bool res -> Some res | _ -> None) named with
| None -> inheritedDefault
| Some x -> x
(validOn, inherited)
| Some ([ILAttribElem.Int32 validOn; ILAttribElem.Bool _allowMultiple; ILAttribElem.Bool inherited ], _) ->
(validOn, inherited)
| _ ->
(validOnDefault, inheritedDefault)
else
match (TryFindFSharpAttribute g g.attrib_AttributeUsageAttribute tcref.Attribs) with
| Some(Attrib(unnamedArgs = [ AttribInt32Arg validOn ])) ->
validOn, inheritedDefault
| Some(Attrib(unnamedArgs = [ AttribInt32Arg validOn; AttribBoolArg(_allowMultiple); AttribBoolArg inherited])) ->
validOn, inherited
| Some _ ->
warning(Error(FSComp.SR.tcUnexpectedConditionInImportedAssembly(), mAttr))
validOnDefault, inheritedDefault
| _ ->
validOnDefault, inheritedDefault

// Determine valid attribute targets
let attributeTargets = enum validOn &&& attrTgt
let directedTargets =
match targetIndicator with
| LongFormAttrTarget attrTarget -> attrTarget
| UnrecognizedLongAttrTarget attrTarget ->
errorR(Error(FSComp.SR.tcUnrecognizedAttributeTarget(), attrTarget.idRange))
attributeTargets
| ShortFormAttributeTarget -> attributeTargets &&& ~~~ attrEx

let constrainedTargets = attributeTargets &&& directedTargets

// Check if attribute is valid for the target
if constrainedTargets = enum 0 then
if (directedTargets = AttributeTargets.Assembly || directedTargets = AttributeTargets.Module) then
errorR(Error(FSComp.SR.tcAttributeIsNotValidForLanguageElementUseDo(), mAttr))
else
warning(Error(FSComp.SR.tcAttributeIsNotValidForLanguageElement(), mAttr))

constrainedTargets

//-------------------------------------------------------------------------
// TcAttribute*
// *Ex means the function accepts attribute targets that must be explicit
Expand All @@ -11302,24 +11371,13 @@ and TcAttributeEx canFail (cenv: cenv) (env: TcEnv) attrTgt attrEx (synAttr: Syn
let targetIndicator = synAttr.Target
let isAppliedToGetterOrSetter = synAttr.AppliesToGetterAndSetter
let mAttr = synAttr.Range
let typath, tyid = List.frontAndBack tycon
let tpenv = emptyUnscopedTyparEnv
let _, tyId = List.frontAndBack tycon
let ad = env.eAccessRights

// if we're checking an attribute that was applied directly to a getter or a setter, then
// what we're really checking against is a method, not a property
let attrTgt = if isAppliedToGetterOrSetter then ((attrTgt ^^^ AttributeTargets.Property) ||| AttributeTargets.Method) else attrTgt
let ty, tpenv =
let try1 n =
let tyid = mkSynId tyid.idRange n
let tycon = (typath @ [tyid])

match ResolveTypeLongIdent cenv.tcSink cenv.nameResolver ItemOccurrence.UseInAttribute OpenQualified env.eNameResEnv ad tycon TypeNameResolutionStaticArgsInfo.DefiniteEmpty PermitDirectReferenceToGeneratedType.No with
| Exception err -> raze err
| Result(tinstEnclosing, tcref, inst) -> success(TcTypeApp cenv NoNewTypars CheckCxs ItemOccurrence.UseInAttribute env tpenv mAttr tcref tinstEnclosing [] inst)

ForceRaise ((try1 (tyid.idText + "Attribute")) |> otherwise (fun () -> (try1 tyid.idText)))

let ty, tpenv = ResolveAttributeType cenv env mAttr tycon
if not (IsTypeAccessible g cenv.amap mAttr ad ty) then errorR(Error(FSComp.SR.tcTypeIsInaccessible(), mAttr))

let tcref = tcrefOfAppTy g ty
Expand All @@ -11330,53 +11388,7 @@ and TcAttributeEx canFail (cenv: cenv) (env: TcEnv) attrTgt attrEx (synAttr: Syn
| Some d, Some defines when not (List.contains d defines) ->
[], false
| _ ->
// REVIEW: take notice of inherited?
let validOn, _inherited =
let validOnDefault = 0x7fff
let inheritedDefault = true
if tcref.IsILTycon then
let tdef = tcref.ILTyconRawMetadata
let tref = g.attrib_AttributeUsageAttribute.TypeRef

match TryDecodeILAttribute tref tdef.CustomAttrs with
| Some ([ILAttribElem.Int32 validOn ], named) ->
let inherited =
match List.tryPick (function "Inherited", _, _, ILAttribElem.Bool res -> Some res | _ -> None) named with
| None -> inheritedDefault
| Some x -> x
(validOn, inherited)
| Some ([ILAttribElem.Int32 validOn; ILAttribElem.Bool _allowMultiple; ILAttribElem.Bool inherited ], _) ->
(validOn, inherited)
| _ ->
(validOnDefault, inheritedDefault)
else
match (TryFindFSharpAttribute g g.attrib_AttributeUsageAttribute tcref.Attribs) with
| Some(Attrib(_, _, [ AttribInt32Arg validOn ], _, _, _, _)) ->
(validOn, inheritedDefault)
| Some(Attrib(_, _, [ AttribInt32Arg validOn
AttribBoolArg(_allowMultiple)
AttribBoolArg inherited], _, _, _, _)) ->
(validOn, inherited)
| Some _ ->
warning(Error(FSComp.SR.tcUnexpectedConditionInImportedAssembly(), mAttr))
(validOnDefault, inheritedDefault)
| _ ->
(validOnDefault, inheritedDefault)
let attributeTargets = enum validOn &&& attrTgt
let directedTargets =
match targetIndicator with
| LongFormAttrTarget attrTarget -> attrTarget
| UnrecognizedLongAttrTarget attrTarget ->
errorR(Error(FSComp.SR.tcUnrecognizedAttributeTarget(), attrTarget.idRange))
attributeTargets
| ShortFormAttributeTarget -> attributeTargets &&& ~~~ attrEx

let constrainedTargets = attributeTargets &&& directedTargets
if constrainedTargets = enum 0 then
if (directedTargets = AttributeTargets.Assembly || directedTargets = AttributeTargets.Module) then
error(Error(FSComp.SR.tcAttributeIsNotValidForLanguageElementUseDo(), mAttr))
else
warning(Error(FSComp.SR.tcAttributeIsNotValidForLanguageElement(), mAttr))
let constrainedTargets = CheckAttributeUsage g mAttr tcref attrTgt targetIndicator attrEx

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

Expand Down
2 changes: 2 additions & 0 deletions src/Compiler/Checking/Expressions/CheckExpressions.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -955,6 +955,8 @@ module AttributeTargets =
val FieldDeclRestricted: AttributeTargets

/// The allowed attribute targets for an F# union case declaration
/// - AttributeTargets.Method: union case with fields
/// - AttributeTargets.Property: union case with no fields
val UnionCaseDecl: AttributeTargets

/// The allowed attribute targets for an F# type declaration
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/Driver/CompilerDiagnostics.fs
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,7 @@ type PhasedDiagnostic with
| 3579 -> false // alwaysUseTypedStringInterpolation - off by default
| 3582 -> false // infoIfFunctionShadowsUnionCase - off by default
| 3570 -> false // tcAmbiguousDiscardDotLambda - off by default
| 3878 -> false // tcAttributeIsNotValidForUnionCaseWithFields - off by default
| _ ->
match x.Exception with
| DiagnosticEnabledWithLanguageFeature(_, _, _, enabled) -> enabled
Expand Down
3 changes: 2 additions & 1 deletion src/Compiler/FSComp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1806,4 +1806,5 @@ featureAllowLetOrUseBangTypeAnnotationWithoutParens,"Allow let! and use! type an
3874,lexWarnDirectiveMustBeFirst,"#nowarn/#warnon directives must appear as the first non-whitespace characters on a line"
3875,lexWarnDirectiveMustHaveArgs,"Warn directives must have warning number(s) as argument(s)"
3876,lexWarnDirectivesMustMatch,"There is another %s for this warning already in line %d."
3877,lexLineDirectiveMappingIsNotUnique,"The file '%s' was also pointed to in a line directive in '%s'. Proper warn directive application may not be possible."
3877,lexLineDirectiveMappingIsNotUnique,"The file '%s' was also pointed to in a line directive in '%s'. Proper warn directive application may not be possible."
3878,tcAttributeIsNotValidForUnionCaseWithFields,"This attribute is not valid for use on union cases with fields."
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ko.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.pl.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading