Skip to content

Commit

Permalink
Go to C# symbols (#3357)
Browse files Browse the repository at this point in the history
* go to C# symbols

* fix tests

* cleanup

* fix tests

* support field, events and constructors

* fix tests

* implement IVsSymbolicNavigationNotify

* cleanup

* fix

* cleanup

* Improve package definition specs fro F# VS Package

* Fixed some bugs with nested types with fields/events

* fix compilation

* use AppTy AP

* Add support for overloaded methods, generic methods, generic (nested) classes, constructors

* Jump to property definition instead of getter/setter

* Add ExternalSymbol to FSharp.Compiler.Service.fsproj

* Fix FSharp.Compiler.Private.BuildFromSource.fsproj

* address code review
  • Loading branch information
vasily-kirichenko authored and KevinRansom committed Aug 21, 2017
1 parent cd23839 commit 9606209
Show file tree
Hide file tree
Showing 14 changed files with 417 additions and 56 deletions.
6 changes: 6 additions & 0 deletions fcs/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj
Expand Up @@ -559,6 +559,12 @@
<Compile Include="$(FSharpSourcesRoot)\fsharp\vs\ServiceXmlDocParser.fs">
<Link>Service/ServiceXmlDocParser.fs</Link>
</Compile>
<Compile Include="$(FSharpSourcesRoot)\fsharp\vs\ExternalSymbol.fsi">
<Link>Service/ExternalSymbol.fsi</Link>
</Compile>
<Compile Include="$(FSharpSourcesRoot)\fsharp\vs\ExternalSymbol.fs">
<Link>Service/ExternalSymbol.fs</Link>
</Compile>
<Compile Include="$(FSharpSourcesRoot)\fsharp\vs\service.fsi">
<Link>Service/service.fsi</Link>
</Compile>
Expand Down
Expand Up @@ -558,6 +558,12 @@
<Compile Include="..\SimulatedMSBuildReferenceResolver.fs">
<Link>Service/SimulatedMSBuildReferenceResolver.fs</Link>
</Compile>
<Compile Include="..\vs\ExternalSymbol.fsi">
<Link>Service/ExternalSymbol.fsi</Link>
</Compile>
<Compile Include="..\vs\ExternalSymbol.fs">
<Link>Service/ExternalSymbol.fs</Link>
</Compile>
<Compile Include="..\vs\service.fsi">
<Link>Service/service.fsi</Link>
</Compile>
Expand Down
Expand Up @@ -594,6 +594,12 @@
<Compile Include="..\SimulatedMSBuildReferenceResolver.fs">
<Link>Service/SimulatedMSBuildReferenceResolver.fs</Link>
</Compile>
<Compile Include="..\vs\ExternalSymbol.fsi">
<Link>Service/ExternalSymbol.fsi</Link>
</Compile>
<Compile Include="..\vs\ExternalSymbol.fs">
<Link>Service/ExternalSymbol.fs</Link>
</Compile>
<Compile Include="..\vs\service.fsi">
<Link>Service/service.fsi</Link>
</Compile>
Expand Down
3 changes: 2 additions & 1 deletion src/fsharp/symbols/SymbolHelpers.fs
Expand Up @@ -394,11 +394,12 @@ module internal SymbolHelpers =
| Item.SetterArg (_,item) -> rangeOfItem g preferFlag item
| Item.ArgName (id,_, _) -> Some id.idRange
| Item.CustomOperation (_,_,implOpt) -> implOpt |> Option.bind (rangeOfMethInfo g preferFlag)
| Item.ImplicitOp (_, {contents = Some(TraitConstraintSln.FSMethSln(_, vref, _))}) -> Some vref.Range
| Item.ImplicitOp _ -> None
| Item.NewDef id -> Some id.idRange
| Item.UnqualifiedType tcrefs -> tcrefs |> List.tryPick (rangeOfEntityRef preferFlag >> Some)
| Item.DelegateCtor typ
| Item.FakeInterfaceCtor typ -> typ |> tryNiceEntityRefOfTy |> Option.map (rangeOfEntityRef preferFlag)
| Item.NewDef _ -> None

// Provided type definitions do not have a useful F# CCU for the purposes of goto-definition.
let computeCcuOfTyconRef (tcref:TyconRef) =
Expand Down
116 changes: 116 additions & 0 deletions src/fsharp/vs/ExternalSymbol.fs
@@ -0,0 +1,116 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.FSharp.Compiler.SourceCodeServices

open FSharp.Reflection
open Microsoft.FSharp.Compiler.AbstractIL.IL
open System.Diagnostics

module private Option =

let ofOptionList (xs : 'a option list) : 'a list option =

if xs |> List.forall Option.isSome then
xs |> List.map Option.get |> Some
else
None

/// Represents a type in an external (non F#) assembly.
[<RequireQualifiedAccess>]
type ExternalType =
/// Type defined in non-F# assembly.
| Type of fullName: string * genericArgs: ExternalType list
/// Array of type that is defined in non-F# assembly.
| Array of inner: ExternalType
/// Pointer defined in non-F# assembly.
| Pointer of inner: ExternalType
/// Type variable defined in non-F# assembly.
| TypeVar of typeName: string
override this.ToString() =
match this with
| Type (name, genericArgs) ->
match genericArgs with
| [] -> ""
| args ->
args
|> List.map (sprintf "%O")
|> String.concat ", "
|> sprintf "<%s>"
|> sprintf "%s%s" name
| Array inner -> sprintf "%O[]" inner
| Pointer inner -> sprintf "&%O" inner
| TypeVar name -> sprintf "'%s" name

module ExternalType =
let rec internal tryOfILType (typeVarNames: string array) (ilType: ILType) =

match ilType with
| ILType.Array (_, inner) ->
tryOfILType typeVarNames inner |> Option.map ExternalType.Array
| ILType.Boxed tyspec
| ILType.Value tyspec ->
tyspec.GenericArgs
|> List.map (tryOfILType typeVarNames)
|> Option.ofOptionList
|> Option.map (fun genericArgs -> ExternalType.Type (tyspec.FullName, genericArgs))
| ILType.Ptr inner ->
tryOfILType typeVarNames inner |> Option.map ExternalType.Pointer
| ILType.TypeVar ordinal ->
typeVarNames
|> Array.tryItem (int ordinal)
|> Option.map (fun typeVarName -> ExternalType.TypeVar typeVarName)
| _ ->
None

[<RequireQualifiedAccess>]
type ParamTypeSymbol =
| Param of ExternalType
| Byref of ExternalType
override this.ToString () =
match this with
| Param t -> t.ToString()
| Byref t -> sprintf "ref %O" t

module ParamTypeSymbol =
let rec internal tryOfILType (typeVarNames : string array) =
function
| ILType.Byref inner -> ExternalType.tryOfILType typeVarNames inner |> Option.map ParamTypeSymbol.Byref
| ilType -> ExternalType.tryOfILType typeVarNames ilType |> Option.map ParamTypeSymbol.Param

let internal tryOfILTypes typeVarNames ilTypes =
ilTypes |> List.map (tryOfILType typeVarNames) |> Option.ofOptionList

[<RequireQualifiedAccess>]
[<DebuggerDisplay "{ToDebuggerDisplay(),nq}">]
type ExternalSymbol =
| Type of fullName: string
| Constructor of typeName: string * args: ParamTypeSymbol list
| Method of typeName: string * name: string * paramSyms: ParamTypeSymbol list * genericArity: int
| Field of typeName: string * name: string
| Event of typeName: string * name: string
| Property of typeName: string * name: string
override this.ToString () =
match this with
| Type fullName -> fullName
| Constructor (typeName, args) ->
args
|> List.map (sprintf "%O")
|> String.concat ", "
|> sprintf "%s..ctor(%s)" typeName
| Method (typeName, name, args, genericArity) ->
let genericAritySuffix =
if genericArity > 0 then sprintf "`%d" genericArity
else ""

args
|> List.map (sprintf "%O")
|> String.concat ", "
|> sprintf "%s.%s%s(%s)" typeName name genericAritySuffix
| Field (typeName, name)
| Event (typeName, name)
| Property (typeName, name) ->
sprintf "%s.%s" typeName name

member internal this.ToDebuggerDisplay () =
let caseInfo, _ = FSharpValue.GetUnionFields(this, typeof<ExternalSymbol>)
sprintf "%s %O" caseInfo.Name this
47 changes: 47 additions & 0 deletions src/fsharp/vs/ExternalSymbol.fsi
@@ -0,0 +1,47 @@
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.FSharp.Compiler.SourceCodeServices

open FSharp.Reflection
open Microsoft.FSharp.Compiler.AbstractIL.IL

/// Represents a type in an external (non F#) assembly.
[<RequireQualifiedAccess>]
type ExternalType =
/// Type defined in non-F# assembly.
| Type of fullName: string * genericArgs: ExternalType list
/// Array of type that is defined in non-F# assembly.
| Array of inner: ExternalType
/// Pointer defined in non-F# assembly.
| Pointer of inner: ExternalType
/// Type variable defined in non-F# assembly.
| TypeVar of typeName: string
override ToString : unit -> string

module ExternalType =
val internal tryOfILType : string array -> ILType -> ExternalType option


/// Represents the type of a single method parameter
[<RequireQualifiedAccess>]
type ParamTypeSymbol =
| Param of ExternalType
| Byref of ExternalType
override ToString : unit -> string

module ParamTypeSymbol =
val internal tryOfILType : string array -> ILType -> ParamTypeSymbol option
val internal tryOfILTypes : string array -> ILType list -> ParamTypeSymbol list option


/// Represents a symbol in an external (non F#) assembly
[<RequireQualifiedAccess>]
type ExternalSymbol =
| Type of fullName: string
| Constructor of typeName: string * args: ParamTypeSymbol list
| Method of typeName: string * name: string * paramSyms: ParamTypeSymbol list * genericArity: int
| Field of typeName: string * name: string
| Event of typeName: string * name: string
| Property of typeName: string * name: string
override ToString : unit -> string
member internal ToDebuggerDisplay : unit -> string
133 changes: 96 additions & 37 deletions src/fsharp/vs/service.fs
Expand Up @@ -17,7 +17,7 @@ open Microsoft.FSharp.Core.Printf
open Microsoft.FSharp.Compiler
open Microsoft.FSharp.Compiler.AbstractIL
open Microsoft.FSharp.Compiler.AbstractIL.IL
open Microsoft.FSharp.Compiler.AbstractIL.Diagnostics
open Microsoft.FSharp.Compiler.AbstractIL.Diagnostics
open Microsoft.FSharp.Compiler.AbstractIL.Internal
open Microsoft.FSharp.Compiler.AbstractIL.Internal.Library

Expand Down Expand Up @@ -88,12 +88,14 @@ type FSharpFindDeclFailureReason =
// trying to find declaration of ProvidedMember without TypeProviderDefinitionLocationAttribute
| ProvidedMember of string

[<RequireQualifiedAccess>]
type FSharpFindDeclResult =
/// declaration not found + reason
| DeclNotFound of FSharpFindDeclFailureReason
/// found declaration
| DeclFound of range

/// Indicates an external declaration was found
| ExternalDecl of assembly : string * externalSym : ExternalSymbol

/// This type is used to describe what was found during the name resolution.
/// (Depending on the kind of the items, we may stop processing or continue to find better items)
Expand Down Expand Up @@ -1096,50 +1098,107 @@ type TypeCheckInfo
(fun msg ->
Trace.TraceInformation(sprintf "FCS: recovering from error in GetMethodsAsSymbols: '%s'" msg)
None)

member scope.GetDeclarationLocation (ctok, line, lineStr, colAtEndOfNames, names, preferFlag) =
ErrorScope.Protect Range.range0
(fun () ->
match GetDeclItemsForNamesAtPosition (ctok, None,Some(names), None, line, lineStr, colAtEndOfNames, ResolveTypeNamesToCtors,ResolveOverloads.Yes,(fun() -> []), fun _ -> false) with
| None
| Some ([], _, _, _) -> FSharpFindDeclResult.DeclNotFound (FSharpFindDeclFailureReason.Unknown "")
| Some (item :: _, _, _, _) ->

// For IL-based entities, switch to a different item. This is because
// rangeOfItem, ccuOfItem don't work on IL methods or fields.
//
// Later comment: to be honest, they aren't going to work on these new items either.
// This is probably old code from when we supported 'go to definition' generating IL metadata.
let item =
| Some (item :: _, _, _, _) ->
let getTypeVarNames (ilinfo: ILMethInfo) =
let classTypeParams = ilinfo.DeclaringTyconRef.ILTyconRawMetadata.GenericParams |> List.map (fun paramDef -> paramDef.Name)
let methodTypeParams = ilinfo.FormalMethodTypars |> List.map (fun typ -> typ.Name)
classTypeParams @ methodTypeParams |> Array.ofList

let result =
match item.Item with
| Item.MethodGroup (_, (ILMeth (_,ilinfo,_)) :: _, _)
| Item.CtorGroup (_, (ILMeth (_,ilinfo,_)) :: _) -> Item.Types ("", [ ilinfo.ApparentEnclosingType ])
| Item.ILField (ILFieldInfo (typeInfo, _)) -> Item.Types ("", [ typeInfo.ToType ])
| Item.ImplicitOp(_, {contents = Some(TraitConstraintSln.FSMethSln(_, vref, _))}) -> Item.Value(vref)
| _ -> item.Item

let fail defaultReason =
match item with
| Item.CtorGroup (_, (ILMeth (_,ilinfo,_)) :: _) ->
match ilinfo.MetadataScope with
| ILScopeRef.Assembly assref ->
let typeVarNames = getTypeVarNames ilinfo
ParamTypeSymbol.tryOfILTypes typeVarNames ilinfo.ILMethodRef.ArgTypes
|> Option.map (fun args ->
let externalSym = ExternalSymbol.Constructor (ilinfo.ILMethodRef.EnclosingTypeRef.FullName, args)
FSharpFindDeclResult.ExternalDecl (assref.Name, externalSym))
| _ -> None

| Item.MethodGroup (name, (ILMeth (_,ilinfo,_)) :: _, _) ->
match ilinfo.MetadataScope with
| ILScopeRef.Assembly assref ->
let typeVarNames = getTypeVarNames ilinfo
ParamTypeSymbol.tryOfILTypes typeVarNames ilinfo.ILMethodRef.ArgTypes
|> Option.map (fun args ->
let externalSym = ExternalSymbol.Method (ilinfo.ILMethodRef.EnclosingTypeRef.FullName, name, args, ilinfo.ILMethodRef.GenericArity)
FSharpFindDeclResult.ExternalDecl (assref.Name, externalSym))
| _ -> None

| Item.Property (name, ILProp (_, propInfo) :: _) ->
let methInfo =
if propInfo.HasGetter then Some (propInfo.GetterMethod g)
elif propInfo.HasSetter then Some (propInfo.SetterMethod g)
else None

match methInfo with
| Some methInfo ->
match methInfo.MetadataScope with
| ILScopeRef.Assembly assref ->
let externalSym = ExternalSymbol.Property (methInfo.ILMethodRef.EnclosingTypeRef.FullName, name)
Some (FSharpFindDeclResult.ExternalDecl (assref.Name, externalSym))
| _ -> None
| None -> None

| Item.ILField (ILFieldInfo (ILTypeInfo (tr, _, _, _) & typeInfo, fieldDef)) when not tr.IsLocalRef ->
match typeInfo.ILScopeRef with
| ILScopeRef.Assembly assref ->
let externalSym = ExternalSymbol.Field (typeInfo.ILTypeRef.FullName, fieldDef.Name)
Some (FSharpFindDeclResult.ExternalDecl (assref.Name, externalSym))
| _ -> None

| Item.Event (ILEvent (_, ILEventInfo (ILTypeInfo (tr, _, _, _) & typeInfo, eventDef))) when not tr.IsLocalRef ->
match typeInfo.ILScopeRef with
| ILScopeRef.Assembly assref ->
let externalSym = ExternalSymbol.Event (typeInfo.ILTypeRef.FullName, eventDef.Name)
Some (FSharpFindDeclResult.ExternalDecl (assref.Name, externalSym))
| _ -> None

| Item.ImplicitOp(_, {contents = Some(TraitConstraintSln.FSMethSln(_, _vref, _))}) ->
//Item.Value(vref)
None

| Item.Types (_, [AppTy g (tr, _)]) when not tr.IsLocalRef ->
match tr.TypeReprInfo, tr.PublicPath with
| TILObjectRepr(TILObjectReprData (ILScopeRef.Assembly assref, _, _)), Some (PubPath parts) ->
let fullName = parts |> String.concat "."
Some (FSharpFindDeclResult.ExternalDecl (assref.Name, ExternalSymbol.Type fullName))
| _ -> None
| _ -> None

match result with
| Some x -> x
| None ->
let fail defaultReason =
match item.Item with
#if EXTENSIONTYPING
| SymbolHelpers.ItemIsProvidedType g (tcref) -> FSharpFindDeclResult.DeclNotFound (FSharpFindDeclFailureReason.ProvidedType(tcref.DisplayName))
| Item.CtorGroup(name, ProvidedMeth(_)::_)
| Item.MethodGroup(name, ProvidedMeth(_)::_, _)
| Item.Property(name, ProvidedProp(_)::_) -> FSharpFindDeclResult.DeclNotFound (FSharpFindDeclFailureReason.ProvidedMember(name))
| Item.Event(ProvidedEvent(_) as e) -> FSharpFindDeclResult.DeclNotFound (FSharpFindDeclFailureReason.ProvidedMember(e.EventName))
| Item.ILField(ProvidedField(_) as f) -> FSharpFindDeclResult.DeclNotFound (FSharpFindDeclFailureReason.ProvidedMember(f.FieldName))
| SymbolHelpers.ItemIsProvidedType g (tcref) -> FSharpFindDeclResult.DeclNotFound (FSharpFindDeclFailureReason.ProvidedType(tcref.DisplayName))
| Item.CtorGroup(name, ProvidedMeth(_)::_)
| Item.MethodGroup(name, ProvidedMeth(_)::_, _)
| Item.Property(name, ProvidedProp(_)::_) -> FSharpFindDeclResult.DeclNotFound (FSharpFindDeclFailureReason.ProvidedMember(name))
| Item.Event(ProvidedEvent(_) as e) -> FSharpFindDeclResult.DeclNotFound (FSharpFindDeclFailureReason.ProvidedMember(e.EventName))
| Item.ILField(ProvidedField(_) as f) -> FSharpFindDeclResult.DeclNotFound (FSharpFindDeclFailureReason.ProvidedMember(f.FieldName))
#endif
| _ -> FSharpFindDeclResult.DeclNotFound defaultReason

match rangeOfItem g preferFlag item with
| None -> fail (FSharpFindDeclFailureReason.Unknown "")
| Some itemRange ->

let projectDir = Filename.directoryName (if projectFileName = "" then mainInputFileName else projectFileName)
let filename = fileNameOfItem g (Some projectDir) itemRange item
if FileSystem.SafeExists filename then
FSharpFindDeclResult.DeclFound (mkRange filename itemRange.Start itemRange.End)
else
fail FSharpFindDeclFailureReason.NoSourceCode // provided items may have TypeProviderDefinitionLocationAttribute that binds them to some location
| _ -> FSharpFindDeclResult.DeclNotFound defaultReason

match rangeOfItem g preferFlag item.Item with
| None -> fail (FSharpFindDeclFailureReason.Unknown "")
| Some itemRange ->
let projectDir = Filename.directoryName (if projectFileName = "" then mainInputFileName else projectFileName)
let filename = fileNameOfItem g (Some projectDir) itemRange item.Item
if FileSystem.SafeExists filename then
FSharpFindDeclResult.DeclFound (mkRange filename itemRange.Start itemRange.End)
else
fail FSharpFindDeclFailureReason.NoSourceCode // provided items may have TypeProviderDefinitionLocationAttribute that binds them to some location
)
(fun msg ->
Trace.TraceInformation(sprintf "FCS: recovering from error in GetDeclarationLocation: '%s'" msg)
Expand Down
4 changes: 3 additions & 1 deletion src/fsharp/vs/service.fsi
Expand Up @@ -59,7 +59,9 @@ type internal FSharpFindDeclResult =
/// Indicates a declaration location was not found, with an additional reason
| DeclNotFound of FSharpFindDeclFailureReason
/// Indicates a declaration location was found
| DeclFound of range
| DeclFound of range
/// Indicates an external declaration was found
| ExternalDecl of assembly : string * externalSym : ExternalSymbol

/// Represents the checking context implied by the ProjectOptions
[<Sealed>]
Expand Down

0 comments on commit 9606209

Please sign in to comment.