diff --git a/src/Compiler/Checking/CheckBasics.fs b/src/Compiler/Checking/CheckBasics.fs index 6df98110001..bbfa5557b2d 100644 --- a/src/Compiler/Checking/CheckBasics.fs +++ b/src/Compiler/Checking/CheckBasics.fs @@ -2,6 +2,7 @@ module internal FSharp.Compiler.CheckBasics +open System.Collections.Concurrent open System.Collections.Generic open FSharp.Compiler.Diagnostics @@ -313,6 +314,8 @@ type TcFileState = diagnosticOptions: FSharpDiagnosticOptions + argInfoCache: ConcurrentDictionary<(string * range), ArgReprInfo> + // forward call TcPat: WarnOnUpperFlag -> TcFileState -> TcEnv -> PrelimValReprInfo option -> TcPatValFlags -> TcPatLinearEnv -> TType -> SynPat -> (TcPatPhase2Input -> Pattern) * TcPatLinearEnv @@ -362,6 +365,7 @@ type TcFileState = conditionalDefines = conditionalDefines isInternalTestSpanStackReferring = isInternalTestSpanStackReferring diagnosticOptions = diagnosticOptions + argInfoCache = ConcurrentDictionary() TcPat = tcPat TcSimplePats = tcSimplePats TcSequenceExpressionEntry = tcSequenceExpressionEntry diff --git a/src/Compiler/Checking/CheckBasics.fsi b/src/Compiler/Checking/CheckBasics.fsi index 6081eab8ef6..24c4fe0a42e 100644 --- a/src/Compiler/Checking/CheckBasics.fsi +++ b/src/Compiler/Checking/CheckBasics.fsi @@ -2,6 +2,7 @@ module internal FSharp.Compiler.CheckBasics +open System.Collections.Concurrent open System.Collections.Generic open FSharp.Compiler.Diagnostics open Internal.Utilities.Library @@ -263,6 +264,11 @@ type TcFileState = diagnosticOptions: FSharpDiagnosticOptions + /// A cache for ArgReprInfos which get created multiple times for the same values + /// Since they need to be later mutated with updates from signature files this should make sure + /// we're always dealing with the same instance and the updates don't get lost + argInfoCache: ConcurrentDictionary<(string * range), ArgReprInfo> + // forward call TcPat: WarnOnUpperFlag -> TcFileState diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs index 3f5896e2c07..0699c35ae5f 100644 --- a/src/Compiler/Checking/CheckDeclarations.fs +++ b/src/Compiler/Checking/CheckDeclarations.fs @@ -531,7 +531,7 @@ module TcRecdUnionAndEnumDeclarations = | SynUnionCaseKind.FullType (ty, arity) -> let tyR, _ = TcTypeAndRecover cenv NoNewTypars CheckCxs ItemOccurence.UseInType WarnOnIWSAM.Yes env tpenv ty - let curriedArgTys, recordTy = GetTopTauTypeInFSharpForm g (arity |> TranslateSynValInfo m (TcAttributes cenv env) |> TranslatePartialValReprInfo []).ArgInfos tyR m + let curriedArgTys, recordTy = GetTopTauTypeInFSharpForm g (arity |> TranslateSynValInfo cenv m (TcAttributes cenv env) |> TranslatePartialValReprInfo []).ArgInfos tyR m if curriedArgTys.Length > 1 then errorR(Error(FSComp.SR.tcIllegalFormForExplicitTypeDeclaration(), m)) @@ -2461,7 +2461,7 @@ module EstablishTypeDefinitionCores = | SynUnionCaseKind.FullType (ty, arity) -> let tyR, _ = TcTypeAndRecover cenv NoNewTypars NoCheckCxs ItemOccurence.UseInType WarnOnIWSAM.Yes env tpenv ty - let curriedArgTys, _ = GetTopTauTypeInFSharpForm g (arity |> TranslateSynValInfo m (TcAttributes cenv env) |> TranslatePartialValReprInfo []).ArgInfos tyR m + let curriedArgTys, _ = GetTopTauTypeInFSharpForm g (arity |> TranslateSynValInfo cenv m (TcAttributes cenv env) |> TranslatePartialValReprInfo []).ArgInfos tyR m if curriedArgTys.Length > 1 then errorR(Error(FSComp.SR.tcIllegalFormForExplicitTypeDeclaration(), m)) @@ -3462,7 +3462,7 @@ module EstablishTypeDefinitionCores = noFieldsCheck userFields primaryConstructorInDelegateCheck(implicitCtorSynPats) let tyR, _ = TcTypeAndRecover cenv NoNewTypars CheckCxs ItemOccurence.UseInType WarnOnIWSAM.Yes envinner tpenv ty - let _, _, curriedArgInfos, returnTy, _ = GetValReprTypeInCompiledForm g (arity |> TranslateSynValInfo m (TcAttributes cenv envinner) |> TranslatePartialValReprInfo []) 0 tyR m + let _, _, curriedArgInfos, returnTy, _ = GetValReprTypeInCompiledForm g (arity |> TranslateSynValInfo cenv m (TcAttributes cenv envinner) |> TranslatePartialValReprInfo []) 0 tyR m if curriedArgInfos.Length < 1 then error(Error(FSComp.SR.tcInvalidDelegateSpecification(), m)) if curriedArgInfos.Length > 1 then error(Error(FSComp.SR.tcDelegatesCannotBeCurried(), m)) let ttps = thisTyconRef.Typars m diff --git a/src/Compiler/Checking/CheckExpressions.fs b/src/Compiler/Checking/CheckExpressions.fs index 7d7ba39a0e4..367b7d81292 100644 --- a/src/Compiler/Checking/CheckExpressions.fs +++ b/src/Compiler/Checking/CheckExpressions.fs @@ -912,7 +912,8 @@ let AdjustValSynInfoInSignature g ty (SynValInfo(argsData, retData) as sigMD) = | _ -> sigMD -let TranslateTopArgSynInfo isArg m tcAttributes (SynArgInfo(Attributes attrs, isOpt, nm)) = + +let TranslateTopArgSynInfo (cenv: cenv) isArg m tcAttributes (SynArgInfo(Attributes attrs, isOpt, nm)) = // Synthesize an artificial "OptionalArgument" attribute for the parameter let optAttrs = if isOpt then @@ -921,7 +922,7 @@ let TranslateTopArgSynInfo isArg m tcAttributes (SynArgInfo(Attributes attrs, is Target=None AppliesToGetterAndSetter=false Range=m} : SynAttribute) ] - else + else [] if isArg && not (isNil attrs) && Option.isNone nm then @@ -932,7 +933,26 @@ let TranslateTopArgSynInfo isArg m tcAttributes (SynArgInfo(Attributes attrs, is // Call the attribute checking function let attribs = tcAttributes (optAttrs@attrs) - ({ Attribs = attribs; Name = nm } : ArgReprInfo) + + let key = nm |> Option.map (fun id -> id.idText, id.idRange) + + let argInfo = + key + |> Option.map cenv.argInfoCache.TryGetValue + |> Option.bind (fun (found, info) -> + if found then + Some info + else None) + |> Option.defaultValue ({ Attribs = attribs; Name = nm; OtherRange = None }: ArgReprInfo) + + match key with + | Some k -> cenv.argInfoCache.[k] <- argInfo + | None -> () + + // Set freshly computed attribs in case they are different in the cache + argInfo.Attribs <- attribs + + argInfo /// Members have an arity inferred from their syntax. This "valSynData" is not quite the same as the arities /// used in the middle and backends of the compiler ("valReprInfo"). @@ -940,9 +960,9 @@ let TranslateTopArgSynInfo isArg m tcAttributes (SynArgInfo(Attributes attrs, is /// Hence remove all "zeros" from arity and replace them with 1 here. /// Note we currently use the compiled form for choosing unique names, to distinguish overloads because this must match up /// between signature and implementation, and the signature just has "unit". -let TranslateSynValInfo m tcAttributes (SynValInfo(argsData, retData)) = - PrelimValReprInfo (argsData |> List.mapSquared (TranslateTopArgSynInfo true m (tcAttributes AttributeTargets.Parameter)), - retData |> TranslateTopArgSynInfo false m (tcAttributes AttributeTargets.ReturnValue)) +let TranslateSynValInfo (cenv: cenv) m tcAttributes (SynValInfo(argsData, retData)) = + PrelimValReprInfo (argsData |> List.mapSquared (TranslateTopArgSynInfo cenv true m (tcAttributes AttributeTargets.Parameter)), + retData |> TranslateTopArgSynInfo cenv false m (tcAttributes AttributeTargets.ReturnValue)) let TranslatePartialValReprInfo tps (PrelimValReprInfo (argsData, retData)) = ValReprInfo(ValReprInfo.InferTyparInfo tps, argsData, retData) @@ -4057,7 +4077,7 @@ and TcPseudoMemberSpec cenv newOk env synTypes tpenv synMemberSig m = else warning(Error(FSComp.SR.tcTraitMayNotUseComplexThings(), m)) - let item = Item.ArgName (Some id, memberConstraintTy, None, id.idRange) + let item = Item.OtherName (Some id, memberConstraintTy, None, None, id.idRange) CallNameResolutionSink cenv.tcSink (id.idRange, env.NameEnv, item, emptyTyparInst, ItemOccurence.Use, env.AccessRights) TTrait(tys, logicalCompiledName, memberFlags, argTys, returnTy, ref None), tpenv @@ -4152,7 +4172,7 @@ and TcValSpec (cenv: cenv) env declKind newOk containerInfo memFlagsOpt thisTyOp let reallyGenerateOneMember(id: Ident, valSynInfo, tyR, memberFlags) = let PrelimValReprInfo(argsData, _) as prelimValReprInfo = - TranslateSynValInfo id.idRange (TcAttributes cenv env) valSynInfo + TranslateSynValInfo cenv id.idRange (TcAttributes cenv env) valSynInfo // Fold in the optional argument information @@ -4208,7 +4228,7 @@ and TcValSpec (cenv: cenv) env declKind newOk containerInfo memFlagsOpt thisTyOp yield! generateOneMember({memberFlags with MemberKind=SynMemberKind.PropertySet}) ], tpenv | _ -> let valSynInfo = AdjustValSynInfoInSignature g declaredTy valSynInfo - let prelimValReprInfo = TranslateSynValInfo id.idRange (TcAttributes cenv env) valSynInfo + let prelimValReprInfo = TranslateSynValInfo cenv id.idRange (TcAttributes cenv env) valSynInfo [ ValSpecResult(altActualParent, None, id, enclosingDeclaredTypars, declaredTypars, declaredTy, prelimValReprInfo, declKind) ], tpenv //------------------------------------------------------------------------- @@ -4663,7 +4683,7 @@ and TcStaticConstantParameter (cenv: cenv) (env: TcEnv) tpenv kind (StripParenTy let record ttype = match idOpt with | Some id -> - let item = Item.ArgName (Some id, ttype, Some container, id.idRange) + let item = Item.OtherName (Some id, ttype, None, Some container, id.idRange) CallNameResolutionSink cenv.tcSink (id.idRange, env.NameEnv, item, emptyTyparInst, ItemOccurence.Use, env.AccessRights) | _ -> () @@ -6143,6 +6163,7 @@ and TcIteratedLambdas (cenv: cenv) isFirst (env: TcEnv) overallTy takenNames tpe | infos :: rest -> if infos.Length = vspecs.Length then (vspecs, infos) ||> List.iter2 (fun v argInfo -> + v.SetArgReprInfoForDisplay (Some argInfo) let inlineIfLambda = HasFSharpAttribute g g.attrib_InlineIfLambdaAttribute argInfo.Attribs if inlineIfLambda then v.SetInlineIfLambda()) @@ -6828,7 +6849,7 @@ and ComputeObjectExprOverrides (cenv: cenv) (env: TcEnv) tpenv impls = // Convert the syntactic info to actual info let overrides = (overrides, bindNameAndSynInfoPairs) ||> List.map2 (fun (id: Ident, memberFlags, ty, bindingAttribs, bindingBody) (_, valSynData) -> - let partialValInfo = TranslateSynValInfo id.idRange (TcAttributes cenv env) valSynData + let partialValInfo = TranslateSynValInfo cenv id.idRange (TcAttributes cenv env) valSynData let tps, _ = tryDestForallTy g ty let valInfo = TranslatePartialValReprInfo tps partialValInfo DispatchSlotChecking.GetObjectExprOverrideInfo g cenv.amap (implTy, id, memberFlags, ty, valInfo, bindingAttribs, bindingBody)) @@ -8252,7 +8273,7 @@ and TcItemThen (cenv: cenv) (overallTy: OverallTy) env tpenv (tinstEnclosing, it // These items are not expected here - they are only used for reporting symbols from name resolution to language service | Item.ActivePatternCase _ | Item.AnonRecdField _ - | Item.ArgName _ + | Item.OtherName _ | Item.CustomBuilder _ | Item.ModuleOrNamespaces _ | Item.NewDef _ @@ -9153,7 +9174,7 @@ and TcLookupItemThen cenv overallTy env tpenv mObjExpr objExpr objExprTy delayed | Item.NewDef _ | Item.SetterArg _ | Item.CustomBuilder _ - | Item.ArgName _ + | Item.OtherName _ | Item.ActivePatternCase _ -> error (Error (FSComp.SR.tcSyntaxFormUsedOnlyWithRecordLabelsPropertiesAndFields(), mItem)) @@ -9765,7 +9786,7 @@ and TcMethodApplication | Some id -> id.idRange | None -> id.idRange let container = ArgumentContainer.Method finalCalledMethInfo - let item = Item.ArgName (idOpt, assignedArg.CalledArg.CalledArgumentType, Some container, m) + let item = Item.OtherName (idOpt, assignedArg.CalledArg.CalledArgumentType, None, Some container, m) CallNameResolutionSink cenv.tcSink (id.idRange, env.NameEnv, item, emptyTyparInst, ItemOccurence.Use, ad)) /// STEP 6. Build the call expression, then adjust for byref-returns, out-parameters-as-tuples, post-hoc property assignments, methods-as-first-class-value, @@ -10405,7 +10426,7 @@ and TcNormalizedBinding declKind (cenv: cenv) env tpenv overallTy safeThisValOpt // Use the syntactic arity if we're defining a function let (SynValData(_, valSynInfo, _)) = valSynData - let prelimValReprInfo = TranslateSynValInfo mBinding (TcAttributes cenv env) valSynInfo + let prelimValReprInfo = TranslateSynValInfo cenv mBinding (TcAttributes cenv env) valSynInfo // Check the pattern of the l.h.s. of the binding let tcPatPhase2, (TcPatLinearEnv (tpenv, nameToPrelimValSchemeMap, _)) = @@ -11482,7 +11503,7 @@ and AnalyzeAndMakeAndPublishRecursiveValue // NOTE: The type scheme here is normally not 'complete'!!!! The type is more or less just a type variable at this point. // NOTE: top arity, type and typars get fixed-up after inference let prelimTyscheme = GeneralizedType(enclosingDeclaredTypars@declaredTypars, ty) - let prelimValReprInfo = TranslateSynValInfo mBinding (TcAttributes cenv envinner) valSynInfo + let prelimValReprInfo = TranslateSynValInfo cenv mBinding (TcAttributes cenv envinner) valSynInfo let valReprInfo, valReprInfoForDisplay = UseSyntacticValReprInfo declKind prelimTyscheme prelimValReprInfo let hasDeclaredTypars = not (List.isEmpty declaredTypars) let prelimValScheme = ValScheme(bindingId, prelimTyscheme, valReprInfo, valReprInfoForDisplay, memberInfoOpt, false, inlineFlag, NormalVal, vis, false, false, false, hasDeclaredTypars) @@ -12055,6 +12076,27 @@ and TcLetrecBindings overridesOK (cenv: cenv) env tpenv (binds, bindsm, scopem) // Bind specifications of values //------------------------------------------------------------------------- +let private PublishArguments (cenv: cenv) (env: TcEnv) vspec (synValSig: SynValSig) numEnclosingTypars = + let arities = arityOfVal vspec + let _tps, _witnessInfos, curriedArgInfos, _retTy, _ = GetValReprTypeInCompiledForm cenv.g arities numEnclosingTypars vspec.Type vspec.DefinitionRange + + let argInfos = + // Drop "this" argument for instance methods + match vspec.IsInstanceMember, curriedArgInfos with + | true, _::args + | _, args -> args + + let synArgInfos = synValSig.SynInfo.CurriedArgInfos + let argData = + (synArgInfos, argInfos) + ||> Seq.zip + |> Seq.collect (fun x -> x ||> Seq.zip) + |> Seq.choose (fun (synArgInfo, argInfo) -> synArgInfo.Ident |> Option.map (pair argInfo)) + + for (argTy, argReprInfo), ident in argData do + let item = Item.OtherName (Some ident, argTy, Some argReprInfo, None, ident.idRange) + CallNameResolutionSink cenv.tcSink (ident.idRange, env.NameEnv, item, emptyTyparInst, ItemOccurence.Binding, env.AccessRights) + let TcAndPublishValSpec (cenv: cenv, env, containerInfo: ContainerInfo, declKind : DeclKind, memFlagsOpt, tpenv, synValSig) = let g = cenv.g @@ -12122,6 +12164,8 @@ let TcAndPublishValSpec (cenv: cenv, env, containerInfo: ContainerInfo, declKind let xmlDoc = xmlDoc.ToXmlDoc(checkXmlDocs, paramNames) let vspec = MakeAndPublishVal cenv env (altActualParent, true, declKind, ValNotInRecScope, valscheme, attrs, xmlDoc, literalValue, false) + PublishArguments cenv env vspec synValSig allDeclaredTypars.Length + assert(vspec.InlineInfo = inlineFlag) vspec, tpenv) diff --git a/src/Compiler/Checking/CheckExpressions.fsi b/src/Compiler/Checking/CheckExpressions.fsi index f3e9ad559d4..0d02f07a223 100644 --- a/src/Compiler/Checking/CheckExpressions.fsi +++ b/src/Compiler/Checking/CheckExpressions.fsi @@ -852,6 +852,7 @@ val TcValSpec: /// giving the names and attributes relevant to arguments and return, but before type /// parameters have been fully inferred via generalization. val TranslateSynValInfo: + cenv: TcFileState -> range -> tcAttributes: (AttributeTargets -> SynAttribute list -> Attrib list) -> synValInfo: SynValInfo -> diff --git a/src/Compiler/Checking/CheckIncrementalClasses.fs b/src/Compiler/Checking/CheckIncrementalClasses.fs index 7ace6fe83b3..9e6570056c1 100644 --- a/src/Compiler/Checking/CheckIncrementalClasses.fs +++ b/src/Compiler/Checking/CheckIncrementalClasses.fs @@ -130,7 +130,7 @@ let TcImplicitCtorLhs_Phase2A(cenv: cenv, env, tpenv, tcref: TyconRef, vis, attr CheckForNonAbstractInterface g ModuleOrMemberBinding tcref memberFlags false id.idRange let memberInfo = MakeMemberDataAndMangledNameForMemberVal(g, tcref, false, attribs, [], memberFlags, valSynData, id, false) - let prelimValReprInfo = TranslateSynValInfo m (TcAttributes cenv env) valSynData + let prelimValReprInfo = TranslateSynValInfo cenv m (TcAttributes cenv env) valSynData let prelimTyschemeG = GeneralizedType(copyOfTyconTypars, ctorTy) let isComplete = ComputeIsComplete copyOfTyconTypars [] ctorTy let varReprInfo = InferGenericArityFromTyScheme prelimTyschemeG prelimValReprInfo @@ -154,7 +154,7 @@ let TcImplicitCtorLhs_Phase2A(cenv: cenv, env, tpenv, tcref: TyconRef, vis, attr let id = ident ("cctor", m) CheckForNonAbstractInterface g ModuleOrMemberBinding tcref ClassCtorMemberFlags false id.idRange let memberInfo = MakeMemberDataAndMangledNameForMemberVal(g, tcref, false, [], [], ClassCtorMemberFlags, valSynData, id, false) - let prelimValReprInfo = TranslateSynValInfo m (TcAttributes cenv env) valSynData + let prelimValReprInfo = TranslateSynValInfo cenv m (TcAttributes cenv env) valSynData let prelimTyschemeG = GeneralizedType(copyOfTyconTypars, cctorTy) let valReprInfo = InferGenericArityFromTyScheme prelimTyschemeG prelimValReprInfo let cctorValScheme = ValScheme(id, prelimTyschemeG, Some valReprInfo, None, Some memberInfo, false, ValInline.Never, NormalVal, Some (SynAccess.Private Range.Zero), false, true, false, false) diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs index 570928368a8..edeed1136d2 100644 --- a/src/Compiler/Checking/NameResolution.fs +++ b/src/Compiler/Checking/NameResolution.fs @@ -229,7 +229,7 @@ type Item = /// Represents the resolution of a name to a named argument // - // In the FCS API, Item.ArgName corresponds to FSharpParameter symbols. + // In the FCS API, Item.OtherName corresponds to FSharpParameter symbols. // Not all parameters have names, e.g. for 'g' in this: // // let f (g: int -> int) x = ... @@ -238,7 +238,7 @@ type Item = // based on analyzing the type of g as a function type. // // For these parameters, the identifier will be missing. - | ArgName of ident: Ident option * argType: TType * container: ArgumentContainer option * range: range + | OtherName of ident: Ident option * argType: TType * argInfo: ArgReprInfo option * container: ArgumentContainer option * range: range /// Represents the resolution of a name to a named property setter | SetterArg of Ident * Item @@ -283,8 +283,8 @@ type Item = | Item.TypeVar (nm, _) -> nm | Item.Trait traitInfo -> traitInfo.MemberDisplayNameCore | Item.ModuleOrNamespaces(modref :: _) -> modref.DisplayNameCore - | Item.ArgName (Some id, _, _, _) -> id.idText - | Item.ArgName (None, _, _, _) -> "" + | Item.OtherName (ident = Some id) -> id.idText + | Item.OtherName (ident = None) -> "" | Item.SetterArg (id, _) -> id.idText | Item.CustomOperation (customOpName, _, _) -> customOpName | Item.CustomBuilder (nm, _) -> nm @@ -310,8 +310,8 @@ type Item = | Item.UnqualifiedType(tcref :: _) -> tcref.DisplayName | Item.ModuleOrNamespaces(modref :: _) -> modref.DisplayName | Item.TypeVar (nm, _) -> nm |> ConvertLogicalNameToDisplayName - | Item.ArgName (Some id, _, _, _) -> id.idText |> ConvertValLogicalNameToDisplayName false - | Item.ArgName (None, _, _, _) -> "" + | Item.OtherName (ident = Some id) -> id.idText |> ConvertValLogicalNameToDisplayName false + | Item.OtherName (ident = None) -> "" | _ -> d.DisplayNameCore |> ConvertLogicalNameToDisplayName let valRefHash (vref: ValRef) = @@ -1907,10 +1907,10 @@ let ItemsAreEffectivelyEqual g orig other = | Some vref1, Some vref2 -> valRefDefnEq g vref1 vref2 | _ -> false - | Item.ArgName (Some id1, _, _, m1), Item.ArgName (Some id2, _, _, m2) -> + | Item.OtherName (ident = Some id1; range = m1), Item.OtherName (ident = Some id2; range = m2) -> (id1.idText = id2.idText && equals m1 m2) - | Item.ArgName (Some id, _, _, _), ValUse vref | ValUse vref, Item.ArgName (Some id, _, _, _) -> + | Item.OtherName (ident = Some id), ValUse vref | ValUse vref, Item.OtherName (ident = Some id) -> ((equals id.idRange vref.DefinitionRange || equals id.idRange vref.SigRange) && id.idText = vref.DisplayName) | Item.AnonRecdField(anon1, _, i1, _), Item.AnonRecdField(anon2, _, i2, _) -> anonInfoEquiv anon1 anon2 && i1 = i2 @@ -1952,7 +1952,7 @@ let ItemsAreEffectivelyEqualHash (g: TcGlobals) orig = | ActivePatternCaseUse (_, _, idx)-> hash idx | MethodUse minfo -> minfo.ComputeHashCode() | PropertyUse pinfo -> pinfo.ComputeHashCode() - | Item.ArgName (Some id, _, _, _) -> hash id.idText + | Item.OtherName (ident = Some id) -> hash id.idText | ILFieldUse ilfinfo -> ilfinfo.ComputeHashCode() | UnionCaseUse ucase -> hash ucase.CaseName | RecordFieldUse (name, _) -> hash name @@ -2076,7 +2076,7 @@ type TcResultsSinkImpl(tcGlobals, ?sourceText: ISourceText) = let keyOpt = match item with | Item.Value vref -> Some (endPos, vref.DisplayName) - | Item.ArgName (Some id, _, _, _) -> Some (endPos, id.idText) + | Item.OtherName (ident = Some id) -> Some (endPos, id.idText) | _ -> None match keyOpt with @@ -2244,7 +2244,7 @@ let CheckAllTyparsInferrable amap m item = | Item.CustomOperation _ | Item.CustomBuilder _ | Item.TypeVar _ - | Item.ArgName _ + | Item.OtherName _ | Item.ActivePatternResult _ | Item.Value _ | Item.ActivePatternCase _ diff --git a/src/Compiler/Checking/NameResolution.fsi b/src/Compiler/Checking/NameResolution.fsi index f0eed4a6bb8..a4f8a14dd56 100755 --- a/src/Compiler/Checking/NameResolution.fsi +++ b/src/Compiler/Checking/NameResolution.fsi @@ -122,7 +122,7 @@ type Item = /// Represents the resolution of a name to a named argument // - // In the FCS API, Item.ArgName corresponds to FSharpParameter symbols. + // In the FCS API, Item.OtherName corresponds to FSharpParameter symbols. // Not all parameters have names, e.g. for 'g' in this: // // let f (g: int -> int) x = ... @@ -131,7 +131,12 @@ type Item = // based on analyzing the type of g as a function type. // // For these parameters, the identifier will be missing. - | ArgName of ident: Ident option * argType: TType * container: ArgumentContainer option * range: range + | OtherName of + ident: Ident option * + argType: TType * + argInfo: ArgReprInfo option * + container: ArgumentContainer option * + range: range /// Represents the resolution of a name to a named property setter | SetterArg of Ident * Item diff --git a/src/Compiler/Checking/SignatureConformance.fs b/src/Compiler/Checking/SignatureConformance.fs index 7ef065dff27..2aa8bf924db 100644 --- a/src/Compiler/Checking/SignatureConformance.fs +++ b/src/Compiler/Checking/SignatureConformance.fs @@ -309,8 +309,11 @@ type Checker(g, amap, denv, remapInfo: SignatureRepackageInfo, checkingSig) = if sigHasInlineIfLambda && not implHasInlineIfLambda then errorR(Error (FSComp.SR.implMissingInlineIfLambda(), m)) - implArgInfo.Name <- sigArgInfo.Name - implArgInfo.Attribs <- attribs))) && + implArgInfo.OtherRange <- sigArgInfo.Name |> Option.map (fun ident -> ident.idRange) + sigArgInfo.OtherRange <- implArgInfo.Name |> Option.map (fun ident -> ident.idRange) + + implArgInfo.Name <- implArgInfo.Name |> Option.orElse sigArgInfo.Name + implArgInfo.Attribs <- attribs))) && checkAttribs aenv implRetInfo.Attribs sigRetInfo.Attribs (fun attribs -> implRetInfo.Name <- sigRetInfo.Name diff --git a/src/Compiler/Driver/CompilerDiagnostics.fs b/src/Compiler/Driver/CompilerDiagnostics.fs index f57b44c4715..d5ba62b0524 100644 --- a/src/Compiler/Driver/CompilerDiagnostics.fs +++ b/src/Compiler/Driver/CompilerDiagnostics.fs @@ -825,6 +825,7 @@ type Exception with { ArgReprInfo.Name = name |> Option.map (fun name -> Ident(name, range.Zero)) ArgReprInfo.Attribs = [] + ArgReprInfo.OtherRange = None }) let argsL, retTyL, genParamTysL = diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs index f6c3b1b00cf..f3472f256f7 100644 --- a/src/Compiler/Interactive/fsi.fs +++ b/src/Compiler/Interactive/fsi.fs @@ -1615,7 +1615,17 @@ let internal mkBoundValueTypedImpl tcGlobals m moduleName name ty = ty, ValMutability.Immutable, false, - Some(ValReprInfo([], [], { Attribs = []; Name = None })), + Some( + ValReprInfo( + [], + [], + { + Attribs = [] + Name = None + OtherRange = None + } + ) + ), vis, ValNotInRecScope, None, diff --git a/src/Compiler/Service/FSharpCheckerResults.fs b/src/Compiler/Service/FSharpCheckerResults.fs index 7582e109849..c0dcd3f2962 100644 --- a/src/Compiler/Service/FSharpCheckerResults.fs +++ b/src/Compiler/Service/FSharpCheckerResults.fs @@ -259,9 +259,30 @@ type FSharpSymbolUse(denv: DisplayEnv, symbol: FSharpSymbol, inst: TyparInstanti member _.Range = range + member this.IsPrivateToFileAndSignatureFile = + + let couldBeParameter, declarationLocation = + match this.Symbol with + | :? FSharpParameter as p -> true, Some p.DeclarationLocation + | :? FSharpMemberOrFunctionOrValue as m when not m.IsModuleValueOrMember -> true, Some m.DeclarationLocation + | _ -> false, None + + let thisIsSignature = SourceFileImpl.IsSignatureFile this.Range.FileName + + let signatureLocation = this.Symbol.SignatureLocation + + couldBeParameter + && (thisIsSignature + || (signatureLocation.IsSome && signatureLocation <> declarationLocation)) + member this.IsPrivateToFile = + let isPrivate = match this.Symbol with + | _ when this.IsPrivateToFileAndSignatureFile -> false + | :? FSharpMemberOrFunctionOrValue as m when not m.IsModuleValueOrMember -> + // local binding or parameter + true | :? FSharpMemberOrFunctionOrValue as m -> let fileSignatureLocation = m.DeclaringEntity |> Option.bind (fun e -> e.SignatureLocation) @@ -271,10 +292,9 @@ type FSharpSymbolUse(denv: DisplayEnv, symbol: FSharpSymbol, inst: TyparInstanti let fileHasSignatureFile = fileSignatureLocation <> fileDeclarationLocation - fileHasSignatureFile && not m.HasSignatureFile - || not m.IsModuleValueOrMember - || m.Accessibility.IsPrivate + fileHasSignatureFile && not m.HasSignatureFile || m.Accessibility.IsPrivate | :? FSharpEntity as m -> m.Accessibility.IsPrivate + | :? FSharpParameter -> true | :? FSharpGenericParameter -> true | :? FSharpUnionCase as m -> m.Accessibility.IsPrivate | :? FSharpField as m -> m.Accessibility.IsPrivate @@ -577,7 +597,7 @@ type internal TypeCheckInfo x |> List.choose (fun (ParamData (_isParamArray, _isInArg, _isOutArg, _optArgInfo, _callerInfo, name, _, ty)) -> match name with - | Some id -> Some(Item.ArgName(Some id, ty, Some(ArgumentContainer.Method meth), id.idRange)) + | Some id -> Some(Item.OtherName(Some id, ty, None, Some(ArgumentContainer.Method meth), id.idRange)) | None -> None) | _ -> []) @@ -900,7 +920,7 @@ type internal TypeCheckInfo | Item.NewDef _ | Item.SetterArg _ | Item.CustomBuilder _ - | Item.ArgName _ + | Item.OtherName _ | Item.ActivePatternCase _ -> CompletionItemKind.Other let isUnresolved = diff --git a/src/Compiler/Service/FSharpCheckerResults.fsi b/src/Compiler/Service/FSharpCheckerResults.fsi index 5b14cdf9bc7..6eb541982d0 100644 --- a/src/Compiler/Service/FSharpCheckerResults.fsi +++ b/src/Compiler/Service/FSharpCheckerResults.fsi @@ -180,6 +180,10 @@ type public FSharpSymbolUse = /// The range of text representing the reference to the symbol member Range: range + /// Indicates if the FSharpSymbolUse is private to the implementation & signature file. + /// This is true for function and method parameters. + member IsPrivateToFileAndSignatureFile: bool + /// Indicates if the FSharpSymbolUse is declared as private member IsPrivateToFile: bool diff --git a/src/Compiler/Service/ItemKey.fs b/src/Compiler/Service/ItemKey.fs index 49649455c08..ef0ddedc3ea 100644 --- a/src/Compiler/Service/ItemKey.fs +++ b/src/Compiler/Service/ItemKey.fs @@ -298,6 +298,10 @@ and [] ItemKeyStoreBuilder() = writeString ItemKeyTags.parameters writeType false vref.Type + match vref.Deref.ArgReprInfoForDisplay with + | Some ({ OtherRange = Some (r) }) -> writeRange r + | _ -> () + match vref.TryDeclaringEntity with | ParentNone -> writeChar '%' | Parent eref -> writeEntityRef eref @@ -429,8 +433,17 @@ and [] ItemKeyStoreBuilder() = writeString ItemKeyTags.itemDelegateCtor writeType false ty + // Named argument in a signature + | Item.OtherName (ident = Some (ident); argType = ty; argInfo = Some _) -> + writeString ItemKeyTags.itemValue + writeString ident.idText + writeString ItemKeyTags.parameters + writeType false ty + writeRange ident.idRange + writeChar '%' + // We should consider writing ItemKey for each of these - | Item.ArgName _ -> () + | Item.OtherName _ -> () | Item.FakeInterfaceCtor _ -> () | Item.CustomOperation _ -> () | Item.CustomBuilder _ -> () diff --git a/src/Compiler/Service/SemanticClassification.fs b/src/Compiler/Service/SemanticClassification.fs index f1fdc92c0bb..b9576972cfa 100644 --- a/src/Compiler/Service/SemanticClassification.fs +++ b/src/Compiler/Service/SemanticClassification.fs @@ -352,7 +352,7 @@ module TcResolutionsExtensions = | Item.Event _, _, m -> add m SemanticClassificationType.Event - | Item.ArgName _, _, m -> add m SemanticClassificationType.NamedArgument + | Item.OtherName _, _, m -> add m SemanticClassificationType.NamedArgument | Item.SetterArg _, _, m -> add m SemanticClassificationType.NamedArgument diff --git a/src/Compiler/Service/ServiceDeclarationLists.fs b/src/Compiler/Service/ServiceDeclarationLists.fs index c6546c4f021..a0da4745902 100644 --- a/src/Compiler/Service/ServiceDeclarationLists.fs +++ b/src/Compiler/Service/ServiceDeclarationLists.fs @@ -472,7 +472,7 @@ module DeclarationListHelpers = ToolTipElement.Single (layout, FSharpXmlDoc.None, ?symbol = symbol) // Named parameters - | Item.ArgName (Some id, argTy, _, _) -> + | Item.OtherName (ident = Some id; argType = argTy) -> let argTy, _ = PrettyTypes.PrettifyType g argTy let layout = wordL (tagText (FSComp.SR.typeInfoArgument())) ^^ @@ -486,7 +486,7 @@ module DeclarationListHelpers = | Item.SetterArg (_, item) -> FormatItemDescriptionToToolTipElement displayFullName infoReader ad m denv (ItemWithNoInst item) symbol width - | Item.ArgName (None, _, _, _) + | Item.OtherName (ident = None) // TODO: give a decent tooltip for implicit operators that include the resolution of the operator // @@ -871,7 +871,7 @@ module internal DescriptionListsImpl = | Item.NewDef _ | Item.ModuleOrNamespaces _ | Item.ImplicitOp _ - | Item.ArgName _ + | Item.OtherName _ | Item.MethodGroup(_, [], _) | Item.CtorGroup(_,[]) | Item.Property(_,[]) -> @@ -967,7 +967,7 @@ module internal DescriptionListsImpl = | Item.ModuleOrNamespaces(modref :: _) -> if modref.IsNamespace then FSharpGlyph.NameSpace else FSharpGlyph.Module | Item.NewDef _ - | Item.ArgName _ + | Item.OtherName _ | Item.SetterArg _ -> FSharpGlyph.Variable // These empty lists are not expected to occur @@ -1014,7 +1014,7 @@ module internal DescriptionListsImpl = | Item.CustomBuilder _ | Item.ActivePatternCase _ | Item.AnonRecdField _ - | Item.ArgName _ + | Item.OtherName _ | Item.ImplicitOp _ | Item.ModuleOrNamespaces _ | Item.SetterArg _ diff --git a/src/Compiler/Symbols/SymbolHelpers.fs b/src/Compiler/Symbols/SymbolHelpers.fs index 5f028e020b6..67161c99f0f 100644 --- a/src/Compiler/Symbols/SymbolHelpers.fs +++ b/src/Compiler/Symbols/SymbolHelpers.fs @@ -109,7 +109,7 @@ module internal SymbolHelpers = | Item.CtorGroup(_, minfos) -> minfos |> List.tryPick (rangeOfMethInfo g preferFlag) | Item.ActivePatternResult(APInfo _, _, _, m) -> Some m | Item.SetterArg (_, item) -> rangeOfItem g preferFlag item - | Item.ArgName (_, _, _, m) -> Some m + | Item.OtherName (range = m) -> Some m | Item.CustomOperation (_, _, implOpt) -> implOpt |> Option.bind (rangeOfMethInfo g preferFlag) | Item.ImplicitOp (_, {contents = Some(TraitConstraintSln.FSMethSln(vref=vref))}) -> Some vref.Range | Item.ImplicitOp _ -> None @@ -168,7 +168,7 @@ module internal SymbolHelpers = |> Option.bind ccuOfValRef |> Option.orElseWith (fun () -> pinfo.DeclaringTyconRef |> computeCcuOfTyconRef)) - | Item.ArgName (_, _, meth, _) -> + | Item.OtherName (container = meth) -> match meth with | None -> None | Some (ArgumentContainer.Method minfo) -> ccuOfMethInfo g minfo @@ -309,7 +309,7 @@ module internal SymbolHelpers = | Item.CtorGroup(_, minfo :: _) -> mkXmlComment (GetXmlDocSigOfMethInfo infoReader m minfo) - | Item.ArgName(_, _, Some argContainer, _) -> + | Item.OtherName(container = Some argContainer) -> match argContainer with | ArgumentContainer.Method minfo -> mkXmlComment (GetXmlDocSigOfMethInfo infoReader m minfo) | ArgumentContainer.Type tcref -> mkXmlComment (GetXmlDocSigOfEntityRef infoReader m tcref) @@ -322,7 +322,7 @@ module internal SymbolHelpers = // These do not have entires in XML doc files | Item.CustomOperation _ - | Item.ArgName _ + | Item.OtherName _ | Item.ActivePatternResult _ | Item.AnonRecdField _ | Item.ImplicitOp _ @@ -393,7 +393,7 @@ module internal SymbolHelpers = // These are never expected to have duplicates in declaration lists etc | Item.ActivePatternResult _ | Item.AnonRecdField _ - | Item.ArgName _ + | Item.OtherName _ | Item.FakeInterfaceCtor _ | Item.ImplicitOp _ | Item.NewDef _ @@ -499,7 +499,7 @@ module internal SymbolHelpers = // These are not expected to occur, see InEqualityRelation and ItemWhereTypIsPreferred | Item.ActivePatternResult _ | Item.AnonRecdField _ - | Item.ArgName _ + | Item.OtherName _ | Item.FakeInterfaceCtor _ | Item.ImplicitOp _ | Item.NewDef _ @@ -578,7 +578,7 @@ module internal SymbolHelpers = let definiteNamespace = modrefs |> List.forall (fun modref -> modref.IsNamespace) if definiteNamespace then fullDisplayTextOfModRef modref else modref.DisplayName | Item.TypeVar _ - | Item.ArgName _ -> item.DisplayName + | Item.OtherName _ -> item.DisplayName | Item.SetterArg (_, item) -> FullNameOfItem g item | Item.ImplicitOp(id, _) -> id.idText | Item.UnionCaseField (UnionCaseInfo (_, ucref), fieldIndex) -> ucref.FieldByIndex(fieldIndex).DisplayName @@ -678,7 +678,7 @@ module internal SymbolHelpers = else GetXmlCommentForItemAux None infoReader m item - | Item.ArgName (_, _, argContainer, _) -> + | Item.OtherName (container = argContainer) -> let doc = match argContainer with | Some(ArgumentContainer.Method minfo) -> @@ -934,7 +934,7 @@ module internal SymbolHelpers = | Item.MethodGroup(_, [], _) | Item.CustomOperation (_, _, None) // "into" | Item.NewDef _ // "let x$yz = ..." - no keyword - | Item.ArgName _ // no keyword on named parameters + | Item.OtherName _ // no keyword on named parameters | Item.Trait _ | Item.UnionCaseField _ | Item.TypeVar _ @@ -978,7 +978,7 @@ module internal SymbolHelpers = | Item.CustomBuilder _ | Item.ActivePatternCase _ | Item.AnonRecdField _ - | Item.ArgName _ + | Item.OtherName _ | Item.ImplicitOp _ | Item.ModuleOrNamespaces _ | Item.SetterArg _ diff --git a/src/Compiler/Symbols/Symbols.fs b/src/Compiler/Symbols/Symbols.fs index 820098ca01d..77591803e16 100644 --- a/src/Compiler/Symbols/Symbols.fs +++ b/src/Compiler/Symbols/Symbols.fs @@ -322,7 +322,7 @@ type FSharpSymbol(cenv: SymbolEnv, item: unit -> Item, access: FSharpSymbol -> C | Item.ActivePatternResult (apinfo, ty, n, _) -> FSharpActivePatternCase(cenv, apinfo, ty, n, None, item) :> _ - | Item.ArgName(id, ty, argOwner, m) -> + | Item.OtherName(id, ty, _, argOwner, m) -> FSharpParameter(cenv, id, ty, argOwner, m) :> _ | Item.ImplicitOp(_, { contents = Some(TraitConstraintSln.FSMethSln(vref=vref)) }) -> @@ -2072,7 +2072,7 @@ type FSharpMemberOrFunctionOrValue(cenv, d:FSharpMemberOrValData, item) = [ [ for ParamData(isParamArrayArg, isInArg, isOutArg, optArgInfo, _callerInfo, nmOpt, _reflArgInfo, pty) in p.GetParamDatas(cenv.amap, range0) do // INCOMPLETENESS: Attribs is empty here, so we can't look at attributes for // either .NET or F# parameters - let argInfo: ArgReprInfo = { Name=nmOpt; Attribs= [] } + let argInfo: ArgReprInfo = { Name=nmOpt; Attribs=[]; OtherRange=None } let m = match nmOpt with | Some v -> v.idRange @@ -2091,7 +2091,7 @@ type FSharpMemberOrFunctionOrValue(cenv, d:FSharpMemberOrValData, item) = [ for ParamData(isParamArrayArg, isInArg, isOutArg, optArgInfo, _callerInfo, nmOpt, _reflArgInfo, pty) in argTys do // INCOMPLETENESS: Attribs is empty here, so we can't look at attributes for // either .NET or F# parameters - let argInfo: ArgReprInfo = { Name=nmOpt; Attribs= [] } + let argInfo: ArgReprInfo = { Name=nmOpt; Attribs=[]; OtherRange=None } let m = match nmOpt with | Some v -> v.idRange @@ -2368,7 +2368,7 @@ type FSharpMemberOrFunctionOrValue(cenv, d:FSharpMemberOrValData, item) = let nm = String.uncapitalize witnessInfo.MemberName let nm = if used.Contains nm then nm + string i else nm let m = x.DeclarationLocation - let argReprInfo : ArgReprInfo = { Attribs=[]; Name=Some (mkSynId m nm) } + let argReprInfo : ArgReprInfo = { Attribs=[]; Name=Some (mkSynId m nm); OtherRange=None } let p = FSharpParameter(cenv, paramTy, argReprInfo, None, m, false, false, false, false, true) p, (used.Add nm, i + 1)) |> fst @@ -2696,7 +2696,7 @@ type FSharpStaticParameter(cenv, sp: Tainted< TypeProviders.ProvidedParameterInf let paramTy = Import.ImportProvidedType cenv.amap m (sp.PApply((fun x -> x.ParameterType), m)) let nm = sp.PUntaint((fun p -> p.Name), m) let id = mkSynId m nm - Item.ArgName(Some id, paramTy, None, m)), + Item.OtherName(Some id, paramTy, None, None, m)), (fun _ _ _ -> true)) member _.Name = @@ -2734,11 +2734,11 @@ type FSharpStaticParameter(cenv, sp: Tainted< TypeProviders.ProvidedParameterInf type FSharpParameter(cenv, paramTy: TType, topArgInfo: ArgReprInfo, ownerOpt, m: range, isParamArrayArg, isInArg, isOutArg, isOptionalArg, isWitnessArg) = inherit FSharpSymbol(cenv, - (fun () -> Item.ArgName(topArgInfo.Name, paramTy, ownerOpt, m)), + (fun () -> Item.OtherName(topArgInfo.Name, paramTy, Some topArgInfo, ownerOpt, m)), (fun _ _ _ -> true)) new (cenv, idOpt, ty, ownerOpt, m) = - let argInfo: ArgReprInfo = { Name = idOpt; Attribs = [] } + let argInfo: ArgReprInfo = { Name = idOpt; Attribs = []; OtherRange = None } FSharpParameter(cenv, ty, argInfo, ownerOpt, m, false, false, false, false, false) new (cenv, ty, argInfo: ArgReprInfo, m: range) = diff --git a/src/Compiler/TypedTree/TypedTree.fs b/src/Compiler/TypedTree/TypedTree.fs index 3bfe81677ea..b18cdd1d2a9 100644 --- a/src/Compiler/TypedTree/TypedTree.fs +++ b/src/Compiler/TypedTree/TypedTree.fs @@ -1930,7 +1930,7 @@ type ExceptionInfo = override x.ToString() = sprintf "%+A" x -/// Represents the contents of of a module of namespace +/// Represents the contents of a module or namespace [] type ModuleOrNamespaceType(kind: ModuleOrNamespaceKind, vals: QueueList, entities: QueueList) = @@ -2578,6 +2578,10 @@ type ValOptionalData = /// that may be compiled as closures (that is are not necessarily compiled as top-level methods). mutable val_repr_info_for_display: ValReprInfo option + /// Records the "extra information" for parameters in implementation files if we've been able to correlate + /// them with lambda arguments. + mutable arg_repr_info_for_display: ArgReprInfo option + /// How visible is this? /// MUTABILITY: for unpickle linkage mutable val_access: Accessibility @@ -2641,6 +2645,7 @@ type Val = val_defn = None val_repr_info = None val_repr_info_for_display = None + arg_repr_info_for_display = None val_access = TAccess [] val_xmldoc = XmlDoc.Empty val_other_xmldoc = None @@ -2656,8 +2661,9 @@ type Val = | _ -> x.val_range /// Range of the definition (signature) of the value, used by Visual Studio - member x.SigRange = + member x.SigRange = match x.val_opt_data with + | Some { arg_repr_info_for_display = Some { OtherRange = Some m } } -> m | Some { val_other_range = Some(m, false) } -> m | _ -> x.val_range @@ -2711,6 +2717,11 @@ type Val = | Some optData -> optData.val_repr_info_for_display | _ -> None + member x.ArgReprInfoForDisplay: ArgReprInfo option = + match x.val_opt_data with + | Some optData -> optData.arg_repr_info_for_display + | _ -> None + member x.Id = ident(x.LogicalName, x.Range) /// Is this represented as a "top level" static binding (i.e. a static field, static member, @@ -3107,6 +3118,11 @@ type Val = | Some optData -> optData.val_repr_info_for_display <- info | _ -> x.val_opt_data <- Some { Val.NewEmptyValOptData() with val_repr_info_for_display = info } + member x.SetArgReprInfoForDisplay info = + match x.val_opt_data with + | Some optData -> optData.arg_repr_info_for_display <- info + | _ -> x.val_opt_data <- Some { Val.NewEmptyValOptData() with arg_repr_info_for_display = info } + member x.SetType ty = x.val_type <- ty member x.SetOtherRange m = @@ -3170,6 +3186,7 @@ type Val = val_const = tg.val_const val_defn = tg.val_defn val_repr_info_for_display = tg.val_repr_info_for_display + arg_repr_info_for_display = tg.arg_repr_info_for_display val_repr_info = tg.val_repr_info val_access = tg.val_access val_xmldoc = tg.val_xmldoc @@ -4665,8 +4682,8 @@ type ValReprInfo = /// Records the "extra information" for an argument compiled as a real /// method argument, specifically the argument name and attributes. [] -type ArgReprInfo = - { +type ArgReprInfo = + { /// The attributes for the argument // MUTABILITY: used when propagating signature attributes into the implementation. mutable Attribs: Attribs @@ -4674,6 +4691,10 @@ type ArgReprInfo = /// The name for the argument at this position, if any // MUTABILITY: used when propagating names of parameters from signature into the implementation. mutable Name: Ident option + + /// The range of the signature/implementation counterpart to this argument, if any + // MUTABILITY: used when propagating ranges from signature into the implementation. + mutable OtherRange: range option } [] diff --git a/src/Compiler/TypedTree/TypedTree.fsi b/src/Compiler/TypedTree/TypedTree.fsi index 3105d4e078e..bcc951ecc4d 100644 --- a/src/Compiler/TypedTree/TypedTree.fsi +++ b/src/Compiler/TypedTree/TypedTree.fsi @@ -1829,6 +1829,10 @@ type ValOptionalData = /// that may be compiled as closures (that is are not necessarily compiled as top-level methods). mutable val_repr_info_for_display: ValReprInfo option + /// Records the "extra information" for parameters in implementation files if we've been able to correlate + /// them with lambda arguments. + mutable arg_repr_info_for_display: ArgReprInfo option + /// How visible is this? /// MUTABILITY: for unpickle linkage mutable val_access: Accessibility @@ -1945,6 +1949,8 @@ type Val = member SetValReprInfoForDisplay: info: ValReprInfo option -> unit + member SetArgReprInfoForDisplay: info: ArgReprInfo option -> unit + override ToString: unit -> string /// How visible is this value, function or member? @@ -2206,6 +2212,10 @@ type Val = /// that may be compiled as closures (that is are not necessarily compiled as top-level methods). member ValReprInfoForDisplay: ValReprInfo option + /// Records the "extra information" for parameters in implementation files if we've been able to correlate + /// them with lambda arguments. + member ArgReprInfoForDisplay: ArgReprInfo option + /// Get the declared documentation for the value member XmlDoc: XmlDoc @@ -3397,6 +3407,9 @@ type ArgReprInfo = /// The name for the argument at this position, if any mutable Name: Syntax.Ident option + + /// The range of the signature/implementation counterpart to this argument, if any + mutable OtherRange: range option } override ToString: unit -> string diff --git a/src/Compiler/TypedTree/TypedTreeBasics.fs b/src/Compiler/TypedTree/TypedTreeBasics.fs index 511a4cc44f2..651eadd0d65 100644 --- a/src/Compiler/TypedTree/TypedTreeBasics.fs +++ b/src/Compiler/TypedTree/TypedTreeBasics.fs @@ -29,13 +29,13 @@ let getNameOfScopeRef sref = /// Metadata on values (names of arguments etc.) module ValReprInfo = - let unnamedTopArg1: ArgReprInfo = { Attribs=[]; Name=None } + let unnamedTopArg1: ArgReprInfo = { Attribs = []; Name = None; OtherRange = None } let unnamedTopArg = [unnamedTopArg1] let unitArgData: ArgReprInfo list list = [[]] - let unnamedRetVal: ArgReprInfo = { Attribs = []; Name=None } + let unnamedRetVal: ArgReprInfo = { Attribs = []; Name = None; OtherRange = None } let selfMetadata = unnamedTopArg @@ -43,12 +43,12 @@ module ValReprInfo = let IsEmpty info = match info with - | ValReprInfo([], [], { Attribs = []; Name=None }) -> true + | ValReprInfo([], [], { Attribs = []; Name = None; OtherRange = None }) -> true | _ -> false let InferTyparInfo (tps: Typar list) = tps |> List.map (fun tp -> TyparReprInfo(tp.Id, tp.Kind)) - let InferArgReprInfo (v: Val) : ArgReprInfo = { Attribs = []; Name= Some v.Id } + let InferArgReprInfo (v: Val) : ArgReprInfo = { Attribs = []; Name = Some v.Id; OtherRange = None } let InferArgReprInfos (vs: Val list list) = ValReprInfo([], List.mapSquared InferArgReprInfo vs, unnamedRetVal) diff --git a/src/Compiler/TypedTree/TypedTreeOps.fs b/src/Compiler/TypedTree/TypedTreeOps.fs index 1bc7cdd4c99..702f7292429 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.fs @@ -5451,9 +5451,9 @@ let InferValReprInfoOfExpr g allowTypeDirectedDetupling ty partialArgAttribsL re let attribs = if partialAttribs.Length = tys.Length then partialAttribs else tys |> List.map (fun _ -> []) - (ids, attribs) ||> List.map2 (fun id attribs -> { Name = id; Attribs = attribs }: ArgReprInfo )) + (ids, attribs) ||> List.map2 (fun id attribs -> { Name = id; Attribs = attribs; OtherRange = None }: ArgReprInfo )) - let retInfo: ArgReprInfo = { Attribs = retAttribs; Name = None } + let retInfo: ArgReprInfo = { Attribs = retAttribs; Name = None; OtherRange = None } let info = ValReprInfo (ValReprInfo.InferTyparInfo tps, curriedArgInfos, retInfo) if ValReprInfo.IsEmpty info then ValReprInfo.emptyValData else info @@ -5644,7 +5644,7 @@ and remapPossibleForallTyImpl ctxt tmenv ty = remapTypeFull (remapAttribs ctxt tmenv) tmenv ty and remapArgData ctxt tmenv (argInfo: ArgReprInfo) : ArgReprInfo = - { Attribs = remapAttribs ctxt tmenv argInfo.Attribs; Name = argInfo.Name } + { Attribs = remapAttribs ctxt tmenv argInfo.Attribs; Name = argInfo.Name; OtherRange = argInfo.OtherRange } and remapValReprInfo ctxt tmenv (ValReprInfo(tpNames, arginfosl, retInfo)) = ValReprInfo(tpNames, List.mapSquared (remapArgData ctxt tmenv) arginfosl, remapArgData ctxt tmenv retInfo) diff --git a/src/Compiler/TypedTree/TypedTreePickle.fs b/src/Compiler/TypedTree/TypedTreePickle.fs index 2e14d3ca3be..c2d5a44bfd4 100644 --- a/src/Compiler/TypedTree/TypedTreePickle.fs +++ b/src/Compiler/TypedTree/TypedTreePickle.fs @@ -1752,7 +1752,7 @@ let u_ArgReprInfo st = let b = u_option u_ident st match a, b with | [], None -> ValReprInfo.unnamedTopArg1 - | _ -> { Attribs = a; Name = b } + | _ -> { Attribs = a; Name = b; OtherRange = None } let u_TyparReprInfo st = let a = u_ident st @@ -2263,6 +2263,7 @@ and u_ValData st = val_defn = None val_repr_info = x10 val_repr_info_for_display = None + arg_repr_info_for_display = None val_const = x14 val_access = x13 val_xmldoc = defaultArg x15 XmlDoc.Empty diff --git a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/FindReferences.fs b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/FindReferences.fs index a760dc6f953..a17d83d7212 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/FindReferences.fs +++ b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/FindReferences.fs @@ -152,6 +152,65 @@ let foo x = x ++ 4""" }) ]) } +module Parameters = + + [] + let ``We find function parameter in signature file`` () = + let source = """let f param = param + 1""" + let signature = """val f: param:int -> int""" + SyntheticProject.Create( + { sourceFile "Source" [] with Source = source; SignatureFile = Custom signature }) + .Workflow { + placeCursor "Source" "param" + findAllReferences (expectToFind [ + "FileSource.fsi", 2, 7, 12 + "FileSource.fs", 2, 6, 11 + "FileSource.fs", 2, 14, 19 + ]) + } + + [] + let ``We find method parameter in signature file`` () = + SyntheticProject.Create( + { sourceFile "Source" [] with + Source = "type MyClass() = member this.Method(methodParam) = methodParam + 1" + SignatureFile = AutoGenerated }) + .Workflow { + // Some race condition probably triggered by auto-generating signatures makes this + // flaky in CI compressed metadata builds. Clearing the cache before we start fixes it ¯\_(ツ)_/¯ + clearCache + placeCursor "Source" "methodParam" + findAllReferences (expectToFind [ + "FileSource.fsi", 8, 17, 28 + "FileSource.fs", 2, 36, 47 + "FileSource.fs", 2, 51, 62 + ]) + } + + [] + let ``We only find the correct parameter`` () = + let source = """ +let myFunc1 param = param + 1 +let myFunc2 param = param + 2 +""" + let signature = """ +val myFunc1: param: int -> int +val myFunc2: param: int -> int +""" + SyntheticProject.Create("TupleParameterTest", + { sourceFile "Source" [] with + ExtraSource = source + SignatureFile = Custom signature }) + .Workflow { + checkFile "Source" expectOk + placeCursor "Source" 7 17 "let myFunc1 param = param + 1" ["param"] + findAllReferences (expectToFind [ + "FileSource.fsi", 3, 13, 18 + "FileSource.fs", 7, 12, 17 + "FileSource.fs", 7, 20, 25 + ]) + } + module Attributes = let project() = SyntheticProject.Create( diff --git a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/SymbolUse.fs b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/SymbolUse.fs index 103ca2be73c..720dc0b1271 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/SymbolUse.fs +++ b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/SymbolUse.fs @@ -1,69 +1,88 @@ module FSharp.Compiler.ComponentTests.FSharpChecker.SymbolUse + open FSharp.Compiler.CodeAnalysis open Xunit open FSharp.Test.ProjectGeneration +open FSharp.Compiler.Symbols + +open FSharp.Compiler.EditorServices +open FSharp.Compiler.NameResolution module IsPrivateToFile = + let functionParameter = "param" + let source = $""" + let f x = x + 1 + let f2 {functionParameter} = {functionParameter} + 1 + """ + let testFile = { sourceFile "Test" [] with Source = source } + + let signature = $""" + val f: x:int -> int + val f2: {functionParameter}: int -> int + """ + + let testFileWithSignature = { testFile with SignatureFile = Custom signature } + [] let ``Function definition in signature file`` () = let project = SyntheticProject.Create( - sourceFile "First" [] |> addSignatureFile, - sourceFile "Second" ["First"]) + testFileWithSignature, + sourceFile "Second" [testFile.Id]) project.Workflow { - checkFile "First" (fun (typeCheckResult: FSharpCheckFileResults) -> - let symbolUse = typeCheckResult.GetSymbolUseAtLocation(5, 6, "let f2 x = x + 1", ["f2"]) |> Option.defaultWith (fun () -> failwith "no symbol use found") - Assert.False(symbolUse.IsPrivateToFile)) + checkSymbolUse testFile.Id "f2" (fun symbolUse -> + Assert.False(symbolUse.IsPrivateToFile) + Assert.False(symbolUse.IsPrivateToFileAndSignatureFile)) } [] let ``Function definition, no signature file`` () = let project = SyntheticProject.Create( - sourceFile "First" [], - sourceFile "Second" ["First"]) + testFile, + sourceFile "Second" [testFile.Id]) project.Workflow { - checkFile "First" (fun (typeCheckResult: FSharpCheckFileResults) -> - let symbolUse = typeCheckResult.GetSymbolUseAtLocation(5, 6, "let f2 x = x + 1", ["f2"]) |> Option.defaultWith (fun () -> failwith "no symbol use found") - Assert.False(symbolUse.IsPrivateToFile)) + checkSymbolUse testFile.Id "f2" (fun symbolUse -> + Assert.False(symbolUse.IsPrivateToFile) + Assert.False(symbolUse.IsPrivateToFileAndSignatureFile)) } [] let ``Function definition not in signature file`` () = - let signature = $""" -type TFirstV_1<'a> = | TFirst of 'a -val f: x: 'a -> TFirstV_1<'a> -// no f2 here -""" + let signature = "val f: x: int -> int" let project = SyntheticProject.Create( - { sourceFile "First" [] with SignatureFile = Custom signature }, - sourceFile "Second" ["First"]) + { testFile with SignatureFile = Custom signature }, + sourceFile "Second" [testFile.Id]) project.Workflow { - checkFile "First" (fun (typeCheckResult: FSharpCheckFileResults) -> - let symbolUse = typeCheckResult.GetSymbolUseAtLocation(5, 6, "let f2 x = x + 1", ["f2"]) |> Option.defaultWith (fun () -> failwith "no symbol use found") - Assert.True(symbolUse.IsPrivateToFile)) + checkSymbolUse testFile.Id "f2" (fun symbolUse -> + Assert.True(symbolUse.IsPrivateToFile) + Assert.False(symbolUse.IsPrivateToFileAndSignatureFile)) } [] let ``Function parameter, no signature file`` () = - SyntheticProject.Create(sourceFile "First" []).Workflow { - checkFile "First" (fun (typeCheckResult: FSharpCheckFileResults) -> - let symbolUse = typeCheckResult.GetSymbolUseAtLocation(5, 8, "let f2 x = x + 1", ["x"]) |> Option.defaultWith (fun () -> failwith "no symbol use found") - Assert.True(symbolUse.IsPrivateToFile)) + SyntheticProject.Create(testFile).Workflow { + checkSymbolUse testFile.Id functionParameter (fun symbolUse -> + Assert.True(symbolUse.IsPrivateToFile) + Assert.False(symbolUse.IsPrivateToFileAndSignatureFile)) } - /// This is a bug: https://github.com/dotnet/fsharp/issues/14277 [] - let ``Function parameter, with signature file`` () = - SyntheticProject.Create(sourceFile "First" [] |> addSignatureFile).Workflow { - checkFile "First" (fun (typeCheckResult: FSharpCheckFileResults) -> - let symbolUse = typeCheckResult.GetSymbolUseAtLocation(5, 8, "let f2 x = x + 1", ["x"]) |> Option.defaultWith (fun () -> failwith "no symbol use found") - // This should be false, because it's also in the signature file - Assert.True(symbolUse.IsPrivateToFile)) + let ``Function parameter, with signature file, part 1`` () = + SyntheticProject.Create(testFileWithSignature).Workflow { + checkSymbolUse testFile.Id functionParameter (fun symbolUse -> + Assert.False(symbolUse.IsPrivateToFile)) + } + + [] + let ``Function parameter, with signature file, part 2`` () = + SyntheticProject.Create(testFileWithSignature).Workflow { + checkSymbolUse testFile.Id functionParameter (fun symbolUse -> + Assert.True(symbolUse.IsPrivateToFileAndSignatureFile)) } // [] This is a bug - https://github.com/dotnet/fsharp/issues/14419 diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl index 74021c8c9ac..0b36e1a5132 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl @@ -2182,6 +2182,7 @@ FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean IsFromPattern FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean IsFromType FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean IsFromUse FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean IsPrivateToFile +FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean IsPrivateToFileAndSignatureFile FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean get_IsFromAttribute() FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean get_IsFromComputationExpression() FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean get_IsFromDefinition() @@ -2191,6 +2192,7 @@ FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean get_IsFromPattern() FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean get_IsFromType() FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean get_IsFromUse() FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean get_IsPrivateToFile() +FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean get_IsPrivateToFileAndSignatureFile() FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: FSharp.Compiler.Symbols.FSharpDisplayContext DisplayContext FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: FSharp.Compiler.Symbols.FSharpDisplayContext get_DisplayContext() FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: FSharp.Compiler.Symbols.FSharpSymbol Symbol diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl index 3e44cfa67f0..8aaf99986ee 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl @@ -2182,6 +2182,7 @@ FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean IsFromPattern FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean IsFromType FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean IsFromUse FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean IsPrivateToFile +FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean IsPrivateToFileAndSignatureFile FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean get_IsFromAttribute() FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean get_IsFromComputationExpression() FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean get_IsFromDefinition() @@ -2191,6 +2192,7 @@ FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean get_IsFromPattern() FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean get_IsFromType() FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean get_IsFromUse() FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean get_IsPrivateToFile() +FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: Boolean get_IsPrivateToFileAndSignatureFile() FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: FSharp.Compiler.Symbols.FSharpDisplayContext DisplayContext FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: FSharp.Compiler.Symbols.FSharpDisplayContext get_DisplayContext() FSharp.Compiler.CodeAnalysis.FSharpSymbolUse: FSharp.Compiler.Symbols.FSharpSymbol Symbol diff --git a/tests/FSharp.Test.Utilities/ProjectGeneration.fs b/tests/FSharp.Test.Utilities/ProjectGeneration.fs index 578a39aa17d..23b2315efee 100644 --- a/tests/FSharp.Test.Utilities/ProjectGeneration.fs +++ b/tests/FSharp.Test.Utilities/ProjectGeneration.fs @@ -440,10 +440,17 @@ module Helpers = let getSymbolUse fileName (source: string) (symbolName: string) options (checker: FSharpChecker) = async { - let index = source.IndexOf symbolName - let line = source |> Seq.take index |> Seq.where ((=) '\n') |> Seq.length - let fullLine = source.Split '\n' |> Array.item line - let colAtEndOfNames = fullLine.IndexOf symbolName + symbolName.Length + let lines = source.Split '\n' |> Seq.skip 1 // module definition + let lineNumber, fullLine, colAtEndOfNames = + lines + |> Seq.mapi (fun lineNumber line -> + let index = line.IndexOf symbolName + if index >= 0 then + let colAtEndOfNames = line.IndexOf symbolName + symbolName.Length + Some (lineNumber + 2, line, colAtEndOfNames) + else None) + |> Seq.tryPick id + |> Option.defaultValue (-1, "", -1) let! results = checker.ParseAndCheckFileInProject( fileName, 0, SourceText.ofString source, options) @@ -451,10 +458,10 @@ module Helpers = let typeCheckResults = getTypeCheckResult results let symbolUse = - typeCheckResults.GetSymbolUseAtLocation(line + 1, colAtEndOfNames, fullLine, [symbolName]) + typeCheckResults.GetSymbolUseAtLocation(lineNumber, colAtEndOfNames, fullLine, [symbolName]) return symbolUse |> Option.defaultWith (fun () -> - failwith $"No symbol found in {fileName} at {line}:{colAtEndOfNames}\nFile contents:\n\n{source}\n") + failwith $"No symbol found in {fileName} at {lineNumber}:{colAtEndOfNames}\nFile contents:\n\n{source}\n") } let singleFileChecker source = @@ -700,19 +707,33 @@ type ProjectWorkflowBuilder return { ctx with Cursor = su } } - /// Find a symbol by finding the first occurrence of the symbol name in the file - [] - member this.PlaceCursor(workflow: Async, fileId, symbolName: string) = + member this.FindSymbolUse(ctx: WorkflowContext, fileId, symbolName: string) = async { - let! ctx = workflow let file = ctx.Project.Find fileId let fileName = ctx.Project.ProjectDir ++ file.FileName let source = renderSourceFile ctx.Project file let options= ctx.Project.GetProjectOptions checker - let! su = getSymbolUse fileName source symbolName options checker + return! getSymbolUse fileName source symbolName options checker + } + + /// Find a symbol by finding the first occurrence of the symbol name in the file + [] + member this.PlaceCursor(workflow: Async, fileId, symbolName: string) = + async { + let! ctx = workflow + let! su = this.FindSymbolUse(ctx, fileId, symbolName) return { ctx with Cursor = Some su } } + [] + member this.CheckSymbolUse(workflow: Async, fileId, symbolName: string, check) = + async { + let! ctx = workflow + let! su = this.FindSymbolUse(ctx, fileId, symbolName) + check su + return ctx + } + /// Find all references within a single file, results are provided to the 'processResults' function [] member this.FindAllReferencesInFile(workflow: Async, fileId: string, processResults) = @@ -776,6 +797,17 @@ type ProjectWorkflowBuilder return ctx } + /// Clear checker caches. + [] + member this.ClearCache(workflow: Async) = + async { + let! ctx = workflow + let options = [for p in ctx.Project.GetAllProjects() -> p.GetProjectOptions checker] + checker.ClearCache(options) + checker.ClearLanguageServiceRootCachesAndCollectAndFinalizeAllTransients() + return ctx + } + /// Find all references to a module defined in a given file. /// These should only be found in files that depend on this file. /// diff --git a/vsintegration/src/FSharp.Editor/Common/Pervasive.fs b/vsintegration/src/FSharp.Editor/Common/Pervasive.fs index c99ae5b4b68..18e5c463c72 100644 --- a/vsintegration/src/FSharp.Editor/Common/Pervasive.fs +++ b/vsintegration/src/FSharp.Editor/Common/Pervasive.fs @@ -9,6 +9,13 @@ open System.Diagnostics let isSignatureFile (filePath: string) = String.Equals(Path.GetExtension filePath, ".fsi", StringComparison.OrdinalIgnoreCase) +/// Returns the corresponding signature file path for given implementation file path or vice versa +let getOtherFile (filePath: string) = + if isSignatureFile filePath then + Path.ChangeExtension(filePath, ".fs") + else + Path.ChangeExtension(filePath, ".fsi") + /// Checks if the file paht ends with '.fsx' or '.fsscript' let isScriptFile (filePath: string) = let ext = Path.GetExtension filePath diff --git a/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs b/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs index 0396d4b334f..9bc946a9cbf 100644 --- a/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs +++ b/vsintegration/src/FSharp.Editor/InlineRename/InlineRenameService.fs @@ -78,7 +78,6 @@ type internal InlineRenameInfo triggerSpan: TextSpan, lexerSymbol: LexerSymbol, symbolUse: FSharpSymbolUse, - declLoc: SymbolDeclarationLocation, checkFileResults: FSharpCheckFileResults ) = @@ -89,8 +88,8 @@ type internal InlineRenameInfo | true, text -> text | _ -> document.GetTextAsync(cancellationToken).Result - let symbolUses ct = - SymbolHelpers.getSymbolUsesInSolution (symbolUse.Symbol, declLoc, checkFileResults, document.Project.Solution, ct) + let symbolUses = + SymbolHelpers.getSymbolUsesInSolution (symbolUse, checkFileResults, document) override _.CanRename = true override _.LocalizedErrorMessage = null @@ -127,7 +126,7 @@ type internal InlineRenameInfo override _.FindRenameLocationsAsync(_, _, cancellationToken) = async { - let! symbolUsesByDocumentId = symbolUses cancellationToken + let! symbolUsesByDocumentId = symbolUses let! locations = symbolUsesByDocumentId @@ -183,12 +182,10 @@ type internal InlineRenameService [] () = symbol.FullIsland ) - let! declLoc = symbolUse.GetDeclarationLocation(document) - let! span = RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.Range) let triggerSpan = Tokenizer.fixupSpan (sourceText, span) - return InlineRenameInfo(document, triggerSpan, symbol, symbolUse, declLoc, checkFileResults) :> FSharpInlineRenameInfo + return InlineRenameInfo(document, triggerSpan, symbol, symbolUse, checkFileResults) :> FSharpInlineRenameInfo } override _.GetRenameInfoAsync(document: Document, position: int, cancellationToken: CancellationToken) : Task = diff --git a/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs b/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs index 74918f6ec62..a96a5979baf 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/SymbolHelpers.fs @@ -14,7 +14,6 @@ open Microsoft.CodeAnalysis.Text open FSharp.Compiler.CodeAnalysis open FSharp.Compiler.Symbols open FSharp.Compiler.Text -open Microsoft.VisualStudio.FSharp.Editor.Symbols open Microsoft.VisualStudio.FSharp.Editor.Telemetry module internal SymbolHelpers = @@ -64,7 +63,7 @@ module internal SymbolHelpers = ( symbol: FSharpSymbol, projects: Project list, - onFound: Document -> TextSpan -> range -> Async, + onFound: Document -> range -> Async, ct: CancellationToken ) = match projects with @@ -87,52 +86,78 @@ module internal SymbolHelpers = TelemetryReporter.reportEvent "getSymbolUsesInProjectsFinished" props } - let getSymbolUsesInSolution - ( - symbol: FSharpSymbol, - declLoc: SymbolDeclarationLocation, - checkFileResults: FSharpCheckFileResults, - solution: Solution, - ct: CancellationToken - ) = + let findSymbolUses (symbolUse: FSharpSymbolUse) (currentDocument: Document) (checkFileResults: FSharpCheckFileResults) onFound = async { - let toDict (symbolUseRanges: range seq) = - let groups = - symbolUseRanges - |> Seq.collect (fun symbolUse -> - solution.GetDocumentIdsWithFilePath(symbolUse.FileName) - |> Seq.map (fun id -> id, symbolUse)) - |> Seq.groupBy fst - - groups.ToImmutableDictionary((fun (id, _) -> id), (fun (_, xs) -> xs |> Seq.map snd |> Seq.toArray)) - - match declLoc with - | SymbolDeclarationLocation.CurrentDocument -> - let! ct = Async.CancellationToken - let symbolUses = checkFileResults.GetUsesOfSymbolInFile(symbol, ct) - return toDict (symbolUses |> Seq.map (fun symbolUse -> symbolUse.Range)) - | SymbolDeclarationLocation.Projects (projects, isInternalToProject) -> - let symbolUseRanges = ConcurrentBag() - - let projects = - if isInternalToProject then - projects - else + match symbolUse.GetSymbolScope currentDocument with + + | Some SymbolScope.CurrentDocument -> + let symbolUses = checkFileResults.GetUsesOfSymbolInFile(symbolUse.Symbol) + + for symbolUse in symbolUses do + do! onFound currentDocument symbolUse.Range + + | Some SymbolScope.SignatureAndImplementation -> + let otherFile = getOtherFile currentDocument.FilePath + + let! otherFileCheckResults = + match currentDocument.Project.Solution.TryGetDocumentFromPath otherFile with + | Some doc -> + async { + let! _, checkFileResults = doc.GetFSharpParseAndCheckResultsAsync("findReferencedSymbolsAsync") + return [ checkFileResults, doc ] + } + | None -> async.Return [] + + let symbolUses = + (checkFileResults, currentDocument) :: otherFileCheckResults + |> Seq.collect (fun (checkFileResults, doc) -> + checkFileResults.GetUsesOfSymbolInFile(symbolUse.Symbol) + |> Seq.map (fun symbolUse -> (doc, symbolUse.Range))) + + for document, range in symbolUses do + do! onFound document range + + | scope -> + let projectsToCheck = + match scope with + | Some (SymbolScope.Projects (scopeProjects, false)) -> [ - for project in projects do - yield project - yield! project.GetDependentProjects() + for scopeProject in scopeProjects do + yield scopeProject + yield! scopeProject.GetDependentProjects() ] - |> List.distinctBy (fun x -> x.Id) + |> List.distinct + | Some (SymbolScope.Projects (scopeProjects, true)) -> scopeProjects + // The symbol is declared in .NET framework, an external assembly or in a C# project within the solution. + // In order to find all its usages we have to check all F# projects. + | _ -> Seq.toList currentDocument.Project.Solution.Projects + + let! ct = Async.CancellationToken + + do! + getSymbolUsesInProjects (symbolUse.Symbol, projectsToCheck, onFound, ct) + |> Async.AwaitTask + } + + let getSymbolUses (symbolUse: FSharpSymbolUse) (currentDocument: Document) (checkFileResults: FSharpCheckFileResults) = + async { + let symbolUses = ConcurrentBag() + let onFound = fun document range -> async { symbolUses.Add(document, range) } - let onFound = fun _ _ symbolUseRange -> async { symbolUseRanges.Add symbolUseRange } + do! findSymbolUses symbolUse currentDocument checkFileResults onFound + + return symbolUses |> seq + } + + let getSymbolUsesInSolution (symbolUse: FSharpSymbolUse, checkFileResults: FSharpCheckFileResults, document: Document) = + async { + let! symbolUses = getSymbolUses symbolUse document checkFileResults - do! getSymbolUsesInProjects (symbol, projects, onFound, ct) |> Async.AwaitTask + let symbolUsesWithDocumentId = + symbolUses |> Seq.map (fun (doc, range) -> doc.Id, range) - // Distinct these down because each TFM will produce a new 'project'. - // Unless guarded by a #if define, symbols with the same range will be added N times - let symbolUseRanges = symbolUseRanges |> Seq.distinct - return toDict symbolUseRanges + let usesByDocumentId = symbolUsesWithDocumentId |> Seq.groupBy fst + return usesByDocumentId.ToImmutableDictionary(fst, snd >> Seq.map snd >> Seq.toArray) } type OriginalText = string @@ -174,20 +199,12 @@ module internal SymbolHelpers = symbol.FullIsland ) - let! declLoc = symbolUse.GetDeclarationLocation(document) let newText = textChanger originalText // defer finding all symbol uses throughout the solution return Func<_, _>(fun (cancellationToken: CancellationToken) -> async { - let! symbolUsesByDocumentId = - getSymbolUsesInSolution ( - symbolUse.Symbol, - declLoc, - checkFileResults, - document.Project.Solution, - cancellationToken - ) + let! symbolUsesByDocumentId = getSymbolUsesInSolution (symbolUse, checkFileResults, document) let mutable solution = document.Project.Solution diff --git a/vsintegration/src/FSharp.Editor/LanguageService/Symbols.fs b/vsintegration/src/FSharp.Editor/LanguageService/Symbols.fs index 6f1c4586508..c5418d5903b 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/Symbols.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/Symbols.fs @@ -8,8 +8,9 @@ open FSharp.Compiler.EditorServices open FSharp.Compiler.Symbols [] -type SymbolDeclarationLocation = +type SymbolScope = | CurrentDocument + | SignatureAndImplementation | Projects of Project list * isLocalForProject: bool [] @@ -34,9 +35,11 @@ type FSharpSymbol with type FSharpSymbolUse with - member this.GetDeclarationLocation(currentDocument: Document) : SymbolDeclarationLocation option = + member this.GetSymbolScope(currentDocument: Document) : SymbolScope option = if this.IsPrivateToFile then - Some SymbolDeclarationLocation.CurrentDocument + Some SymbolScope.CurrentDocument + elif this.IsPrivateToFileAndSignatureFile then + Some SymbolScope.SignatureAndImplementation else let isSymbolLocalForProject = this.Symbol.IsInternalToProject @@ -51,12 +54,12 @@ type FSharpSymbolUse with let isScript = isScriptFile filePath if isScript && filePath = currentDocument.FilePath then - Some SymbolDeclarationLocation.CurrentDocument + Some SymbolScope.CurrentDocument elif isScript then // The standalone script might include other files via '#load' // These files appear in project options and the standalone file // should be treated as an individual project - Some(SymbolDeclarationLocation.Projects([ currentDocument.Project ], isSymbolLocalForProject)) + Some(SymbolScope.Projects([ currentDocument.Project ], isSymbolLocalForProject)) else let projects = currentDocument.Project.Solution.GetDocumentIdsWithFilePath(filePath) @@ -67,7 +70,7 @@ type FSharpSymbolUse with match projects with | [] -> None - | projects -> Some(SymbolDeclarationLocation.Projects(projects, isSymbolLocalForProject)) + | projects -> Some(SymbolScope.Projects(projects, isSymbolLocalForProject)) | None -> None type FSharpEntity with diff --git a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs index fd362416c3a..3538e8bbd69 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/WorkspaceExtensions.fs @@ -231,13 +231,8 @@ type Document with fastCheck = this.Project.IsFastFindReferencesEnabled ) - let! ct = Async.CancellationToken - let! sourceText = this.GetTextAsync ct |> Async.AwaitTask - for symbolUse in symbolUses do - match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse) with - | Some textSpan -> do! onFound textSpan symbolUse - | _ -> () + do! onFound symbolUse } /// Try to find a F# lexer/token symbol of the given F# document and position. @@ -283,7 +278,17 @@ type Project with document.GetFSharpCompilationOptionsAsync(userOpName) |> RoslynHelpers.StartAsyncAsTask ct - return options.SourceFiles |> Seq.takeWhile ((<>) document.FilePath) |> Set + let signatureFile = + if not (document.FilePath |> isSignatureFile) then + $"{document.FilePath}i" + else + null + + return + options.SourceFiles + |> Seq.takeWhile ((<>) document.FilePath) + |> Seq.filter ((<>) signatureFile) + |> Set } | _ -> Task.FromResult Set.empty @@ -296,13 +301,13 @@ type Project with documents |> Seq.map (fun doc -> Task.Run(fun () -> - doc.FindFSharpReferencesAsync(symbol, (fun textSpan range -> onFound doc textSpan range), userOpName) + doc.FindFSharpReferencesAsync(symbol, (fun range -> onFound doc range), userOpName) |> RoslynHelpers.StartAsyncUnitAsTask ct)) |> Task.WhenAll else for doc in documents do do! - doc.FindFSharpReferencesAsync(symbol, (fun textSpan range -> onFound doc textSpan range), userOpName) + doc.FindFSharpReferencesAsync(symbol, (fun range -> onFound doc range), userOpName) |> RoslynHelpers.StartAsyncAsTask ct } diff --git a/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs b/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs index 5a4606bacb8..5ab7ba417b0 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/FindUsagesService.fs @@ -109,60 +109,37 @@ type internal FSharpFindUsagesService [] () = |> Async.AwaitTask |> liftAsync - let onFound = - fun (doc: Document) (textSpan: TextSpan) (symbolUse: range) -> - async { - match declarationRange with - | Some declRange when Range.equals declRange symbolUse -> () - | _ -> - if allReferences then - let definitionItem = - if isExternal then - externalDefinitionItem - else - definitionItems - |> List.tryFind (fun (_, projectId) -> doc.Project.Id = projectId) - |> Option.map (fun (definitionItem, _) -> definitionItem) - |> Option.defaultValue externalDefinitionItem - - let referenceItem = - FSharpSourceReferenceItem(definitionItem, FSharpDocumentSpan(doc, textSpan)) - // REVIEW: OnReferenceFoundAsync is throwing inside Roslyn, putting a try/with so find-all refs doesn't fail. - try - do! context.OnReferenceFoundAsync(referenceItem) |> Async.AwaitTask - with _ -> - () - } - - match symbolUse.GetDeclarationLocation document with - | Some SymbolDeclarationLocation.CurrentDocument -> - let symbolUses = checkFileResults.GetUsesOfSymbolInFile(symbolUse.Symbol) - - for symbolUse in symbolUses do - match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse.Range) with - | Some textSpan -> do! onFound document textSpan symbolUse.Range |> liftAsync - | _ -> () - | scope -> - let projectsToCheck = - match scope with - | Some (SymbolDeclarationLocation.Projects (declProjects, false)) -> - [ - for declProject in declProjects do - yield declProject - yield! declProject.GetDependentProjects() - ] - |> List.distinct - | Some (SymbolDeclarationLocation.Projects (declProjects, true)) -> declProjects - // The symbol is declared in .NET framework, an external assembly or in a C# project within the solution. - // In order to find all its usages we have to check all F# projects. - | _ -> Seq.toList document.Project.Solution.Projects - - let! ct = Async.CancellationToken |> liftAsync + let onFound (doc: Document) (symbolUse: range) = + async { + let! sourceText = doc.GetTextAsync(context.CancellationToken) |> Async.AwaitTask + + match declarationRange, RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, symbolUse) with + | Some declRange, _ when Range.equals declRange symbolUse -> () + | _, None -> () + | _, Some textSpan -> + if allReferences then + let definitionItem = + if isExternal then + externalDefinitionItem + else + definitionItems + |> List.tryFind (fun (_, projectId) -> doc.Project.Id = projectId) + |> Option.map (fun (definitionItem, _) -> definitionItem) + |> Option.defaultValue externalDefinitionItem + + let referenceItem = + FSharpSourceReferenceItem(definitionItem, FSharpDocumentSpan(doc, textSpan)) + // REVIEW: OnReferenceFoundAsync is throwing inside Roslyn, putting a try/with so find-all refs doesn't fail. + try + do! context.OnReferenceFoundAsync(referenceItem) |> Async.AwaitTask + with _ -> + () + } + + do! + SymbolHelpers.findSymbolUses symbolUse document checkFileResults onFound + |> liftAsync - do! - SymbolHelpers.getSymbolUsesInProjects (symbolUse.Symbol, projectsToCheck, onFound, ct) - |> Async.AwaitTask - |> liftAsync } |> Async.Ignore diff --git a/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj b/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj index 9fe2d265ade..d21d6e3c6eb 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj +++ b/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj @@ -25,6 +25,7 @@ + diff --git a/vsintegration/tests/FSharp.Editor.Tests/FindReferencesTests.fs b/vsintegration/tests/FSharp.Editor.Tests/FindReferencesTests.fs new file mode 100644 index 00000000000..50c9d7fbf7c --- /dev/null +++ b/vsintegration/tests/FSharp.Editor.Tests/FindReferencesTests.fs @@ -0,0 +1,131 @@ +module FSharp.Editor.Tests.FindReferencesTests + +open System.Threading.Tasks +open System.Threading +open System.IO +open System.Collections.Concurrent + +open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Editor.FindUsages +open Microsoft.CodeAnalysis.ExternalAccess.FSharp.FindUsages +open Microsoft.VisualStudio.FSharp.Editor + +open Xunit + +open FSharp.Test.ProjectGeneration +open FSharp.Editor.Tests.Helpers + +let getPositionOf (subString: string) (filePath) = + filePath |> File.ReadAllText |> (fun source -> source.IndexOf subString) + +module FindReferences = + + let project = + SyntheticProject.Create( + { sourceFile "First" [] with + SignatureFile = AutoGenerated + ExtraSource = + "let someFunc funcParam = funcParam * 1\n" + + "let sharedFunc funcParam = funcParam * 2\n" + }, + { sourceFile "Second" [] with + ExtraSource = "let someFunc funcParam = funcParam * 1" + }, + { sourceFile "Third" [ "First" ] with + ExtraSource = "let someFunc x = ModuleFirst.sharedFunc x + 10" + } + ) + + let solution, checker = RoslynTestHelpers.CreateSolution project + + let findUsagesService = FSharpFindUsagesService() :> IFSharpFindUsagesService + + let getContext () = + let foundDefinitions = ConcurrentBag() + let foundReferences = ConcurrentBag() + + let context = + { new IFSharpFindUsagesContext with + + member _.OnDefinitionFoundAsync(definition: FSharpDefinitionItem) = + foundDefinitions.Add definition + Task.CompletedTask + + member _.OnReferenceFoundAsync(reference: FSharpSourceReferenceItem) = + foundReferences.Add reference + Task.CompletedTask + + member _.ReportMessageAsync _ = Task.CompletedTask + member _.ReportProgressAsync(_, _) = Task.CompletedTask + member _.SetSearchTitleAsync _ = Task.CompletedTask + member _.CancellationToken = CancellationToken.None + } + + context, foundDefinitions, foundReferences + + [] + let ``Find references to a document-local symbol`` () = + + let context, foundDefinitions, foundReferences = getContext () + + let documentPath = project.GetFilePath "Second" + + let document = + solution.TryGetDocumentFromPath documentPath + |> Option.defaultWith (fun _ -> failwith "Document not found") + + findUsagesService + .FindReferencesAsync(document, getPositionOf "funcParam" documentPath, context) + .Wait() + + // We cannot easily inspect what exactly was found here, but that should be verified + // in FSharp.Compiler.ComponentTests.FSharpChecker.FindReferences + if foundDefinitions.Count <> 1 then + failwith $"Expected 1 definition but found {foundDefinitions.Count}" + + if foundReferences.Count <> 1 then + failwith $"Expected 1 reference but found {foundReferences.Count}" + + [] + let ``Find references to an implementation + signature symbol`` () = + + let context, foundDefinitions, foundReferences = getContext () + + let documentPath = project.GetFilePath "First" + + let document = + solution.TryGetDocumentFromPath documentPath + |> Option.defaultWith (fun _ -> failwith "Document not found") + + findUsagesService + .FindReferencesAsync(document, getPositionOf "funcParam" documentPath, context) + .Wait() + + if foundDefinitions.Count <> 1 then + failwith $"Expected 1 definition but found {foundDefinitions.Count}" + + if + foundReferences.Count <> 2 // One in signature file, one in function body + then + failwith $"Expected 2 references but found {foundReferences.Count}" + + [] + let ``Find references to a symbol in project`` () = + let context, foundDefinitions, foundReferences = getContext () + + let documentPath = project.GetFilePath "First" + + let document = + solution.TryGetDocumentFromPath documentPath + |> Option.defaultWith (fun _ -> failwith "Document not found") + + findUsagesService + .FindReferencesAsync(document, getPositionOf "sharedFunc" documentPath, context) + .Wait() + + if foundDefinitions.Count <> 1 then + failwith $"Expected 1 definition but found {foundDefinitions.Count}" + + if + foundReferences.Count <> 2 // One in signature file, one in Third file + then + failwith $"Expected 2 references but found {foundReferences.Count}" diff --git a/vsintegration/tests/FSharp.Editor.Tests/Helpers/RoslynHelpers.fs b/vsintegration/tests/FSharp.Editor.Tests/Helpers/RoslynHelpers.fs index 91c3077d660..17ab1d90f4c 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/Helpers/RoslynHelpers.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/Helpers/RoslynHelpers.fs @@ -15,6 +15,7 @@ open Microsoft.CodeAnalysis.Text open Microsoft.VisualStudio.FSharp.Editor open Microsoft.CodeAnalysis.Host.Mef open FSharp.Compiler.CodeAnalysis +open FSharp.Test.ProjectGeneration [] module MefHelpers = @@ -299,3 +300,40 @@ type RoslynTestHelpers private () = let project = solution.Projects |> Seq.exactlyOne let document = project.Documents |> Seq.exactlyOne document + + static member CreateSolution(syntheticProject: SyntheticProject) = + + let checker = syntheticProject.SaveAndCheck() + + assert (syntheticProject.DependsOn = []) // multi-project not supported yet + + let projId = ProjectId.CreateNewId() + + let docInfos = + [ + for project, file in syntheticProject.GetAllFiles() do + let filePath = getFilePath project file + RoslynTestHelpers.CreateDocumentInfo projId filePath (File.ReadAllText filePath) + + if file.HasSignatureFile then + let sigFilePath = getSignatureFilePath project file + RoslynTestHelpers.CreateDocumentInfo projId sigFilePath (File.ReadAllText sigFilePath) + ] + + let projInfo = + RoslynTestHelpers.CreateProjectInfo projId syntheticProject.ProjectFileName docInfos + + let options = syntheticProject.GetProjectOptions checker + + let metadataReferences = + options.OtherOptions + |> Seq.filter (fun x -> x.StartsWith("-r:")) + |> Seq.map (fun x -> x.Substring(3) |> MetadataReference.CreateFromFile :> MetadataReference) + + let projInfo = projInfo.WithMetadataReferences metadataReferences + + let solution = RoslynTestHelpers.CreateSolution [ projInfo ] + + options |> RoslynTestHelpers.SetProjectOptions projId solution + + solution, checker