From 1bebbcafd94e60a347133344da78e683e9ba1f05 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/2] 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 f425f49dafc..19c2b9c93ef 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 f5d1048408e..40ee7fbb86e 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -39,6 +39,7 @@ + From b90f44e4726924d4cf6bd2cdbe7253cbe7da23c6 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/2] 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 d5c2087765e..055577a1859 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -1,5 +1,6 @@ ### Fixed +* 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))