Skip to content

Fix attributes silently dropped on unparenthesized tuple return types#19714

Open
edgarfgp wants to merge 7 commits into
dotnet:mainfrom
edgarfgp:fix-462-v2
Open

Fix attributes silently dropped on unparenthesized tuple return types#19714
edgarfgp wants to merge 7 commits into
dotnet:mainfrom
edgarfgp:fix-462-v2

Conversation

@edgarfgp
Copy link
Copy Markdown
Contributor

Fixes #462

Problem

When a method return type is an unparenthesized tuple, any attributes placed before it are silently dropped and never emitted to IL:

// Attributes are DROPPED — bug
static member Format name :
    [<ReturnDescription("...")>]
    string * string = ...

// Attributes are KEPT — works
static member Format name :
    [<ReturnDescription("...")>]
    (string * string) = ...

Root cause

In pars.fsy, the topType → topTupleType grammar rule handles the return type annotation. When the return type is a bare multi-element tuple, rmdata (the list of SynArgInfo per tuple element) has more than one entry, so the rule fell into a catch-all branch that replaced everything with SynInfo.unnamedRetVal — an empty SynArgInfo with no attributes:

// Before
ty, (SynValInfo([], (match rmdata with [md] -> md | _ -> SynInfo.unnamedRetVal)))

The attributes were parsed correctly into the first element's SynArgInfo but then discarded here, never reaching SynBindingReturnInfo or IL emission.

Fix

Promote attributes from the first element's SynArgInfo to the overall return SynArgInfo when they are non-empty, since attributes placed before the whole tuple belong to the return position:

// After
let retArgInfo =
    match rmdata with
    | [md] -> md
    | SynArgInfo(attrs, _, _) :: _ when not attrs.IsEmpty -> SynArgInfo(attrs, false, None)
    | _ -> SynInfo.unnamedRetVal
ty, SynValInfo([], retArgInfo)

Tests

  • ReturnType04.fs — runtime reflection test asserting attributes appear on the return parameter for both parenthesized and bare tuple forms
  • Attribute/ReturnTypeAttributeOnBareTuple.fs + .bsl — SyntaxTree parser test documenting that SynValInfo now carries the correct SynArgInfo with attributes in the return position for bare tuples

edgarfgp and others added 2 commits May 11, 2026 14:05
…dotnet#462)

In `pars.fsy`, the `topType → topTupleType` rule discarded all `SynArgInfo`
entries via `SynInfo.unnamedRetVal` whenever the return type was a bare
multi-element tuple (e.g. `string * string`). Attributes placed before the
tuple were parsed into the first element's `SynArgInfo` but then silently
dropped, never reaching `SynBindingReturnInfo` or IL emission.

The fix promotes attributes from the first element's `SynArgInfo` to the
overall return `SynArgInfo` when they are non-empty, so that:

    static member Foo() :
        [<ReturnDescription("...")>]
        string * string = ...

now correctly emits the attribute to `.param [0]` in IL, matching the
behaviour of the parenthesized form `(string * string)`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
These were side effects of running TEST_UPDATE_BSL=1 and are unrelated
to the fix for dotnet#462.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 11, 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

Comment thread tests/service/data/SyntaxTree/Attribute/ReturnTypeAttributeOnBareTuple.fs Outdated
Copy link
Copy Markdown
Member

@T-Gro T-Gro left a comment

Choose a reason for hiding this comment

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

Clean, well-scoped fix for a 10-year-old bug. LGTM.

What the fix does: In the topType -> topTupleType grammar rule, when the return type is a bare multi-element tuple, the old code discarded all SynArgInfo data (including attributes) by falling through to SynInfo.unnamedRetVal. The new code promotes attributes from the first tuple element's SynArgInfo to the return position, which is correct because attributes placed before the whole tuple syntactically belong to the return, not to the first element specifically.

Correctness verified:

  • Single-element case ([md]): unchanged, existing behavior preserved.
  • Multi-element with first-element attrs: promoted to return SynArgInfo(attrs, false, None) -- consistent with unnamedRetVal structure, just carrying the attributes.
  • Multi-element with no first-element attrs: falls through to unnamedRetVal -- correct.
  • Named parameters in tuple return types: name/optionality correctly stays per-element in the SynType.SignatureParameter, only attrs promoted to return position.
  • Downstream flow verified: SynArgInfo.rAttribs -> SynReturnInfo -> SynBindingReturnInfo -> IL emission (SyntaxTreeOps.fs:749-750).

Test coverage:

  • Runtime reflection test (ReturnType04.fs) with AllowMultiple attributes on both parenthesized and bare tuple -- directly verifies IL emission.
  • Two syntax tree baselines documenting the AST structure for both cases.
  • Parenthesized tuple baseline serves as the control case.

Minor suggestion (non-blocking): Could consider adding a test for let bindings and .fsi signature files, since topType is shared across all contexts. But the grammar is shared so the fix applies everywhere.

@github-project-automation github-project-automation Bot moved this from New to In Progress in F# Compiler and Tooling May 13, 2026
@T-Gro T-Gro added the AI-reviewed PR reviewed by AI review council label May 13, 2026
@T-Gro T-Gro self-requested a review May 13, 2026 11:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI-reviewed PR reviewed by AI review council

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

No diagnostic when custom attributes ignored on unparenthesized tuple type

3 participants