Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Signature of nested type with generic type parameter #15259

Merged
merged 9 commits into from
Jun 5, 2023
Merged
Show file tree
Hide file tree
Changes from 5 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
77 changes: 63 additions & 14 deletions src/Compiler/Checking/NicePrint.fs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,20 @@ module internal PrintUtilities =
| GenericParameterStyle.Prefix -> true
| GenericParameterStyle.Suffix -> false

let layoutTyconRefImpl isAttribute (denv: DisplayEnv) (tcref: TyconRef) =
/// <summary>
/// Creates a layout for TyconRef.
/// </summary>
/// <param name="isAttribute"></param>
/// <param name="denv"></param>
/// <param name="tcref"></param>
/// <param name="demangledPath">
/// Used in the case the TyconRef is a nested type from another assembly which has generic type parameters in the path.
/// Choice1Of2 means the original path string and Choice2Of2 contains a string replacement.
/// For example: System.Collections.Immutable.ImmutableArray&gt;'T&lt;.Builder
/// Lead to access path: System.Collections.Immutable.ImmutableArray`1
/// ImmutableArray`1 will be transformed to ImmutableArray&gt;'t&lt;
/// </param>
let layoutTyconRefImpl isAttribute (denv: DisplayEnv) (tcref: TyconRef) (demangledPath: Choice<string,string> list option) =

let prefix = usePrefix denv tcref
let isArray = not prefix && isArrayTyconRef denv.g tcref
Expand Down Expand Up @@ -201,21 +214,28 @@ module internal PrintUtilities =
if denv.shortTypeNames then
tyconTextL
else
let path = tcref.CompilationPath.DemangledPath
let path =
if denv.includeStaticParametersInTypeNames then
path
let mapPath (s:string) =
let i = s.IndexOf(',')
if i <> -1 then
s.Substring(0, i)+"<...>" // apparently has static params, shorten
else
path |> List.map (fun s ->
let i = s.IndexOf(',')
if i <> -1 then s.Substring(0, i)+"<...>" // apparently has static params, shorten
else s)
s

let path =
match demangledPath with
| None -> tcref.CompilationPath.DemangledPath |> List.map mapPath
| Some demangled ->
demangled
|> List.map (function
| Choice1Of2 s -> mapPath s
| Choice2Of2 s -> s)

let pathText = trimPathByDisplayEnv denv path
if pathText = "" then tyconTextL else leftL (tagUnknownEntity pathText) ^^ tyconTextL

let layoutBuiltinAttribute (denv: DisplayEnv) (attrib: BuiltinAttribInfo) =
let tcref = attrib.TyconRef
squareAngleL (layoutTyconRefImpl true denv tcref)
squareAngleL (layoutTyconRefImpl true denv tcref None)

/// layout the xml docs immediately before another block
let layoutXmlDoc (denv: DisplayEnv) alwaysAddEmptyLine (xml: XmlDoc) restL =
Expand Down Expand Up @@ -499,7 +519,7 @@ module PrintTypes =
layoutAccessibilityCore denv accessibility ++ itemL

/// Layout a reference to a type
let layoutTyconRef denv tcref = layoutTyconRefImpl false denv tcref
let layoutTyconRef denv tcref = layoutTyconRefImpl false denv tcref None

/// Layout the flags of a member
let layoutMemberFlags (memFlags: SynMemberFlags) =
Expand Down Expand Up @@ -571,7 +591,7 @@ module PrintTypes =

/// Layout an attribute 'Type(arg1, ..., argN)'
and layoutAttrib denv (Attrib(tcref, _, args, props, _, _, _)) =
let tcrefL = layoutTyconRefImpl true denv tcref
let tcrefL = layoutTyconRefImpl true denv tcref None
let argsL = bracketL (layoutAttribArgs denv args props)
if List.isEmpty args && List.isEmpty props then
tcrefL
Expand Down Expand Up @@ -900,7 +920,36 @@ module PrintTypes =
| TType_ucase (UnionCaseRef(tc, _), args)
| TType_app (tc, args, _) ->
let prefix = usePrefix denv tc
layoutTypeAppWithInfoAndPrec denv env (layoutTyconRef denv tc) prec prefix args
let demangledCompilationPath, args =
let regex = System.Text.RegularExpressions.Regex(@"\`\d+")
let path, skip =
(0, tc.CompilationPath.DemangledPath)
||> List.mapFold (fun skip path ->
// Verify the path does not contain a generic parameter count.
// For example Foo`3 indicates that there are three parameters in args that belong to this path.
let m = regex.Match(path)
if not m.Success then
Choice1Of2 path, skip
else
let take = m.Value.Replace("`", "") |> int
let genericArgs =
List.skip skip args
|> List.take take
|> List.map (layoutTypeWithInfoAndPrec denv env prec >> showL)
|> String.concat ","
|> sprintf "<%s>"
Choice2Of2 (String.Concat(path.Substring(0, m.Index), genericArgs)), (skip + take)
)

path, List.skip skip args

layoutTypeAppWithInfoAndPrec
denv
env
(layoutTyconRefImpl false denv tc (Some demangledCompilationPath))
prec
prefix
args

// Layout a tuple type
| TType_anon (anonInfo, tys) ->
Expand Down Expand Up @@ -1621,7 +1670,7 @@ module TastDefinitionPrinting =
let layoutExtensionMember denv infoReader (vref: ValRef) =
let (@@*) = if denv.printVerboseSignatures then (@@----) else (@@--)
let tycon = vref.MemberApparentEntity.Deref
let nameL = layoutTyconRefImpl false denv vref.MemberApparentEntity
let nameL = layoutTyconRefImpl false denv vref.MemberApparentEntity None
let nameL = layoutAccessibility denv tycon.Accessibility nameL // "type-accessibility"
let tps =
match PartitionValTyparsForApparentEnclosingType denv.g vref.Deref with
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@
<Compile Include="Signatures\ArrayTests.fs" />
<Compile Include="Signatures\TypeTests.fs" />
<Compile Include="Signatures\SigGenerationRoundTripTests.fs" />
<Compile Include="Signatures\NestedTypeTests.fs" />
<Compile Include="StaticLinking\StaticLinking.fs" />
<Compile Include="FSharpChecker\CommonWorkflows.fs" />
<Compile Include="FSharpChecker\SymbolUse.fs" />
Expand Down
100 changes: 100 additions & 0 deletions tests/FSharp.Compiler.ComponentTests/Signatures/NestedTypeTests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
module FSharp.Compiler.ComponentTests.Signatures.NestedTypeTests

open Xunit
open FsUnit
open FSharp.Test.Compiler
open FSharp.Compiler.ComponentTests.Signatures.TestHelpers

[<Fact>]
let ``Nested type with generics`` () =
let CSLib =
CSharp """
namespace Lib
{
public class Upper<T>
{
public class Lower<U>
{
public void Meh()
{

}
}
}
}
"""

FSharp
"""
module Sample

open Lib

let f (g: Upper<int>.Lower<string>) = g.Meh()
"""
|> withReferences [ CSLib ]
|> printSignatures
|> should equal
"""
module Sample

val f: g: Lib.Upper<int>.Lower<string> -> unit"""

[<Fact>]
let ``Multiple generics in nested type`` () =
let CSLib =
CSharp """
namespace Lib
{
public class Root<A, B, C, D, E>
{
public class Foo<T, U, V, W>
{
public class Bar<X, Y, Z>
{
public void Meh()
{

}
}
}
}
}
"""

FSharp
"""
module Sample

open System
open Lib

let f (g: Root<TimeSpan,TimeSpan,TimeSpan,TimeSpan,TimeSpan>.Foo<int, float, string, System.DateTime>.Bar<char, int, string>) = g.Meh()
"""
|> withReferences [ CSLib ]
|> printSignatures
|> should equal
"""
module Sample

val f: g: Lib.Root<System.TimeSpan,System.TimeSpan,System.TimeSpan,System.TimeSpan,System.TimeSpan>.Foo<int,float,string,System.DateTime>.Bar<char,int,string> -> unit"""

#if NETCOREAPP
nojaf marked this conversation as resolved.
Show resolved Hide resolved
[<Fact>]
let ``ImmutableArray<'T>.Builder roundtrip`` () =
T-Gro marked this conversation as resolved.
Show resolved Hide resolved
let impl =
"""
module Library

open System.Collections.Immutable

type ImmutableArrayViaBuilder<'T>(builder: ImmutableArray<'T>.Builder) = class end
"""

let signature = printSignatures (Fs impl)

Fsi signature
|> withAdditionalSourceFile (FsSource impl)
|> compile
|> shouldSucceed
#endif
24 changes: 23 additions & 1 deletion tests/FSharp.Test.Utilities/Compiler.fs
Original file line number Diff line number Diff line change
Expand Up @@ -829,7 +829,29 @@ module rec Compiler =
| FS fsSource ->
let source = fsSource.Source.GetSourceText |> Option.defaultValue ""
let fileName = fsSource.Source.ChangeExtension.GetSourceFileName
let options = fsSource.Options |> Array.ofList

let references =
let disposals = ResizeArray<IDisposable>()
let outputDirectory =
match fsSource.OutputDirectory with
| Some di -> di
| None -> DirectoryInfo(tryCreateTemporaryDirectory())
let references = processReferences fsSource.References outputDirectory
if references.IsEmpty then
Array.empty
else
outputDirectory.Create()
disposals.Add({ new IDisposable with member _.Dispose() = outputDirectory.Delete(true) })
// Note that only the references are relevant here
let compilation = Compilation.Compilation([], CompileOutput.Exe,Array.empty, TargetFramework.Current, references, None, None)
evaluateReferences outputDirectory disposals fsSource.IgnoreWarnings compilation
|> fst

let options =
[|
yield! fsSource.Options |> Array.ofList
yield! references
|]
CompilerAssert.TypeCheck(options, fileName, source)
| _ -> failwith "Typecheck only supports F#"

Expand Down