Skip to content

Commit d0e2dac

Browse files
Copilotabonie
andauthored
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>
1 parent c09de33 commit d0e2dac

File tree

3 files changed

+172
-2
lines changed

3 files changed

+172
-2
lines changed

src/Compiler/Checking/CheckDeclarations.fs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2735,6 +2735,23 @@ module EstablishTypeDefinitionCores =
27352735
| _ -> () ]
27362736
|> set
27372737

2738+
/// Pre-process open declarations from a list of mutually recursive shapes so that
2739+
/// opened namespaces are available during Phase1A attribute checking.
2740+
/// In recursive scopes, opens are normally processed in Phase1AB (after Phase1A builds
2741+
/// module/type entities), but attributes on modules need the opened namespaces.
2742+
/// Errors are suppressed because some opens may refer to modules being defined in the
2743+
/// current recursive scope, which don't exist yet during Phase1A. Those opens will be
2744+
/// properly processed (with full error reporting) during Phase1AB.
2745+
let private preProcessOpensForPhase1A (cenv: cenv) (env: TcEnv) (shapes: MutRecShapes<_, _, _>) =
2746+
suppressErrorReporting (fun () ->
2747+
use _holder = TemporarilySuspendReportingTypecheckResultsToSink cenv.tcSink
2748+
(env, shapes) ||> List.fold (fun env shape ->
2749+
match shape with
2750+
| MutRecShape.Open(MutRecDataForOpen(target, openm, moduleRange, _)) ->
2751+
let env, _ = TcOpenDecl cenv openm moduleRange env target
2752+
env
2753+
| _ -> env))
2754+
27382755
let TcTyconDefnCore_Phase1A_BuildInitialModule (cenv: cenv) envInitial parent typeNames compInfo decls =
27392756
let g = cenv.g
27402757
let (SynComponentInfo(Attributes attribs, _, _, longPath, xml, _, vis, im)) = compInfo
@@ -2750,7 +2767,11 @@ module EstablishTypeDefinitionCores =
27502767
CheckForDuplicateConcreteType envInitial id.idText im
27512768
CheckNamespaceModuleOrTypeName g id
27522769

2753-
let envForDecls, moduleTyAcc = MakeInnerEnv true envInitial id moduleKind
2770+
let envForDecls, moduleTyAcc = MakeInnerEnv true envInitial id moduleKind
2771+
2772+
// Pre-process opens from children so nested modules can see opened namespaces during attribute checking
2773+
let envForDecls = preProcessOpensForPhase1A cenv envForDecls decls
2774+
27542775
let moduleTy = Construct.NewEmptyModuleOrNamespaceType moduleKind
27552776

27562777
let checkXmlDocs = cenv.diagnosticOptions.CheckXmlDocs
@@ -4039,12 +4060,18 @@ module EstablishTypeDefinitionCores =
40394060

40404061

40414062
let TcMutRecDefns_Phase1 mkLetInfo (cenv: cenv) envInitial parent typeNames inSig tpenv m scopem mutRecNSInfo (mutRecDefns: MutRecShapes<MutRecDefnsPhase1DataForTycon * 'MemberInfo, 'LetInfo, SynComponentInfo>) =
4063+
// Pre-process top-level opens so they are available during attribute checking in Phase1A.
4064+
// In recursive scopes (namespace rec / module rec), opens are normally processed in Phase1AB
4065+
// after module entities are built, but module attributes need access to opened namespaces.
4066+
// See https://github.com/dotnet/fsharp/issues/7931
4067+
let envWithOpens = preProcessOpensForPhase1A cenv envInitial mutRecDefns
4068+
40424069
// Phase1A - build Entity for type definitions, exception definitions and module definitions.
40434070
// Also for abbreviations of any of these. Augmentations are skipped in this phase.
40444071
let withEntities =
40454072
mutRecDefns
40464073
|> MutRecShapes.mapWithParent
4047-
(parent, typeNames, envInitial)
4074+
(parent, typeNames, envWithOpens)
40484075
// Build the initial entity for each module definition
40494076
(fun (innerParent, typeNames, envForDecls) compInfo decls ->
40504077
TcTyconDefnCore_Phase1A_BuildInitialModule cenv envForDecls innerParent typeNames compInfo decls)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
2+
3+
namespace Conformance.BasicGrammarElements
4+
5+
open Xunit
6+
open FSharp.Test.Compiler
7+
8+
module AttributeResolutionInRecursiveScopes =
9+
10+
// https://github.com/dotnet/fsharp/issues/7931
11+
[<Fact>]
12+
let ``Extension attribute on module in namespace rec`` () =
13+
FSharp """
14+
namespace rec Ns
15+
16+
open System.Runtime.CompilerServices
17+
18+
[<Extension>]
19+
module Module =
20+
[<Extension>]
21+
let ext1 (x: int) = x.ToString()
22+
"""
23+
|> asLibrary
24+
|> typecheck
25+
|> shouldSucceed
26+
27+
// https://github.com/dotnet/fsharp/issues/7931
28+
[<Fact>]
29+
let ``Extension attribute on type in namespace rec`` () =
30+
FSharp """
31+
namespace rec Ns
32+
33+
open System.Runtime.CompilerServices
34+
35+
[<Extension>]
36+
type T() =
37+
class end
38+
"""
39+
|> asLibrary
40+
|> typecheck
41+
|> shouldSucceed
42+
43+
// https://github.com/dotnet/fsharp/issues/5795 - Custom attribute used on type and let in rec module
44+
[<Fact>]
45+
let ``Custom attribute used on type and let in rec module`` () =
46+
FSharp """
47+
module rec M
48+
49+
type CustomAttribute() =
50+
inherit System.Attribute()
51+
52+
[<Custom>] type A = | A
53+
[<Custom>] let a = ()
54+
"""
55+
|> typecheck
56+
|> shouldSucceed
57+
58+
// Nested module case: open inside outer module, attribute on inner module
59+
[<Fact>]
60+
let ``Open inside nested module resolves for attribute on inner module in namespace rec`` () =
61+
FSharp """
62+
namespace rec Ns
63+
64+
module Outer =
65+
open System.Runtime.CompilerServices
66+
67+
[<Extension>]
68+
module Inner =
69+
[<Extension>]
70+
let ext1 (x: int) = x.ToString()
71+
"""
72+
|> asLibrary
73+
|> typecheck
74+
|> shouldSucceed
75+
76+
// Non-recursive baseline: should always work
77+
[<Fact>]
78+
let ``Extension attribute works without rec - baseline`` () =
79+
FSharp """
80+
namespace Ns
81+
82+
open System.Runtime.CompilerServices
83+
84+
[<Extension>]
85+
module Module =
86+
[<Extension>]
87+
let ext1 (x: int) = x.ToString()
88+
"""
89+
|> asLibrary
90+
|> typecheck
91+
|> shouldSucceed
92+
93+
// Multiple opens in namespace rec
94+
[<Fact>]
95+
let ``Multiple opens resolve for attributes in namespace rec`` () =
96+
FSharp """
97+
namespace rec Ns
98+
99+
open System
100+
open System.Runtime.CompilerServices
101+
102+
[<Extension>]
103+
module Module =
104+
[<Extension>]
105+
[<Obsolete("test")>]
106+
let ext1 (x: int) = x.ToString()
107+
"""
108+
|> asLibrary
109+
|> typecheck
110+
|> shouldSucceed
111+
112+
// Open in module rec resolves for module attributes
113+
[<Fact>]
114+
let ``Open in module rec resolves for nested module attribute`` () =
115+
FSharp """
116+
module rec M
117+
118+
open System.Runtime.CompilerServices
119+
120+
[<Extension>]
121+
module Inner =
122+
[<Extension>]
123+
let ext1 (x: int) = x.ToString()
124+
"""
125+
|> typecheck
126+
|> shouldSucceed
127+
128+
// Open with Obsolete attribute in namespace rec
129+
[<Fact>]
130+
let ``Obsolete attribute resolves via open in namespace rec`` () =
131+
FSharp """
132+
namespace rec Ns
133+
134+
open System
135+
136+
[<Obsolete("deprecated")>]
137+
module DeprecatedModule =
138+
let x = 42
139+
"""
140+
|> asLibrary
141+
|> typecheck
142+
|> shouldSucceed

tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
<Compile Include="Conformance\BasicGrammarElements\AccessibilityAnnotations\PermittedLocations\PermittedLocations.fs" />
4040
<Compile Include="Conformance\BasicGrammarElements\CustomAttributes\AttributeInheritance\AttributeInheritance.fs" />
4141
<Compile Include="Conformance\BasicGrammarElements\CustomAttributes\AttributeUsage\AttributeUsage.fs" />
42+
<Compile Include="Conformance\BasicGrammarElements\CustomAttributes\AttributeUsage\AttributeResolutionInRecursiveScopes.fs" />
4243
<Compile Include="Conformance\BasicGrammarElements\CustomAttributes\Basic\Basic.fs" />
4344
<Compile Include="Conformance\BasicGrammarElements\CustomAttributes\ImportedAttributes\ImportedAttributes.fs" />
4445
<Compile Include="Conformance\BasicGrammarElements\CustomAttributes\ArgumentsOfAllTypes\ArgumentsOfAllTypes.fs" />

0 commit comments

Comments
 (0)