Skip to content

Commit

Permalink
[VS] Get source text of an Entity symbol's signature for viewing meta…
Browse files Browse the repository at this point in the history
…data (#11325)

* Initial work for generating a signature of a given symbol for viewing metadata

* Do not output the 'event' keyword

* Adding open paths and fixing surface area

* Fixing tests. Adding more extensions to Project and Document. Better handling of determining if we should do semantic diagnostics for a given file.

* Trying to go to definition on metadata. Managing metadata as source projects.

* Navigating to external symbol

* ignoring tests

* Renaming TryGenerateSignatureText to TryGetMetadataText

* Fixing namespace and some parts of module
  • Loading branch information
TIHan authored Mar 31, 2021
1 parent bafc70c commit 2d4a8fc
Show file tree
Hide file tree
Showing 20 changed files with 411 additions and 194 deletions.
15 changes: 14 additions & 1 deletion src/fsharp/NicePrint.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1424,7 +1424,7 @@ module private TastDefinitionPrinting =

let nameL = eventTag |> wordL
let typL = layoutType denv (e.GetDelegateType(amap, m))
staticL ^^ WordL.keywordEvent ^^ nameL ^^ WordL.colon ^^ typL
staticL ^^ WordL.keywordMember ^^ nameL ^^ WordL.colon ^^ typL

let private layoutPropInfo denv amap m (p: PropInfo) =
match p.ArbitraryValRef with
Expand Down Expand Up @@ -1783,6 +1783,17 @@ module private TastDefinitionPrinting =
let xs = List.map (layoutTycon denv infoReader ad m false (wordL (tagKeyword "and"))) t
aboveListL (x :: xs)

let layoutEntity (denv: DisplayEnv) (infoReader: InfoReader) ad m (entity: Entity) =
if entity.IsModule then
// TODO: Implementation of layoutTycon isn't correct for module.
layoutTycon denv infoReader ad m false (wordL (tagKeyword "module")) entity
elif entity.IsNamespace then
emptyL
elif entity.IsExceptionDecl then
layoutExnDefn denv entity
else
layoutTycon denv infoReader ad m true WordL.keywordType entity

//--------------------------------------------------------------------------

module private InferredSigPrinting =
Expand Down Expand Up @@ -1976,6 +1987,8 @@ let stringOfTyparConstraints denv x = x |> PrintTypes.layoutConstraintsWithInfo

let layoutTycon denv infoReader ad m (* width *) x = TastDefinitionPrinting.layoutTycon denv infoReader ad m true WordL.keywordType x (* |> Display.squashTo width *)

let layoutEntity denv infoReader ad m x = TastDefinitionPrinting.layoutEntity denv infoReader ad m x

let layoutUnionCases denv x = x |> TastDefinitionPrinting.layoutUnionCaseFields denv true

/// Pass negative number as pos in case of single cased discriminated unions
Expand Down
2 changes: 2 additions & 0 deletions src/fsharp/NicePrint.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ val stringOfTyparConstraints: denv:DisplayEnv -> x:(Typar * TyparConstraint) lis

val layoutTycon: denv:DisplayEnv -> infoReader:InfoReader -> ad:AccessorDomain -> m:range -> x:Tycon -> Layout

val layoutEntity: denv:DisplayEnv -> infoReader:InfoReader -> ad:AccessorDomain -> m:range -> x:Entity -> Layout

val layoutUnionCases: denv:DisplayEnv -> x:RecdField list -> Layout

val isGeneratedUnionCaseField: pos:int -> f:RecdField -> bool
Expand Down
104 changes: 104 additions & 0 deletions src/fsharp/symbols/Symbols.fs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ open FSharp.Compiler.TypedTree
open FSharp.Compiler.TypedTreeBasics
open FSharp.Compiler.TcGlobals
open FSharp.Compiler.TypedTreeOps
open FSharp.Compiler.Syntax.PrettyNaming
open FSharp.Compiler.AbstractIL

type FSharpAccessibility(a:Accessibility, ?isProtected) =
let isProtected = defaultArg isProtected false
Expand Down Expand Up @@ -806,6 +808,108 @@ type FSharpEntity(cenv: SymbolEnv, entity:EntityRef) =
member x.TryGetMembersFunctionsAndValues() =
try x.MembersFunctionsAndValues with _ -> [||] :> _

member this.TryGetMetadataText() =
match entity.TryDeref with
| ValueSome entity ->
if entity.IsNamespace then None
else

let denv = DisplayEnv.Empty cenv.g
let denv =
{ denv with
showImperativeTyparAnnotations=true
showHiddenMembers=true
showObsoleteMembers=true
showAttributes=true
shrinkOverloads=false
printVerboseSignatures=false }

let extraOpenPath =
match entity.CompilationPathOpt with
| Some cpath ->
let rec getOpenPath accessPath acc =
match accessPath with
| [] -> acc
| (name, ModuleOrNamespaceKind.ModuleOrType) :: accessPath ->
getOpenPath accessPath (name :: acc)
| (name, ModuleOrNamespaceKind.Namespace) :: accessPath ->
getOpenPath accessPath (name :: acc)
| (name, ModuleOrNamespaceKind.FSharpModuleWithSuffix) :: accessPath ->
getOpenPath accessPath (name :: acc)

getOpenPath cpath.AccessPath []
| _ ->
[]
|> List.rev

let needOpenType =
match entity.CompilationPathOpt with
| Some cpath ->
match cpath.AccessPath with
| (_, ModuleOrNamespaceKind.ModuleOrType) :: _ ->
match this.DeclaringEntity with
| Some (declaringEntity: FSharpEntity) -> not declaringEntity.IsFSharpModule
| _ -> false
| _ -> false
| _ ->
false

let denv =
denv.SetOpenPaths
([ FSharpLib.RootPath
FSharpLib.CorePath
FSharpLib.CollectionsPath
FSharpLib.ControlPath
(IL.splitNamespace FSharpLib.ExtraTopLevelOperatorsName)
extraOpenPath
])

let infoReader = cenv.infoReader

let openPathL =
extraOpenPath
|> List.map (fun x -> Layout.wordL (TaggedText.tagUnknownEntity x))

let pathL =
if List.isEmpty extraOpenPath then
Layout.emptyL
else
Layout.sepListL (Layout.sepL TaggedText.dot) openPathL

let headerL =
if List.isEmpty extraOpenPath then
Layout.emptyL
else
Layout.(^^)
(Layout.wordL (TaggedText.tagKeyword "namespace"))
pathL

let openL =
if List.isEmpty openPathL then Layout.emptyL
else
let openKeywordL =
if needOpenType then
Layout.(^^)
(Layout.wordL (TaggedText.tagKeyword "open"))
(Layout.wordL TaggedText.keywordType)
else
Layout.wordL (TaggedText.tagKeyword "open")
Layout.(^^)
openKeywordL
pathL

Layout.aboveListL
[
(Layout.(^^) headerL (Layout.sepL TaggedText.lineBreak))
(Layout.(^^) openL (Layout.sepL TaggedText.lineBreak))
(NicePrint.layoutEntity denv infoReader AccessibleFromSomewhere range0 entity)
]
|> LayoutRender.showL
|> SourceText.ofString
|> Some
| _ ->
None

override x.Equals(other: obj) =
box x === other ||
match other with
Expand Down
3 changes: 3 additions & 0 deletions src/fsharp/symbols/Symbols.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,9 @@ type FSharpEntity =
/// Safe version of `GetMembersFunctionsAndValues`.
member TryGetMembersFunctionsAndValues: unit -> IList<FSharpMemberOrFunctionOrValue>

/// Get the source text of the entity's signature to be used as metadata.
member TryGetMetadataText: unit -> ISourceText option

/// Represents a delegate signature in an F# symbol
[<Class>]
type FSharpDelegateSignature =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1494,11 +1494,11 @@ FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: FSharp.Compiler.Syntax.Pars
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: FSharp.Compiler.Syntax.ParsedInput get_ParseTree()
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.EditorServices.ParameterLocations] FindParameterLocations(FSharp.Compiler.Text.Position)
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] TryRangeOfExprInYieldOrReturn(FSharp.Compiler.Text.Position)
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] TryRangeOfExpressionBeingDereferencedContainingPos(FSharp.Compiler.Text.Position)
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] TryRangeOfFunctionOrMethodBeingApplied(FSharp.Compiler.Text.Position)
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] TryRangeOfNameOfNearestOuterBindingContainingPos(FSharp.Compiler.Text.Position)
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] TryRangeOfRecordExpressionContainingPos(FSharp.Compiler.Text.Position)
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] TryRangeOfRefCellDereferenceContainingPos(FSharp.Compiler.Text.Position)
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] TryRangeOfExpressionBeingDereferencedContainingPos(FSharp.Compiler.Text.Position)
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] ValidateBreakpointLocation(FSharp.Compiler.Text.Position)
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Text.Range]] GetAllArgumentsForFunctionApplicationAtPostion(FSharp.Compiler.Text.Position)
FSharp.Compiler.CodeAnalysis.FSharpParseFileResults: Microsoft.FSharp.Core.FSharpOption`1[System.Tuple`2[FSharp.Compiler.Syntax.Ident,System.Int32]] TryIdentOfPipelineContainingPosAndNumArgsApplied(FSharp.Compiler.Text.Position)
Expand Down Expand Up @@ -3821,6 +3821,7 @@ FSharp.Compiler.Symbols.FSharpEntity: Microsoft.FSharp.Core.FSharpOption`1[FShar
FSharp.Compiler.Symbols.FSharpEntity: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Symbols.FSharpEntity] get_DeclaringEntity()
FSharp.Compiler.Symbols.FSharpEntity: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Symbols.FSharpType] BaseType
FSharp.Compiler.Symbols.FSharpEntity: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Symbols.FSharpType] get_BaseType()
FSharp.Compiler.Symbols.FSharpEntity: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.ISourceText] TryGetMetadataText()
FSharp.Compiler.Symbols.FSharpEntity: Microsoft.FSharp.Core.FSharpOption`1[System.String] Namespace
FSharp.Compiler.Symbols.FSharpEntity: Microsoft.FSharp.Core.FSharpOption`1[System.String] TryFullName
FSharp.Compiler.Symbols.FSharpEntity: Microsoft.FSharp.Core.FSharpOption`1[System.String] TryGetFullCompiledName()
Expand Down
4 changes: 4 additions & 0 deletions vsintegration/src/FSharp.Editor/Common/Constants.fs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ module internal FSharpConstants =
/// "F# Miscellaneous Files"
let FSharpMiscellaneousFilesName = "F# Miscellaneous Files"

[<Literal>]
/// "F# Metadata"
let FSharpMetadataName = "F# Metadata"

[<RequireQualifiedAccess>]
module internal FSharpProviderConstants =

Expand Down
10 changes: 10 additions & 0 deletions vsintegration/src/FSharp.Editor/Common/Extensions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ open FSharp.Compiler.EditorServices
open FSharp.Compiler.Syntax
open FSharp.Compiler.Text

open Microsoft.VisualStudio.FSharp.Editor

type private FSharpGlyph = FSharp.Compiler.EditorServices.FSharpGlyph
type private FSharpRoslynGlyph = Microsoft.CodeAnalysis.ExternalAccess.FSharp.FSharpGlyph

Expand All @@ -36,6 +38,11 @@ type ProjectId with
member this.ToFSharpProjectIdString() =
this.Id.ToString("D").ToLowerInvariant()

type Project with
member this.IsFSharpMiscellaneous = this.Name = FSharpConstants.FSharpMiscellaneousFilesName
member this.IsFSharpMetadata = this.Name.StartsWith(FSharpConstants.FSharpMetadataName)
member this.IsFSharpMiscellaneousOrMetadata = this.IsFSharpMiscellaneous || this.IsFSharpMetadata

type Document with
member this.TryGetLanguageService<'T when 'T :> ILanguageService>() =
match this.Project with
Expand All @@ -47,6 +54,9 @@ type Document with
languageServices.GetService<'T>()
|> Some

member this.IsFSharpScript =
isScriptFile this.FilePath

module private SourceText =

open System.Runtime.CompilerServices
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,9 @@ type internal FSharpDocumentDiagnosticAnalyzer
interface IFSharpDocumentDiagnosticAnalyzer with

member this.AnalyzeSyntaxAsync(document: Document, cancellationToken: CancellationToken): Task<ImmutableArray<Diagnostic>> =
if document.Project.IsFSharpMetadata then Task.FromResult(ImmutableArray.Empty)
else

asyncMaybe {
let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, cancellationToken, userOpName)
return!
Expand All @@ -125,14 +128,14 @@ type internal FSharpDocumentDiagnosticAnalyzer
|> RoslynHelpers.StartAsyncAsTask cancellationToken

member this.AnalyzeSemanticsAsync(document: Document, cancellationToken: CancellationToken): Task<ImmutableArray<Diagnostic>> =
if document.Project.IsFSharpMiscellaneousOrMetadata && not document.IsFSharpScript then Task.FromResult(ImmutableArray.Empty)
else

asyncMaybe {
let! parsingOptions, _, projectOptions = projectInfoManager.TryGetOptionsForDocumentOrProject(document, cancellationToken, userOpName)
if document.Project.Name <> FSharpConstants.FSharpMiscellaneousFilesName || isScriptFile document.FilePath then
return!
FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(checkerProvider.Checker, document, parsingOptions, projectOptions, DiagnosticsType.Semantic)
|> liftAsync
else
return ImmutableArray<Diagnostic>.Empty
return!
FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(checkerProvider.Checker, document, parsingOptions, projectOptions, DiagnosticsType.Semantic)
|> liftAsync
}
|> Async.map (Option.defaultValue ImmutableArray<Diagnostic>.Empty)
|> RoslynHelpers.StartAsyncAsTask cancellationToken
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ type internal SimplifyNameDiagnosticAnalyzer
interface IFSharpSimplifyNameDiagnosticAnalyzer with

member _.AnalyzeSemanticsAsync(descriptor, document: Document, cancellationToken: CancellationToken) =
if document.Project.IsFSharpMiscellaneousOrMetadata && not document.IsFSharpScript then Tasks.Task.FromResult(ImmutableArray.Empty)
else

asyncMaybe {
do! Option.guard document.FSharpOptions.CodeFixes.SimplifyName
do Trace.TraceInformation("{0:n3} (start) SimplifyName", DateTime.Now.TimeOfDay.TotalSeconds)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ type internal UnusedDeclarationsAnalyzer
interface IFSharpUnusedDeclarationsDiagnosticAnalyzer with

member _.AnalyzeSemanticsAsync(descriptor, document, cancellationToken) =
if document.Project.IsFSharpMiscellaneousOrMetadata && not document.IsFSharpScript then Threading.Tasks.Task.FromResult(ImmutableArray.Empty)
else

asyncMaybe {
do! Option.guard document.FSharpOptions.CodeFixes.UnusedDeclarations

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ type internal UnusedOpensDiagnosticAnalyzer
interface IFSharpUnusedOpensDiagnosticAnalyzer with

member _.AnalyzeSemanticsAsync(descriptor, document: Document, cancellationToken: CancellationToken) =
if document.Project.IsFSharpMiscellaneousOrMetadata && not document.IsFSharpScript then Tasks.Task.FromResult(ImmutableArray.Empty)
else

asyncMaybe {
do Trace.TraceInformation("{0:n3} (start) UnusedOpensAnalyzer", DateTime.Now.TimeOfDay.TotalSeconds)
let! _parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject(document, cancellationToken, userOpName)
Expand Down
2 changes: 1 addition & 1 deletion vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
<Compile Include="Common\Logger.fsi" />
<Compile Include="Common\Logger.fs" />
<Compile Include="Common\Pervasive.fs" />
<Compile Include="Common\Extensions.fs" />
<Compile Include="Common\Constants.fs" />
<Compile Include="Common\Extensions.fs" />
<Compile Include="Common\Error.fs" />
<Compile Include="Common\Logging.fs" />
<Compile Include="Common\RoslynHelpers.fs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ open Microsoft.CodeAnalysis
open Microsoft.VisualStudio
open Microsoft.VisualStudio.FSharp.Editor
open Microsoft.VisualStudio.LanguageServices
open Microsoft.VisualStudio.LanguageServices.ProjectSystem
open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Diagnostics

#nowarn "9" // NativePtr.toNativeInt
Expand All @@ -23,10 +24,11 @@ type internal FSharpCheckerProvider
(
analyzerService: IFSharpDiagnosticAnalyzerService,
[<Import(typeof<VisualStudioWorkspace>)>] workspace: VisualStudioWorkspace,
projectContextFactory: IWorkspaceProjectContextFactory,
settings: EditorOptions
) =

let metadataAsSource = FSharpMetadataAsSourceService()
let metadataAsSource = FSharpMetadataAsSourceService(projectContextFactory)

let tryGetMetadataSnapshot (path, timeStamp) =
try
Expand Down
Loading

0 comments on commit 2d4a8fc

Please sign in to comment.