Attempting to fix #17904 and #19020#19738
Conversation
In mkSynBinding, move any attribute written with the explicit 'return:' target from the binding's prefix attributes into SynValData.SynValInfo.retInfo. This makes the syntactic placement (which the parser puts on the binding) match the semantic intent (the attribute targets the method's return value). Fixes: - dotnet#19020: [<return: X>] silently dropped on class members - dotnet#17904: false-positive AllowMultiple=false when [<X>] and [<return: X>] appear on the same member
❗ Release notes required
|
|
🔍 Tooling Safety Check — Affects-Compiler-Output (scanned-sha)d0bf14fb399fd3bc266dfe8f2aa7e88c6d1775ff(/scanned-sha)
|
…-pattern detection After SynInfo.RotateReturnAttributes moves [<return: X>] from the binding's prefix attributes into SynValData.SynValInfo.retInfo, two downstream consumers also need updating: - TcNormalizedBinding's retAttribs computation now type-checks attrs already in valSynData's return SynArgInfo, so isStructRetTy/argAndRetAttribs work for [<return: Struct>] on partial active patterns. - ActivePatternElemsOfValRef now classifies the flag bag from ValReprInfo's result ArgReprInfo rather than scanning vref.Attribs. Fixes recursive struct active patterns (e.g. let rec (|HasOne|_|)).
With the parser-level rotation in SynInfo.RotateReturnAttributes, the binding's prefix attrs never contain [<return: X>] by the time TcNormalizedBinding runs. The partition-and-rotate dance and the valSynData patch are no-ops in every reachable case, so remove them and read return attrs directly from SynValData.SynValInfo.retInfo (where the parser put them) plus any attrs on the return type annotation.
|
@T-Gro I think the CI is stuck or it has not even started. |
Drop the module-let case from the dotnet#19020 test (module bindings were never affected). Rename tests with 'Issue NNNNN -' prefix and tighten source samples and failure messages.
There was a problem hiding this comment.
Pull request overview
This PR fixes two related attribute-handling bugs by ensuring [<return: ...>] prefix attributes on bindings are routed to the return-value metadata slot early (at syntax construction time), so downstream phases (type-checking, IL emit, FCS symbols) see them in the correct place.
Changes:
- Rotate
[<return: ...>]prefix attributes from binding attributes intoSynValInfo.retInfoduringmkSynBinding. - Update downstream consumers (notably active pattern return-kind detection and binding attribute type-checking) to read return attributes from return-info rather than
Val.Attribs. - Add component regression tests for issues #17904 and #19020, plus a release-notes entry.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/FSharp.Compiler.ComponentTests/Language/AttributeCheckingTests.fs | Adds regression tests covering return-attribute emission and duplicate detection across targets. |
| src/Compiler/SyntaxTree/SyntaxTreeOps.fs | Implements parser-stage rotation of [<return: ...>] attributes into SynValInfo.retInfo. |
| src/Compiler/Checking/NameResolution.fs | Adjusts active-pattern return-kind logic to look for return attributes in ValReprInfo return info. |
| src/Compiler/Checking/Expressions/CheckExpressions.fs | Removes typechecker-stage rotation and re-plumbs return attribute collection/typechecking. |
| docs/release-notes/.FSharp.Compiler.Service/11.0.100.md | Documents the bug fixes in release notes. |
| let mutable returnTargeted = [] | ||
|
|
||
| let newAttrs = | ||
| attrs | ||
| |> List.choose (fun lst -> | ||
| let ret, kept = lst.Attributes |> List.partition isReturnTargetedAttribute | ||
| returnTargeted <- returnTargeted @ ret | ||
|
|
||
| if List.isEmpty kept then | ||
| None | ||
| else | ||
| Some { lst with Attributes = kept }) | ||
|
|
| // Pick them up from there along with any attributes on the return type annotation. | ||
| let valAttribs = TcAttrs attrTgt false attrs | ||
|
|
||
| let retAttribs = | ||
| let valSynDataRetSynAttrs = | ||
| let (SynValData(_, SynValInfo(_, SynArgInfo(retAttrs, _, _)), _)) = valSynData | ||
| retAttrs |> List.collect (fun a -> a.Attributes) | ||
| let fromValSyn = TcAttrs AttributeTargets.ReturnValue true valSynDataRetSynAttrs | ||
| match rtyOpt with | ||
| | Some(SynBindingReturnInfo(attributes = Attributes retAttrs)) -> | ||
| fromValSyn @ TcAttrs AttributeTargets.ReturnValue true retAttrs | ||
| | None -> fromValSyn |
Verifies that [<CompilationRepresentation(Instance)>] on a union-type member is not rotated to the return value.
Fixes #17904 and #19020.
Problem
Two related bugs share the same root cause —
[<return: X>]prefix attributes on a binding aren't routed to the return-value metadata slot:[<return: X>]on class members is silently dropped from IL. The attribute appears in source, but reflection on the method's return parameter finds nothing.[<X>]and[<return: X>]on the same member are incorrectly flagged as duplicates underAllowMultiple = false, because both end up inVal.Attribstogether rather than being separated by metadata target.Before
After
Genuine duplicates still error:
Solution
Move the rotation to the parser stage. In
mkSynBinding(SyntaxTreeOps.fs), anySynAttributewhoseTarget = Some "return"is moved out of the binding's prefix attribute list intoSynValData.SynValInfo.retInfo. From that point onward, every downstream consumer (type-checker, IL emit, FCS Symbols API) sees the attribute in the semantically correct slot, regardless of where the user wrote it.The discriminator is syntactic —
synAttr.Target = Some "return"— not the typedconstrainedTargetsbitmap. Attributes without areturn:prefix are never touched, so[<CompilationRepresentation(...)>]onOption.Valueis unaffected and FSharp.Core builds cleanly.