Skip to content

Attempting to fix #17904 and #19020#19738

Draft
edgarfgp wants to merge 8 commits into
dotnet:mainfrom
edgarfgp:fix-17904-allowmultiple-attribute-targets
Draft

Attempting to fix #17904 and #19020#19738
edgarfgp wants to merge 8 commits into
dotnet:mainfrom
edgarfgp:fix-17904-allowmultiple-attribute-targets

Conversation

@edgarfgp
Copy link
Copy Markdown
Contributor

@edgarfgp edgarfgp commented May 15, 2026

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:

Before

open System

[<AttributeUsage(AttributeTargets.ReturnValue)>]
type DescAttribute() = inherit Attribute()

type T() =
    [<return: Desc>]
    member _.Foo a = a + 1

// Reflection finds 0 attributes — silently dropped (#19020)
typeof<T>.GetMethod("Foo").ReturnParameter.GetCustomAttributes(typeof<DescAttribute>, false)
[<AttributeUsage(AttributeTargets.All)>]
type AttrAttribute() = inherit Attribute()

type T() =
    [<Attr>]
    [<return: Attr>]                      // error FS0429: AllowMultiple=false (#17904)
    member _.Foo() = ()

After

// Reflection finds 1 attribute on the return parameter
typeof<T>.GetMethod("Foo").ReturnParameter.GetCustomAttributes(typeof<DescAttribute>, false).Length = 1

// [<Attr>] (method) and [<return: Attr>] (return value) target different
// metadata slots — no longer a duplicate.
type T() =
    [<Attr>]
    [<return: Attr>]
    member _.Foo() = ()                   // compiles cleanly

Genuine duplicates still error:

type T() =
    [<return: Attr>]
    [<return: Attr>]                      // error FS0429: still flagged
    member _.Foo() = ()

type T() =
    [<Attr>]
    [<method: Attr>]                      // error FS0429: still flagged (same target)
    member _.Foo() = ()

Solution

Move the rotation to the parser stage. In mkSynBinding (SyntaxTreeOps.fs), any SynAttribute whose Target = Some "return" is moved out of the binding's prefix attribute list into SynValData.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 syntacticsynAttr.Target = Some "return" — not the typed constrainedTargets bitmap. Attributes without a return: prefix are never touched, so [<CompilationRepresentation(...)>] on Option.Value is unaffected and FSharp.Core builds cleanly.

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
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 15, 2026

❗ Release notes required


✅ Found changes and release notes in following paths:

Change path Release notes path Description
src/Compiler docs/release-notes/.FSharp.Compiler.Service/11.0.100.md

@github-actions
Copy link
Copy Markdown
Contributor

🔍 Tooling Safety Check — Affects-Compiler-Output
Affects-Compiler-Output: src/Compiler/SyntaxTree/SyntaxTreeOps.fs — changes attribute placement on compiled return values

(scanned-sha)d0bf14fb399fd3bc266dfe8f2aa7e88c6d1775ff(/scanned-sha)

Generated by PR Tooling Safety Check · ● 2.1M ·

@github-actions github-actions Bot added the ⚠️ Affects-Compiler-Output Tooling check: PR touches IL emission or codegen label May 15, 2026
edgarfgp added 5 commits May 15, 2026 08:15
…-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.
@edgarfgp
Copy link
Copy Markdown
Contributor Author

@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.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 into SynValInfo.retInfo during mkSynBinding.
  • 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.

Comment on lines +761 to +773
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 })

Comment on lines +11228 to +11239
// 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚠️ Affects-Compiler-Output Tooling check: PR touches IL emission or codegen

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

F# compiler doesn't respect different attribute targets when checking for Multiple appliance

2 participants