Skip to content

Commit

Permalink
Signature of nested type with generic type parameter (#15259)
Browse files Browse the repository at this point in the history
* Proof of concept

* Add generic parameter names to ModuleOrType.

* Revert ModuleOrType change

* Process ticks in demangledPath of TType_app.

* Only apply new logic when includeStaticParametersInTypeNames is active.

* Use FactForNETCOREAPP

* Fix build

---------

Co-authored-by: Tomas Grosup <[email protected]>
  • Loading branch information
nojaf and T-Gro authored Jun 5, 2023
1 parent aab21e5 commit 97c650f
Show file tree
Hide file tree
Showing 5 changed files with 180 additions and 12 deletions.
65 changes: 55 additions & 10 deletions src/Compiler/Checking/NicePrint.fs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,19 @@ 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.
/// 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: string list option) =

let prefix = usePrefix denv tcref
let isArray = not prefix && isArrayTyconRef denv.g tcref
Expand Down Expand Up @@ -201,21 +213,22 @@ module internal PrintUtilities =
if denv.shortTypeNames then
tyconTextL
else
let path = tcref.CompilationPath.DemangledPath
let path =
if denv.includeStaticParametersInTypeNames then
path
Option.defaultValue tcref.CompilationPath.DemangledPath demangledPath
else
path |> List.map (fun s ->
tcref.CompilationPath.DemangledPath
|> List.map (fun s ->
let i = s.IndexOf(',')
if i <> -1 then s.Substring(0, i)+"<...>" // apparently has static params, shorten
else 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 +512,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 +584,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 +913,39 @@ 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 demangledCompilationPathOpt, args =
if not denv.includeStaticParametersInTypeNames then
None, args
else
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
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>"
String.Concat(path.Substring(0, m.Index), genericArgs), (skip + take)
)

Some path, List.skip skip args

layoutTypeAppWithInfoAndPrec
denv
env
(layoutTyconRefImpl false denv tc demangledCompilationPathOpt)
prec
prefix
args

// Layout a tuple type
| TType_anon (anonInfo, tys) ->
Expand Down Expand Up @@ -1621,7 +1666,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 Expand Up @@ -2615,7 +2660,7 @@ let stringOfFSAttrib denv x = x |> PrintTypes.layoutAttrib denv |> squareAngleL

let stringOfILAttrib denv x = x |> PrintTypes.layoutILAttrib denv |> squareAngleL |> showL

let fqnOfEntityRef g x = x |> layoutTyconRefImpl false (DisplayEnv.Empty g) |> showL
let fqnOfEntityRef g x = layoutTyconRefImpl false (DisplayEnv.Empty g) x None |> showL

let layoutImpliedSignatureOfModuleOrNamespace showHeader denv infoReader ad m contents =
InferredSigPrinting.layoutImpliedSignatureOfModuleOrNamespace showHeader denv infoReader ad m contents
Expand Down
3 changes: 2 additions & 1 deletion src/Compiler/TypedTree/TypedTreeOps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3117,7 +3117,8 @@ type DisplayEnv =
suppressInlineKeyword = false
showDocumentation = true
shrinkOverloads = false
escapeKeywordNames = true }
escapeKeywordNames = true
includeStaticParametersInTypeNames = true }
denv.SetOpenPaths
[ FSharpLib.RootPath
FSharpLib.CorePath
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,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
99 changes: 99 additions & 0 deletions tests/FSharp.Compiler.ComponentTests/Signatures/NestedTypeTests.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
module FSharp.Compiler.ComponentTests.Signatures.NestedTypeTests

open Xunit
open FsUnit
open FSharp.Test
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"""

[<FactForNETCOREAPP>]
let ``ImmutableArray<'T>.Builder roundtrip`` () =
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
24 changes: 23 additions & 1 deletion tests/FSharp.Test.Utilities/Compiler.fs
Original file line number Diff line number Diff line change
Expand Up @@ -891,7 +891,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

0 comments on commit 97c650f

Please sign in to comment.