From 53b6ae8af20c78ab36babe3ed13330d4ba4c26dc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 31 Mar 2026 15:13:33 +0000 Subject: [PATCH 1/4] Fix #7931: Resolve opened namespaces for attributes in recursive scopes Pre-process open declarations before Phase1A attribute checking in recursive scopes (namespace rec / module rec). Previously, opens were only processed in Phase1AB after module entities were built, but module attributes needed access to opened namespaces during Phase1A. The fix adds preProcessOpensForPhase1A which silently resolves opens before Phase1A, making opened namespaces available for attribute resolution on modules and types in recursive scopes. Agent-Logs-Url: https://github.com/dotnet/fsharp/sessions/20a6e4bb-a5f1-45cc-b80d-ee1110cd8812 Co-authored-by: abonie <20281641+abonie@users.noreply.github.com> --- src/Compiler/Checking/CheckDeclarations.fs | 31 +++- .../AttributeResolutionInRecursiveScopes.fs | 142 ++++++++++++++++++ .../FSharp.Compiler.ComponentTests.fsproj | 1 + 3 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/AttributeUsage/AttributeResolutionInRecursiveScopes.fs diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs index 3d80f056de0..63c5a8e6340 100644 --- a/src/Compiler/Checking/CheckDeclarations.fs +++ b/src/Compiler/Checking/CheckDeclarations.fs @@ -2735,6 +2735,23 @@ module EstablishTypeDefinitionCores = | _ -> () ] |> set + /// Pre-process open declarations from a list of mutually recursive shapes so that + /// opened namespaces are available during Phase1A attribute checking. + /// In recursive scopes, opens are normally processed in Phase1AB (after Phase1A builds + /// module/type entities), but attributes on modules need the opened namespaces. + /// Errors are suppressed because some opens may refer to modules being defined in the + /// current recursive scope, which don't exist yet during Phase1A. Those opens will be + /// properly processed (with full error reporting) during Phase1AB. + let private preProcessOpensForPhase1A (cenv: cenv) (env: TcEnv) (shapes: MutRecShapes<_, _, _>) = + suppressErrorReporting (fun () -> + use _holder = TemporarilySuspendReportingTypecheckResultsToSink cenv.tcSink + (env, shapes) ||> List.fold (fun env shape -> + match shape with + | MutRecShape.Open(MutRecDataForOpen(target, openm, moduleRange, _)) -> + let env, _ = TcOpenDecl cenv openm moduleRange env target + env + | _ -> env)) + let TcTyconDefnCore_Phase1A_BuildInitialModule (cenv: cenv) envInitial parent typeNames compInfo decls = let g = cenv.g let (SynComponentInfo(Attributes attribs, _, _, longPath, xml, _, vis, im)) = compInfo @@ -2750,7 +2767,11 @@ module EstablishTypeDefinitionCores = CheckForDuplicateConcreteType envInitial id.idText im CheckNamespaceModuleOrTypeName g id - let envForDecls, moduleTyAcc = MakeInnerEnv true envInitial id moduleKind + let envForDecls, moduleTyAcc = MakeInnerEnv true envInitial id moduleKind + + // Pre-process opens from children so nested modules can see opened namespaces during attribute checking + let envForDecls = preProcessOpensForPhase1A cenv envForDecls decls + let moduleTy = Construct.NewEmptyModuleOrNamespaceType moduleKind let checkXmlDocs = cenv.diagnosticOptions.CheckXmlDocs @@ -4039,12 +4060,18 @@ module EstablishTypeDefinitionCores = let TcMutRecDefns_Phase1 mkLetInfo (cenv: cenv) envInitial parent typeNames inSig tpenv m scopem mutRecNSInfo (mutRecDefns: MutRecShapes) = + // Pre-process top-level opens so they are available during attribute checking in Phase1A. + // In recursive scopes (namespace rec / module rec), opens are normally processed in Phase1AB + // after module entities are built, but module attributes need access to opened namespaces. + // See https://github.com/dotnet/fsharp/issues/7931 + let envWithOpens = preProcessOpensForPhase1A cenv envInitial mutRecDefns + // Phase1A - build Entity for type definitions, exception definitions and module definitions. // Also for abbreviations of any of these. Augmentations are skipped in this phase. let withEntities = mutRecDefns |> MutRecShapes.mapWithParent - (parent, typeNames, envInitial) + (parent, typeNames, envWithOpens) // Build the initial entity for each module definition (fun (innerParent, typeNames, envForDecls) compInfo decls -> TcTyconDefnCore_Phase1A_BuildInitialModule cenv envForDecls innerParent typeNames compInfo decls) diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/AttributeUsage/AttributeResolutionInRecursiveScopes.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/AttributeUsage/AttributeResolutionInRecursiveScopes.fs new file mode 100644 index 00000000000..56737c3ee74 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/AttributeUsage/AttributeResolutionInRecursiveScopes.fs @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Conformance.BasicGrammarElements + +open Xunit +open FSharp.Test.Compiler + +module AttributeResolutionInRecursiveScopes = + + // https://github.com/dotnet/fsharp/issues/7931 + [] + let ``Extension attribute on module in namespace rec`` () = + FSharp """ +namespace rec Ns + +open System.Runtime.CompilerServices + +[] +module Module = + [] + let ext1 (x: int) = x.ToString() + """ + |> asLibrary + |> typecheck + |> shouldSucceed + + // https://github.com/dotnet/fsharp/issues/7931 + [] + let ``Extension attribute on type in namespace rec`` () = + FSharp """ +namespace rec Ns + +open System.Runtime.CompilerServices + +[] +type T() = + class end + """ + |> asLibrary + |> typecheck + |> shouldSucceed + + // https://github.com/dotnet/fsharp/issues/5795 - Custom attribute used on type and let in rec module + [] + let ``Custom attribute used on type and let in rec module`` () = + FSharp """ +module rec M + +type CustomAttribute() = + inherit System.Attribute() + +[] type A = | A +[] let a = () + """ + |> typecheck + |> shouldSucceed + + // Nested module case: open inside outer module, attribute on inner module + [] + let ``Open inside nested module resolves for attribute on inner module in namespace rec`` () = + FSharp """ +namespace rec Ns + +module Outer = + open System.Runtime.CompilerServices + + [] + module Inner = + [] + let ext1 (x: int) = x.ToString() + """ + |> asLibrary + |> typecheck + |> shouldSucceed + + // Non-recursive baseline: should always work + [] + let ``Extension attribute works without rec - baseline`` () = + FSharp """ +namespace Ns + +open System.Runtime.CompilerServices + +[] +module Module = + [] + let ext1 (x: int) = x.ToString() + """ + |> asLibrary + |> typecheck + |> shouldSucceed + + // Multiple opens in namespace rec + [] + let ``Multiple opens resolve for attributes in namespace rec`` () = + FSharp """ +namespace rec Ns + +open System +open System.Runtime.CompilerServices + +[] +module Module = + [] + [] + let ext1 (x: int) = x.ToString() + """ + |> asLibrary + |> typecheck + |> shouldSucceed + + // Open in module rec resolves for module attributes + [] + let ``Open in module rec resolves for nested module attribute`` () = + FSharp """ +module rec M + +open System.Runtime.CompilerServices + +[] +module Inner = + [] + let ext1 (x: int) = x.ToString() + """ + |> typecheck + |> shouldSucceed + + // Open with Obsolete attribute in namespace rec + [] + let ``Obsolete attribute resolves via open in namespace rec`` () = + FSharp """ +namespace rec Ns + +open System + +[] +module DeprecatedModule = + let x = 42 + """ + |> asLibrary + |> typecheck + |> shouldSucceed diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index a345c31d728..4076cd64f86 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -39,6 +39,7 @@ + From c8bd4f1aad45b608f8a048ccb25e00f6b3aad785 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 31 Mar 2026 15:20:25 +0000 Subject: [PATCH 2/4] Add release notes for #7931 fix Agent-Logs-Url: https://github.com/dotnet/fsharp/sessions/20a6e4bb-a5f1-45cc-b80d-ee1110cd8812 Co-authored-by: abonie <20281641+abonie@users.noreply.github.com> --- docs/release-notes/.FSharp.Compiler.Service/11.0.100.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index 73971b6a385..6a5bef11928 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -1,6 +1,7 @@ ### Fixed * Fix NRE when calling virtual Object methods on value types through inline SRTP functions. ([Issue #8098](https://github.com/dotnet/fsharp/issues/8098), [PR #19511](https://github.com/dotnet/fsharp/pull/19511)) +* Fix attributes not resolved from opened namespaces in `namespace rec` / `module rec` scopes. ([Issue #7931](https://github.com/dotnet/fsharp/issues/7931), [PR #19502](https://github.com/dotnet/fsharp/pull/19502)) * Fix DU case names matching IWSAM member names no longer cause duplicate property entries. (Issue [#14321](https://github.com/dotnet/fsharp/issues/14321), [PR #19341](https://github.com/dotnet/fsharp/pull/19341)) * Fix DefaultAugmentation(false) duplicate entry in method table. (Issue [#16565](https://github.com/dotnet/fsharp/issues/16565), [PR #19341](https://github.com/dotnet/fsharp/pull/19341)) * Fix abstract event accessors now have SpecialName flag. (Issue [#5834](https://github.com/dotnet/fsharp/issues/5834), [PR #19341](https://github.com/dotnet/fsharp/pull/19341)) From 4138cf3536ec5840098dfe01a1bc08bfc2944856 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Thu, 16 Apr 2026 10:29:53 +0200 Subject: [PATCH 3/4] Restrict open preprocessing to ModuleOrNamespace targets and add negative test - Narrow preProcessOpensForPhase1A to only handle SynOpenDeclTarget.ModuleOrNamespace, avoiding unnecessary processing of 'open type' declarations in incomplete recursive env. - Add negative test verifying undefined attributes still produce FS0039 in namespace rec. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/Compiler/Checking/CheckDeclarations.fs | 2 +- .../AttributeResolutionInRecursiveScopes.fs | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs index 63c5a8e6340..92344fca2c1 100644 --- a/src/Compiler/Checking/CheckDeclarations.fs +++ b/src/Compiler/Checking/CheckDeclarations.fs @@ -2747,7 +2747,7 @@ module EstablishTypeDefinitionCores = use _holder = TemporarilySuspendReportingTypecheckResultsToSink cenv.tcSink (env, shapes) ||> List.fold (fun env shape -> match shape with - | MutRecShape.Open(MutRecDataForOpen(target, openm, moduleRange, _)) -> + | MutRecShape.Open(MutRecDataForOpen(SynOpenDeclTarget.ModuleOrNamespace _ as target, openm, moduleRange, _)) -> let env, _ = TcOpenDecl cenv openm moduleRange env target env | _ -> env)) diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/AttributeUsage/AttributeResolutionInRecursiveScopes.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/AttributeUsage/AttributeResolutionInRecursiveScopes.fs index 56737c3ee74..8eed7eb741f 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/AttributeUsage/AttributeResolutionInRecursiveScopes.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/AttributeUsage/AttributeResolutionInRecursiveScopes.fs @@ -140,3 +140,18 @@ module DeprecatedModule = |> asLibrary |> typecheck |> shouldSucceed + + // Negative test: undefined attribute still errors in namespace rec + [] + let ``Undefined attribute still errors in namespace rec`` () = + FSharp """ +namespace rec Ns + +[] +module M = + let x = 1 + """ + |> asLibrary + |> typecheck + |> shouldFail + |> withErrorCode 39 From 2b1438910bc780ba1f554d1b1243ff89789cbbf0 Mon Sep 17 00:00:00 2001 From: Tomas Grosup Date: Thu, 16 Apr 2026 11:36:45 +0200 Subject: [PATCH 4/4] Add edge-case tests for invalid open and forward-reference in namespace rec Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../AttributeResolutionInRecursiveScopes.fs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/AttributeUsage/AttributeResolutionInRecursiveScopes.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/AttributeUsage/AttributeResolutionInRecursiveScopes.fs index 8eed7eb741f..81e07c8c9d7 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/AttributeUsage/AttributeResolutionInRecursiveScopes.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/AttributeUsage/AttributeResolutionInRecursiveScopes.fs @@ -155,3 +155,39 @@ module M = |> typecheck |> shouldFail |> withErrorCode 39 + + // Negative test: invalid open target still errors despite error suppression in pre-pass + [] + let ``Invalid open target still errors in namespace rec`` () = + FSharp """ +namespace rec Ns + +open DoesNotExist.Namespace + +[] +module M = + let x = 1 + """ + |> asLibrary + |> typecheck + |> shouldFail + |> withErrorCode 39 + + // Forward-reference open to sibling module in the same recursive scope + [] + let ``Forward reference open to sibling module in namespace rec`` () = + FSharp """ +namespace rec Ns + +open Ns.Later + +module Earlier = + let x = Later.y + +module Later = + let y = 42 + """ + |> asLibrary + |> withOptions [ "--nowarn:22"; "--nowarn:40" ] + |> typecheck + |> shouldSucceed