Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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,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))
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(SynOpenDeclTarget.ModuleOrNamespace _ as 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,193 @@
// 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
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This test case is not reproing #5795, and in fact I think #5795 is not fixed by this PR - at the very minimum the comment should be removed

[<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

// Negative test: undefined attribute still errors in namespace rec
[<Fact>]
let ``Undefined attribute still errors in namespace rec`` () =
FSharp """
namespace rec Ns

[<DoesNotExist>]
module M =
let x = 1
"""
|> asLibrary
|> typecheck
|> shouldFail
|> withErrorCode 39

// Negative test: invalid open target still errors despite error suppression in pre-pass
[<Fact>]
let ``Invalid open target still errors in namespace rec`` () =
FSharp """
namespace rec Ns

open DoesNotExist.Namespace

[<System.Obsolete>]
module M =
let x = 1
"""
|> asLibrary
|> typecheck
|> shouldFail
|> withErrorCode 39

// Forward-reference open to sibling module in the same recursive scope
[<Fact>]
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
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