Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
Original file line number Diff line number Diff line change
@@ -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))
Expand Down
31 changes: 29 additions & 2 deletions src/Compiler/Checking/CheckDeclarations.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -4039,12 +4060,18 @@ module EstablishTypeDefinitionCores =


let TcMutRecDefns_Phase1 mkLetInfo (cenv: cenv) envInitial parent typeNames inSig tpenv m scopem mutRecNSInfo (mutRecDefns: MutRecShapes<MutRecDefnsPhase1DataForTycon * 'MemberInfo, 'LetInfo, SynComponentInfo>) =
// 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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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
[<Fact>]
let ``Extension attribute on module in namespace rec`` () =
FSharp """
namespace rec Ns

open System.Runtime.CompilerServices

[<Extension>]
module Module =
[<Extension>]
let ext1 (x: int) = x.ToString()
"""
|> asLibrary
|> typecheck
|> shouldSucceed

// https://github.com/dotnet/fsharp/issues/7931
[<Fact>]
let ``Extension attribute on type in namespace rec`` () =
FSharp """
namespace rec Ns

open System.Runtime.CompilerServices

[<Extension>]
type T() =
class end
"""
|> asLibrary
|> typecheck
|> shouldSucceed

// https://github.com/dotnet/fsharp/issues/5795 - Custom attribute used on type and let in rec module
[<Fact>]
let ``Custom attribute used on type and let in rec module`` () =
FSharp """
module rec M

type CustomAttribute() =
inherit System.Attribute()

[<Custom>] type A = | A
[<Custom>] let a = ()
"""
|> typecheck
|> shouldSucceed

// Nested module case: open inside outer module, attribute on inner module
[<Fact>]
let ``Open inside nested module resolves for attribute on inner module in namespace rec`` () =
FSharp """
namespace rec Ns

module Outer =
open System.Runtime.CompilerServices

[<Extension>]
module Inner =
[<Extension>]
let ext1 (x: int) = x.ToString()
"""
|> asLibrary
|> typecheck
|> shouldSucceed

// Non-recursive baseline: should always work
[<Fact>]
let ``Extension attribute works without rec - baseline`` () =
FSharp """
namespace Ns

open System.Runtime.CompilerServices

[<Extension>]
module Module =
[<Extension>]
let ext1 (x: int) = x.ToString()
"""
|> asLibrary
|> typecheck
|> shouldSucceed

// Multiple opens in namespace rec
[<Fact>]
let ``Multiple opens resolve for attributes in namespace rec`` () =
FSharp """
namespace rec Ns

open System
open System.Runtime.CompilerServices

[<Extension>]
module Module =
[<Extension>]
[<Obsolete("test")>]
let ext1 (x: int) = x.ToString()
"""
|> asLibrary
|> typecheck
|> shouldSucceed

// Open in module rec resolves for module attributes
[<Fact>]
let ``Open in module rec resolves for nested module attribute`` () =
FSharp """
module rec M

open System.Runtime.CompilerServices

[<Extension>]
module Inner =
[<Extension>]
let ext1 (x: int) = x.ToString()
"""
|> typecheck
|> shouldSucceed

// Open with Obsolete attribute in namespace rec
[<Fact>]
let ``Obsolete attribute resolves via open in namespace rec`` () =
FSharp """
namespace rec Ns

open System

[<Obsolete("deprecated")>]
module DeprecatedModule =
let x = 42
"""
|> asLibrary
|> typecheck
|> shouldSucceed
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
<Compile Include="Conformance\BasicGrammarElements\AccessibilityAnnotations\PermittedLocations\PermittedLocations.fs" />
<Compile Include="Conformance\BasicGrammarElements\CustomAttributes\AttributeInheritance\AttributeInheritance.fs" />
<Compile Include="Conformance\BasicGrammarElements\CustomAttributes\AttributeUsage\AttributeUsage.fs" />
<Compile Include="Conformance\BasicGrammarElements\CustomAttributes\AttributeUsage\AttributeResolutionInRecursiveScopes.fs" />
<Compile Include="Conformance\BasicGrammarElements\CustomAttributes\Basic\Basic.fs" />
<Compile Include="Conformance\BasicGrammarElements\CustomAttributes\ImportedAttributes\ImportedAttributes.fs" />
<Compile Include="Conformance\BasicGrammarElements\CustomAttributes\ArgumentsOfAllTypes\ArgumentsOfAllTypes.fs" />
Expand Down
Loading