diff --git a/docs/release-notes/.FSharp.Compiler.Service/8.0.300.md b/docs/release-notes/.FSharp.Compiler.Service/8.0.300.md index b8dc1de0de9..6a4d44d2aec 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/8.0.300.md +++ b/docs/release-notes/.FSharp.Compiler.Service/8.0.300.md @@ -1,11 +1,14 @@ ### Fixed +* Fix missing warning for recursive calls in list comprehensions. ([PR #16652](https://github.com/dotnet/fsharp/pull/16652)) * Code generated files with > 64K methods and generated symbols crash when loaded. Use infered sequence points for debugging. ([Issue #16399](https://github.com/dotnet/fsharp/issues/16399), [#PR 16514](https://github.com/dotnet/fsharp/pull/16514)) * `nameof Module` expressions and patterns are processed to link files in `--test:GraphBasedChecking`. ([PR #16550](https://github.com/dotnet/fsharp/pull/16550)) * Graph Based Checking doesn't throw on invalid parsed input so it can be used for IDE scenarios ([PR #16575](https://github.com/dotnet/fsharp/pull/16575), [PR #16588](https://github.com/dotnet/fsharp/pull/16588), [PR #16643](https://github.com/dotnet/fsharp/pull/16643)) +* Various parenthesization API fixes. ([PR #16578](https://github.com/dotnet/fsharp/pull/16578), [PR #16666](https://github.com/dotnet/fsharp/pull/16666)) * Keep parens for problematic exprs (`if`, `match`, etc.) in `$"{(…):N0}"`, `$"{(…),-3}"`, etc. ([PR #16578](https://github.com/dotnet/fsharp/pull/16578)) -* Fix crash in DOTNET_SYSTEM_GLOBALIZATION_INVARIANT mode [#PR 16471](https://github.com/dotnet/fsharp/pull/16471)) - +* Fix crash in DOTNET_SYSTEM_GLOBALIZATION_INVARIANT mode ([PR #16471](https://github.com/dotnet/fsharp/pull/16471)) +* `[]` member should not produce property symbol. ([Issue #16640](https://github.com/dotnet/fsharp/issues/16640), [PR #16658](https://github.com/dotnet/fsharp/pull/16658)) +* Fix discriminated union initialization. ([#PR 16661](https://github.com/dotnet/fsharp/pull/16661)) ### Added @@ -14,6 +17,7 @@ * Name resolution: keep type vars in subsequent checks ([PR #16456](https://github.com/dotnet/fsharp/pull/16456)) * Higher-order-function-based API for working with the untyped abstract syntax tree. ([PR #16462](https://github.com/dotnet/fsharp/pull/16462)) * Allow returning bool instead of unit option for partial active patterns. ([Language suggestion #1041](https://github.com/fsharp/fslang-suggestions/issues/1041), [PR #16473](https://github.com/dotnet/fsharp/pull/16473)) +* Symbols: Add GenericArguments to FSharpEntity ([PR #16470](https://github.com/dotnet/fsharp/pull/16470)) ### Changed diff --git a/docs/release-notes/.VisualStudio/17.10.md b/docs/release-notes/.VisualStudio/17.10.md index 0045b1bd64b..a4b0a8a9f0d 100644 --- a/docs/release-notes/.VisualStudio/17.10.md +++ b/docs/release-notes/.VisualStudio/17.10.md @@ -1,6 +1,7 @@ ### Fixed * Show signature help mid-pipeline in more scenarios. ([PR #16462](https://github.com/dotnet/fsharp/pull/16462)) +* Various unneeded parentheses code fix improvements. ([PR #16578](https://github.com/dotnet/fsharp/pull/16578), [PR #16666](https://github.com/dotnet/fsharp/pull/16666)) ### Changed diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 503c4903f09..9010c61d06b 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,10 +1,9 @@ - - + https://github.com/dotnet/source-build-reference-packages - 412264fd6c04712d1d31ff05d37c6919101ef4f4 + ffac2194c39660f03761ba81bdd6026202942184 @@ -31,9 +30,9 @@ - + https://github.com/dotnet/arcade - 07cf24f27ee58b5d1a9662334a101d84bd1e07e5 + be88b08c41971b52ec11aec05ef31e72185d4a1f diff --git a/global.json b/global.json index 60ffcefc016..57b76f86929 100644 --- a/global.json +++ b/global.json @@ -17,7 +17,7 @@ "perl": "5.38.0.1" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24075.5", + "Microsoft.DotNet.Arcade.Sdk": "8.0.0-beta.24081.5", "Microsoft.DotNet.Helix.Sdk": "8.0.0-beta.23255.2" } } diff --git a/src/Compiler/AbstractIL/il.fs b/src/Compiler/AbstractIL/il.fs index aa6c4aae69e..7f303178553 100644 --- a/src/Compiler/AbstractIL/il.fs +++ b/src/Compiler/AbstractIL/il.fs @@ -3952,6 +3952,29 @@ let mdef_code2code f (md: ILMethodDef) = let b = MethodBody.IL(notlazy ilCode) md.With(body = notlazy b) +let appendInstrsToCode (instrs: ILInstr list) (c2: ILCode) = + let instrs = Array.ofList instrs + + match + c2.Instrs + |> Array.tryFindIndexBack (fun instr -> + match instr with + | I_ret -> true + | _ -> false) + with + | Some 0 -> + { c2 with + Instrs = Array.concat [| instrs; c2.Instrs |] + } + | Some index -> + { c2 with + Instrs = Array.concat [| c2.Instrs[.. index - 1]; instrs; c2.Instrs[index..] |] + } + | None -> + { c2 with + Instrs = Array.append c2.Instrs instrs + } + let prependInstrsToCode (instrs: ILInstr list) (c2: ILCode) = let instrs = Array.ofList instrs let n = instrs.Length @@ -3985,6 +4008,9 @@ let prependInstrsToCode (instrs: ILInstr list) (c2: ILCode) = Instrs = Array.append instrs c2.Instrs } +let appendInstrsToMethod newCode md = + mdef_code2code (appendInstrsToCode newCode) md + let prependInstrsToMethod newCode md = mdef_code2code (prependInstrsToCode newCode) md diff --git a/src/Compiler/AbstractIL/il.fsi b/src/Compiler/AbstractIL/il.fsi index 5ba803fb757..5e02f4c0c1e 100644 --- a/src/Compiler/AbstractIL/il.fsi +++ b/src/Compiler/AbstractIL/il.fsi @@ -2162,8 +2162,9 @@ val internal mkRawDataValueTypeDef: ILType -> string * size: int32 * pack: uint1 /// the code, and the first instruction will be the new entry /// of the method. The instructions should be non-branching. +val internal appendInstrsToCode: ILInstr list -> ILCode -> ILCode +val internal appendInstrsToMethod: ILInstr list -> ILMethodDef -> ILMethodDef val internal prependInstrsToCode: ILInstr list -> ILCode -> ILCode - val internal prependInstrsToMethod: ILInstr list -> ILMethodDef -> ILMethodDef /// Injecting initialization code into a class. diff --git a/src/Compiler/Checking/CheckDeclarations.fs b/src/Compiler/Checking/CheckDeclarations.fs index c5e21e744cd..ef59411179e 100644 --- a/src/Compiler/Checking/CheckDeclarations.fs +++ b/src/Compiler/Checking/CheckDeclarations.fs @@ -1140,11 +1140,11 @@ module MutRecBindingChecking = for b1, b2 in List.pairwise defnAs do match b1, b2 with | TyconBindingPhase2A.Phase2AMember { - SyntacticBinding = NormalizedBinding(pat = SynPat.Named(ident = SynIdent(ident = getIdent)); valSynData = SynValData(memberFlags = Some mf)) + SyntacticBinding = NormalizedBinding(pat = SynPat.Named(ident = SynIdent(ident = Get_OrSet_Ident & getIdent)); valSynData = SynValData(memberFlags = Some mf)) RecBindingInfo = RecursiveBindingInfo(vspec = vGet) }, TyconBindingPhase2A.Phase2AMember { - SyntacticBinding = NormalizedBinding(pat = SynPat.Named(ident = SynIdent(ident = setIdent))) + SyntacticBinding = NormalizedBinding(pat = SynPat.Named(ident = SynIdent(ident = Get_OrSet_Ident & setIdent))) RecBindingInfo = RecursiveBindingInfo(vspec = vSet) } when Range.equals getIdent.idRange setIdent.idRange -> match vGet.ApparentEnclosingEntity with diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs index b70c297e988..68b333b52d9 100644 --- a/src/Compiler/Checking/NameResolution.fs +++ b/src/Compiler/Checking/NameResolution.fs @@ -278,7 +278,7 @@ type Item = | Item.CtorGroup(nm, _) -> nm |> DemangleGenericTypeName | Item.DelegateCtor ty -> match ty with - | AbbrevOrAppTy tcref -> tcref.DisplayNameCore + | AbbrevOrAppTy(tcref, _) -> tcref.DisplayNameCore // This case is not expected | _ -> "" | Item.UnqualifiedType(tcref :: _) -> tcref.DisplayNameCore @@ -309,7 +309,7 @@ type Item = | Item.Property(info = pinfo :: _) -> pinfo.DisplayName | Item.Event einfo -> einfo.DisplayName | Item.MethodGroup(_, minfo :: _, _) -> minfo.DisplayName - | Item.DelegateCtor (AbbrevOrAppTy tcref) -> tcref.DisplayName + | Item.DelegateCtor (AbbrevOrAppTy(tcref, _)) -> tcref.DisplayName | Item.UnqualifiedType(tcref :: _) -> tcref.DisplayName | Item.ModuleOrNamespaces(modref :: _) -> modref.DisplayName | Item.TypeVar (nm, _) -> nm |> ConvertLogicalNameToDisplayName @@ -1872,11 +1872,11 @@ let (|EntityUse|_|) (item: Item) = match item with | Item.UnqualifiedType (tcref :: _) -> ValueSome tcref | Item.ExnCase tcref -> ValueSome tcref - | Item.Types(_, [AbbrevOrAppTy tcref]) - | Item.DelegateCtor(AbbrevOrAppTy tcref) -> ValueSome tcref + | Item.Types(_, [AbbrevOrAppTy(tcref, _)]) + | Item.DelegateCtor(AbbrevOrAppTy(tcref, _)) -> ValueSome tcref | Item.CtorGroup(_, ctor :: _) -> match ctor.ApparentEnclosingType with - | AbbrevOrAppTy tcref -> ValueSome tcref + | AbbrevOrAppTy(tcref, _) -> ValueSome tcref | _ -> ValueNone | _ -> ValueNone @@ -1958,7 +1958,7 @@ let ItemsAreEffectivelyEqual g orig other = not tp1.IsCompilerGenerated && not tp1.IsFromError && not tp2.IsCompilerGenerated && not tp2.IsFromError && equals tp1.Range tp2.Range - | AbbrevOrAppTy tcref1, AbbrevOrAppTy tcref2 -> + | AbbrevOrAppTy(tcref1, _), AbbrevOrAppTy(tcref2, _) -> tyconRefDefnEq g tcref1 tcref2 | _ -> false) diff --git a/src/Compiler/Checking/TailCallChecks.fs b/src/Compiler/Checking/TailCallChecks.fs index cd8dfd2b77c..c26683e3da9 100644 --- a/src/Compiler/Checking/TailCallChecks.fs +++ b/src/Compiler/Checking/TailCallChecks.fs @@ -539,12 +539,10 @@ and CheckExprOp cenv (op, tyargs, args, m) ctxt : unit = | TOp.ValFieldSet _rf, _, [ _arg1; _arg2 ] -> () | TOp.Coerce, [ tgtTy; srcTy ], [ x ] -> - let tailCall = TailCall.YesFromExpr cenv.g x - if TypeDefinitelySubsumesTypeNoCoercion 0 g cenv.amap m tgtTy srcTy then - CheckExpr cenv x ctxt tailCall + CheckExpr cenv x ctxt TailCall.No else - CheckExprNoByrefs cenv tailCall x + CheckExprNoByrefs cenv TailCall.No x | TOp.Reraise, [ _ty1 ], [] -> () diff --git a/src/Compiler/Checking/infos.fs b/src/Compiler/Checking/infos.fs index 8d249521aa0..c90d49f3dcc 100644 --- a/src/Compiler/Checking/infos.fs +++ b/src/Compiler/Checking/infos.fs @@ -702,7 +702,7 @@ type MethInfo = member x.DebuggerDisplayName = match x with | ILMeth(_, y, _) -> y.DeclaringTyconRef.DisplayNameWithStaticParametersAndUnderscoreTypars + "::" + y.ILName - | FSMeth(_, AbbrevOrAppTy tcref, vref, _) -> tcref.DisplayNameWithStaticParametersAndUnderscoreTypars + "::" + vref.LogicalName + | FSMeth(_, AbbrevOrAppTy(tcref, _), vref, _) -> tcref.DisplayNameWithStaticParametersAndUnderscoreTypars + "::" + vref.LogicalName | FSMeth(_, _, vref, _) -> "??::" + vref.LogicalName #if !NO_TYPEPROVIDERS | ProvidedMeth(_, mi, _, m) -> "ProvidedMeth: " + mi.PUntaint((fun mi -> mi.Name), m) diff --git a/src/Compiler/CodeGen/IlxGen.fs b/src/Compiler/CodeGen/IlxGen.fs index e301813edae..6a574fadc14 100644 --- a/src/Compiler/CodeGen/IlxGen.fs +++ b/src/Compiler/CodeGen/IlxGen.fs @@ -1908,7 +1908,16 @@ type TypeDefBuilder(tdef: ILTypeDef, tdefDiscards) = if not discard then AddPropertyDefToHash m gproperties pdef - member _.PrependInstructionsToSpecificMethodDef(cond, instrs, tag, imports) = + member _.AppendInstructionsToSpecificMethodDef(cond, instrs, tag, imports) = + match ResizeArray.tryFindIndex cond gmethods with + | Some idx -> gmethods[idx] <- appendInstrsToMethod instrs gmethods[idx] + | None -> + let body = + mkMethodBody (false, [], 1, nonBranchingInstrsToCode instrs, tag, imports) + + gmethods.Add(mkILClassCtor body) + + member this.PrependInstructionsToSpecificMethodDef(cond, instrs, tag, imports) = match ResizeArray.tryFindIndex cond gmethods with | Some idx -> gmethods[idx] <- prependInstrsToMethod instrs gmethods[idx] | None -> @@ -1917,6 +1926,8 @@ type TypeDefBuilder(tdef: ILTypeDef, tdefDiscards) = gmethods.Add(mkILClassCtor body) + this + and TypeDefsBuilder() = let tdefs = @@ -2264,6 +2275,22 @@ and AssemblyBuilder(cenv: cenv, anonTypeTable: AnonTypeGenerationTable) as mgbuf /// static init fields on script modules. let scriptInitFspecs = ConcurrentStack() + let initialInstrs seqpt feefee = + [ + yield! + (if isEnvVarSet "NO_ADD_FEEFEE_TO_CCTORS" then [] + elif isEnvVarSet "ADD_SEQPT_TO_CCTORS" then seqpt + else feefee) // mark start of hidden code + ] + + let finalInstrs fspec = + [ + yield mkLdcInt32 0 + yield mkNormalStsfld fspec + yield mkNormalLdsfld fspec + yield AI_pop + ] + member _.AddScriptInitFieldSpec(fieldSpec, range) = scriptInitFspecs.Push((fieldSpec, range)) @@ -2276,15 +2303,7 @@ and AssemblyBuilder(cenv: cenv, anonTypeTable: AnonTypeGenerationTable) as mgbuf let InitializeCompiledScript (fspec, m) = let ilDebugRange = GenPossibleILDebugRange cenv m - mgbuf.AddExplicitInitToSpecificMethodDef( - (fun (md: ILMethodDef) -> md.IsEntryPoint), - tref, - fspec, - ilDebugRange, - imports, - [], - [] - ) + mgbuf.AddExplicitInitToEntryPoint(tref, fspec, ilDebugRange, imports, [], []) scriptInitFspecs |> Seq.iter InitializeCompiledScript | None -> () @@ -2325,24 +2344,23 @@ and AssemblyBuilder(cenv: cenv, anonTypeTable: AnonTypeGenerationTable) as mgbuf if ilMethodDef.IsEntryPoint then explicitEntryPointInfo <- Some tref - member _.AddExplicitInitToSpecificMethodDef(cond, tref, fspec, sourceOpt, imports, feefee, seqpt) = - // Authoring a .cctor with effects forces the cctor for the 'initialization' module by doing a dummy store & load of a field - // Doing both a store and load keeps FxCop happier because it thinks the field is useful - let instrs = - [ - yield! - (if isEnvVarSet "NO_ADD_FEEFEE_TO_CCTORS" then [] - elif isEnvVarSet "ADD_SEQPT_TO_CCTORS" then seqpt - else feefee) // mark start of hidden code - yield mkLdcInt32 0 - yield mkNormalStsfld fspec - yield mkNormalLdsfld fspec - yield AI_pop - ] + member _.AddExplicitInitToEntryPoint(tref, fspec, sourceOpt, imports, feefee, seqpt) = + + let cond = (fun (md: ILMethodDef) -> md.IsEntryPoint) gtdefs .FindNestedTypeDefBuilder(tref) - .PrependInstructionsToSpecificMethodDef(cond, instrs, sourceOpt, imports) + .PrependInstructionsToSpecificMethodDef(cond, (initialInstrs seqpt feefee) @ (finalInstrs fspec), sourceOpt, imports) + |> ignore + + member _.AddExplicitInitToCctor(tref, fspec, sourceOpt, imports, feefee, seqpt) = + + let cond = (fun (md: ILMethodDef) -> md.Name = ".cctor") + + gtdefs + .FindNestedTypeDefBuilder(tref) + .PrependInstructionsToSpecificMethodDef(cond, initialInstrs seqpt feefee, sourceOpt, imports) + .AppendInstructionsToSpecificMethodDef(cond, finalInstrs fspec, sourceOpt, imports) member _.AddEventDef(tref, edef) = gtdefs.FindNestedTypeDefBuilder(tref).AddEventDef(edef) @@ -10194,15 +10212,7 @@ and GenImplFile cenv (mgbuf: AssemblyBuilder) mainInfoOpt eenv (implFile: Checke // This adds the explicit init of the .cctor to the explicit entry point main method let ilDebugRange = GenPossibleILDebugRange cenv m - mgbuf.AddExplicitInitToSpecificMethodDef( - (fun md -> md.IsEntryPoint), - tref, - fspec, - ilDebugRange, - eenv.imports, - feefee, - seqpt - )) + mgbuf.AddExplicitInitToEntryPoint(tref, fspec, ilDebugRange, eenv.imports, feefee, seqpt)) let cctorMethDef = mkILClassCtor (MethodBody.IL(InterruptibleLazy.FromValue topCode)) @@ -10289,7 +10299,7 @@ and GenForceWholeFileInitializationAsPartOfCCtor cenv (mgbuf: AssemblyBuilder) ( // Doing both a store and load keeps FxCop happier because it thinks the field is useful lazyInitInfo.Add(fun fspec feefee seqpt -> let ilDebugRange = GenPossibleILDebugRange cenv m - mgbuf.AddExplicitInitToSpecificMethodDef((fun md -> md.Name = ".cctor"), tref, fspec, ilDebugRange, imports, feefee, seqpt)) + mgbuf.AddExplicitInitToCctor(tref, fspec, ilDebugRange, imports, feefee, seqpt)) /// Generate an Equals method. and GenEqualsOverrideCallingIComparable cenv (tcref: TyconRef, ilThisTy, _ilThatTy) = diff --git a/src/Compiler/Service/FSharpCheckerResults.fs b/src/Compiler/Service/FSharpCheckerResults.fs index 5f18a90968a..f55010a9025 100644 --- a/src/Compiler/Service/FSharpCheckerResults.fs +++ b/src/Compiler/Service/FSharpCheckerResults.fs @@ -1805,9 +1805,9 @@ type internal TypeCheckInfo |> List.sortBy (fun d -> let n = match d.Item with - | Item.Types(_, AbbrevOrAppTy tcref :: _) -> 1 + tcref.TyparsNoRange.Length + | Item.Types(_, AbbrevOrAppTy(tcref, _) :: _) -> 1 + tcref.TyparsNoRange.Length // Put delegate ctors after types, sorted by #typars. RemoveDuplicateItems will remove FakeInterfaceCtor and DelegateCtor if an earlier type is also reported with this name - | Item.DelegateCtor(AbbrevOrAppTy tcref) -> 1000 + tcref.TyparsNoRange.Length + | Item.DelegateCtor(AbbrevOrAppTy(tcref, _)) -> 1000 + tcref.TyparsNoRange.Length // Put type ctors after types, sorted by #typars. RemoveDuplicateItems will remove DefaultStructCtors if a type is also reported with this name | Item.CtorGroup(_, cinfo :: _) -> 1000 + 10 * cinfo.DeclaringTyconRef.TyparsNoRange.Length | _ -> 0 @@ -1823,10 +1823,10 @@ type internal TypeCheckInfo items |> List.groupBy (fun d -> match d.Item with - | Item.Types(_, AbbrevOrAppTy tcref :: _) + | Item.Types(_, AbbrevOrAppTy(tcref, _) :: _) | Item.ExnCase tcref -> tcref.LogicalName | Item.UnqualifiedType(tcref :: _) - | Item.DelegateCtor(AbbrevOrAppTy tcref) -> tcref.CompiledName + | Item.DelegateCtor(AbbrevOrAppTy(tcref, _)) -> tcref.CompiledName | Item.CtorGroup(_, cinfo :: _) -> cinfo.ApparentEnclosingTyconRef.CompiledName | _ -> d.Item.DisplayName) diff --git a/src/Compiler/Service/ServiceParseTreeWalk.fs b/src/Compiler/Service/ServiceParseTreeWalk.fs index ec5b623d0b8..d0c0132dc6b 100644 --- a/src/Compiler/Service/ServiceParseTreeWalk.fs +++ b/src/Compiler/Service/ServiceParseTreeWalk.fs @@ -1163,8 +1163,8 @@ module SyntaxNode = [] [] -module ParsedInput = - let fold folder state (parsedInput: ParsedInput) = +module internal SyntaxNodes = + let fold folder state (ast: SyntaxNode list) = let mutable state = state let visitor = @@ -1270,7 +1270,6 @@ module ParsedInput = loop diveResults - let ast = parsedInput.Contents let m = (range0, ast) ||> List.fold (fun acc node -> unionRanges acc node.Range) ignore (SyntaxTraversal.traverseUntil pickAll m.End visitor ast) state @@ -1454,7 +1453,7 @@ module ParsedInput = ignore (SyntaxTraversal.traverseUntil pick pos visitor ast) state - let foldWhile folder state (parsedInput: ParsedInput) = + let foldWhile folder state (ast: SyntaxNode list) = let pickAll _ _ _ diveResults = let rec loop diveResults = match diveResults with @@ -1465,11 +1464,10 @@ module ParsedInput = loop diveResults - let ast = parsedInput.Contents let m = (range0, ast) ||> List.fold (fun acc node -> unionRanges acc node.Range) foldWhileImpl pickAll m.End folder state ast - let tryPick chooser position (parsedInput: ParsedInput) = + let tryPick chooser position (ast: SyntaxNode list) = let visitor = { new SyntaxVisitorBase<'T>() with member _.VisitExpr(path, _, defaultTraverse, expr) = @@ -1545,25 +1543,46 @@ module ParsedInput = | _ -> None } - SyntaxTraversal.traverseUntil SyntaxTraversal.pick position visitor parsedInput.Contents + SyntaxTraversal.traverseUntil SyntaxTraversal.pick position visitor ast - let tryPickLast chooser position (parsedInput: ParsedInput) = - (None, parsedInput.Contents) + let tryPickLast chooser position (ast: SyntaxNode list) = + (None, ast) ||> foldWhileImpl SyntaxTraversal.pick position (fun prev path node -> match chooser path node with | Some _ as next -> Some next | None -> Some prev) - let tryNode position (parsedInput: ParsedInput) = + let tryNode position (ast: SyntaxNode list) = let Matching = Some - (None, parsedInput.Contents) + (None, ast) ||> foldWhileImpl SyntaxTraversal.pick position (fun _prev path node -> if rangeContainsPos node.Range position then Some(Matching(node, path)) else None) - let exists predicate position parsedInput = - tryPick (fun path node -> if predicate path node then Some() else None) position parsedInput + let exists predicate position ast = + tryPick (fun path node -> if predicate path node then Some() else None) position ast |> Option.isSome + +[] +[] +module ParsedInput = + let fold folder state (parsedInput: ParsedInput) = + SyntaxNodes.fold folder state parsedInput.Contents + + let foldWhile folder state (parsedInput: ParsedInput) = + SyntaxNodes.foldWhile folder state parsedInput.Contents + + let tryPick chooser position (parsedInput: ParsedInput) = + SyntaxNodes.tryPick chooser position parsedInput.Contents + + let tryPickLast chooser position (parsedInput: ParsedInput) = + SyntaxNodes.tryPickLast chooser position parsedInput.Contents + + let tryNode position (parsedInput: ParsedInput) = + SyntaxNodes.tryNode position parsedInput.Contents + + let exists predicate position (parsedInput: ParsedInput) = + SyntaxNodes.exists predicate position parsedInput.Contents diff --git a/src/Compiler/Service/ServiceParseTreeWalk.fsi b/src/Compiler/Service/ServiceParseTreeWalk.fsi index 86ca17380ee..ab9e98f6e81 100644 --- a/src/Compiler/Service/ServiceParseTreeWalk.fsi +++ b/src/Compiler/Service/ServiceParseTreeWalk.fsi @@ -219,6 +219,135 @@ module SyntaxNode = /// val (|Attributes|): node: SyntaxNode -> SynAttributes +/// +/// Holds operations for working with the untyped abstract syntax tree. +/// +[] +[] +module internal SyntaxNodes = + /// + /// Applies the given predicate to each node of the AST and its context (path) + /// down to a given position, returning true if a matching node is found, otherwise false. + /// Traversal is short-circuited if no matching node is found through the given position. + /// + /// The predicate to match each node against. + /// The position in the input file down to which to apply the function. + /// The AST to search. + /// True if a matching node is found, or false if no matching node is found. + /// + /// + /// let isInTypeDefn = + /// (pos, ast) + /// ||> SyntaxNodes.exists (fun _path node -> + /// match node with + /// | SyntaxNode.SynTypeDefn _ -> true + /// | _ -> false) + /// + /// + val exists: predicate: (SyntaxVisitorPath -> SyntaxNode -> bool) -> position: pos -> ast: SyntaxNode list -> bool + + /// + /// Applies a function to each node of the AST and its context (path), + /// threading an accumulator through the computation. + /// + /// The function to use to update the state given each node and its context. + /// The initial state. + /// The AST to fold over. + /// The final state. + /// + /// + /// let unnecessaryParentheses = + /// (HashSet Range.comparer, ast) ||> SyntaxNodes.fold (fun acc path node -> + /// match node with + /// | SyntaxNode.SynExpr (SynExpr.Paren (expr = inner; rightParenRange = Some _; range = range)) when + /// not (SynExpr.shouldBeParenthesizedInContext getLineString path inner) + /// -> + /// ignore (acc.Add range) + /// acc + /// + /// | SyntaxNode.SynPat (SynPat.Paren (inner, range)) when + /// not (SynPat.shouldBeParenthesizedInContext path inner) + /// -> + /// ignore (acc.Add range) + /// acc + /// + /// | _ -> acc) + /// + /// + val fold: + folder: ('State -> SyntaxVisitorPath -> SyntaxNode -> 'State) -> state: 'State -> ast: SyntaxNode list -> 'State + + /// + /// Applies a function to each node of the AST and its context (path) + /// until the folder returns None, threading an accumulator through the computation. + /// + /// The function to use to update the state given each node and its context, or to stop traversal by returning None. + /// The initial state. + /// The AST to fold over. + /// The final state. + val foldWhile: + folder: ('State -> SyntaxVisitorPath -> SyntaxNode -> 'State option) -> + state: 'State -> + ast: SyntaxNode list -> + 'State + + /// + /// Dives to the deepest node that contains the given position, + /// returning the node and its path if found, or None if no + /// node contains the position. + /// + /// The position in the input file down to which to dive. + /// The AST to search. + /// The deepest node containing the given position, along with the path taken through the node's ancestors to find it. + val tryNode: position: pos -> ast: SyntaxNode list -> (SyntaxNode * SyntaxVisitorPath) option + + /// + /// Applies the given function to each node of the AST and its context (path) + /// down to a given position, returning Some x for the first node + /// for which the function returns Some x for some value x, otherwise None. + /// Traversal is short-circuited if no matching node is found through the given position. + /// + /// The function to apply to each node and its context to derive an optional value. + /// The position in the input file down to which to apply the function. + /// The AST to search. + /// The first value for which the function returns Some, or None if no matching node is found. + /// + /// + /// let range = + /// (pos, ast) ||> SyntaxNodes.tryPick (fun _path node -> + /// match node with + /// | SyntaxNode.SynExpr (SynExpr.InterpolatedString (range = range)) when + /// rangeContainsPos range pos + /// -> Some range + /// | _ -> None) + /// + /// + val tryPick: + chooser: (SyntaxVisitorPath -> SyntaxNode -> 'T option) -> position: pos -> ast: SyntaxNode list -> 'T option + + /// + /// Applies the given function to each node of the AST and its context (path) + /// down to a given position, returning Some x for the last (deepest) node + /// for which the function returns Some x for some value x, otherwise None. + /// Traversal is short-circuited if no matching node is found through the given position. + /// + /// The function to apply to each node and its context to derive an optional value. + /// The position in the input file down to which to apply the function. + /// The AST to search. + /// The last (deepest) value for which the function returns Some, or None if no matching node is found. + /// + /// + /// let range = + /// (pos, ast) + /// ||> SyntaxNodes.tryPickLast (fun path node -> + /// match node, path with + /// | FuncIdent range -> Some range + /// | _ -> None) + /// + /// + val tryPickLast: + chooser: (SyntaxVisitorPath -> SyntaxNode -> 'T option) -> position: pos -> ast: SyntaxNode list -> 'T option + /// /// Holds operations for working with the /// untyped abstract syntax tree (). diff --git a/src/Compiler/Service/SynExpr.fs b/src/Compiler/Service/SynExpr.fs index 41dbd4d4d18..07321ce1646 100644 --- a/src/Compiler/Service/SynExpr.fs +++ b/src/Compiler/Service/SynExpr.fs @@ -774,6 +774,23 @@ module SynExpr = loop clauses + let innerBindingsWouldShadowOuter expr1 (expr2: SynExpr) = + let identsBoundInInner = + (Set.empty, [ SyntaxNode.SynExpr expr1 ]) + ||> SyntaxNodes.fold (fun idents _path node -> + match node with + | SyntaxNode.SynPat(SynPat.Named(ident = SynIdent(ident = ident))) -> idents.Add ident.idText + | _ -> idents) + + if identsBoundInInner.IsEmpty then + false + else + (expr2.Range.End, [ SyntaxNode.SynExpr expr2 ]) + ||> SyntaxNodes.exists (fun _path node -> + match node with + | SyntaxNode.SynExpr(SynExpr.Ident ident) -> identsBoundInInner.Contains ident.idText + | _ -> false) + match outer, inner with | ConfusableWithTypeApp, _ -> true @@ -812,6 +829,21 @@ module SynExpr = -> true + // Keep parens if a name in the outer scope is rebound + // in the inner scope and would shadow the outer binding + // if the parens were removed, e.g.: + // + // let x = 3 + // ( + // let x = 4 + // printfn $"{x}" + // ) + // x + | SynExpr.Sequential(expr1 = SynExpr.Paren(expr = Is inner); expr2 = expr2), _ when innerBindingsWouldShadowOuter inner expr2 -> + true + + | SynExpr.InterpolatedString _, SynExpr.Sequential _ -> true + | SynExpr.InterpolatedString(contents = contents), (SynExpr.Tuple(isStruct = false) | Dangling.Problematic _) -> contents |> List.exists (function diff --git a/src/Compiler/Service/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs index 9f3881ecfd2..0795cb6c17a 100644 --- a/src/Compiler/Service/TransparentCompiler.fs +++ b/src/Compiler/Service/TransparentCompiler.fs @@ -291,6 +291,8 @@ type internal CompilerCaches(sizeFactor: int) = member val ItemKeyStore = AsyncMemoize(sf, 2 * sf, name = "ItemKeyStore") + member val ScriptClosure = AsyncMemoize(sf, 2 * sf, name = "ScriptClosure") + member this.Clear(projects: Set) = let shouldClear project = projects |> Set.contains project @@ -303,6 +305,7 @@ type internal CompilerCaches(sizeFactor: int) = this.AssemblyData.Clear(shouldClear) this.SemanticClassification.Clear(snd >> shouldClear) this.ItemKeyStore.Clear(snd >> shouldClear) + this.ScriptClosure.Clear(snd >> shouldClear) // Todo check if correct predicate type internal TransparentCompiler ( @@ -366,6 +369,52 @@ type internal TransparentCompiler ) :> IBackgroundCompiler + let ComputeScriptClosure + (fileName: string) + (source: ISourceText) + (defaultFSharpBinariesDir: string) + (useSimpleResolution: bool) + (useFsiAuxLib: bool option) + (useSdkRefs: bool option) + (sdkDirOverride: string option) + (assumeDotNetFramework: bool option) + (projectSnapshot: ProjectSnapshot) + = + caches.ScriptClosure.Get( + projectSnapshot.FileKey fileName, + node { + let useFsiAuxLib = defaultArg useFsiAuxLib true + let useSdkRefs = defaultArg useSdkRefs true + let reduceMemoryUsage = ReduceMemoryFlag.Yes + let assumeDotNetFramework = defaultArg assumeDotNetFramework false + + let applyCompilerOptions tcConfig = + let fsiCompilerOptions = GetCoreFsiCompilerOptions tcConfig + ParseCompilerOptions(ignore, fsiCompilerOptions, projectSnapshot.OtherOptions) + + let closure = + LoadClosure.ComputeClosureOfScriptText( + legacyReferenceResolver, + defaultFSharpBinariesDir, + fileName, + source, + CodeContext.Editing, + useSimpleResolution, + useFsiAuxLib, + useSdkRefs, + sdkDirOverride, + Lexhelp.LexResourceManager(), + applyCompilerOptions, + assumeDotNetFramework, + tryGetMetadataSnapshot, + reduceMemoryUsage, + dependencyProviderForScripts + ) + + return closure + } + ) + let ComputeFrameworkImports (tcConfig: TcConfig) frameworkDLLs nonFrameworkResolutions = let frameworkDLLsKey = frameworkDLLs @@ -576,90 +625,113 @@ type internal TransparentCompiler } ] - let ComputeTcConfigBuilder (projectSnapshot: ProjectSnapshotBase<_>) = - - let useSimpleResolutionSwitch = "--simpleresolution" - let commandLineArgs = projectSnapshot.CommandLineOptions - let defaultFSharpBinariesDir = FSharpCheckerResultsSettings.defaultFSharpBinariesDir - let useScriptResolutionRules = projectSnapshot.UseScriptResolutionRules - - let projectReferences = - getProjectReferences projectSnapshot "ComputeTcConfigBuilder" - - // TODO: script support - let loadClosureOpt: LoadClosure option = None - - let getSwitchValue (switchString: string) = - match commandLineArgs |> List.tryFindIndex (fun s -> s.StartsWithOrdinal switchString) with - | Some idx -> Some(commandLineArgs[idx].Substring(switchString.Length)) - | _ -> None - - let sdkDirOverride = - match loadClosureOpt with - | None -> None - | Some loadClosure -> loadClosure.SdkDirOverride - - // see also fsc.fs: runFromCommandLineToImportingAssemblies(), as there are many similarities to where the PS creates a tcConfigB - let tcConfigB = - TcConfigBuilder.CreateNew( - legacyReferenceResolver, - defaultFSharpBinariesDir, - implicitIncludeDir = projectSnapshot.ProjectDirectory, - reduceMemoryUsage = ReduceMemoryFlag.Yes, - isInteractive = useScriptResolutionRules, - isInvalidationSupported = true, - defaultCopyFSharpCore = CopyFSharpCoreFlag.No, - tryGetMetadataSnapshot = tryGetMetadataSnapshot, - sdkDirOverride = sdkDirOverride, - rangeForErrors = range0 - ) + let ComputeTcConfigBuilder (projectSnapshot: ProjectSnapshot) = + node { + let useSimpleResolutionSwitch = "--simpleresolution" + let commandLineArgs = projectSnapshot.CommandLineOptions + let defaultFSharpBinariesDir = FSharpCheckerResultsSettings.defaultFSharpBinariesDir + let useScriptResolutionRules = projectSnapshot.UseScriptResolutionRules - tcConfigB.primaryAssembly <- - match loadClosureOpt with - | None -> PrimaryAssembly.Mscorlib - | Some loadClosure -> - if loadClosure.UseDesktopFramework then - PrimaryAssembly.Mscorlib - else - PrimaryAssembly.System_Runtime + let projectReferences = + getProjectReferences projectSnapshot "ComputeTcConfigBuilder" - tcConfigB.resolutionEnvironment <- (LegacyResolutionEnvironment.EditingOrCompilation true) + let getSwitchValue (switchString: string) = + match commandLineArgs |> List.tryFindIndex (fun s -> s.StartsWithOrdinal switchString) with + | Some idx -> Some(commandLineArgs[idx].Substring(switchString.Length)) + | _ -> None - tcConfigB.conditionalDefines <- - let define = - if useScriptResolutionRules then - "INTERACTIVE" - else - "COMPILED" + let useSimpleResolution = + (getSwitchValue useSimpleResolutionSwitch) |> Option.isSome - define :: tcConfigB.conditionalDefines + let! (loadClosureOpt: LoadClosure option) = + match projectSnapshot.SourceFiles, projectSnapshot.UseScriptResolutionRules with + | [ fsxFile ], true -> // assuming UseScriptResolutionRules and a single source file means we are doing this for a script + node { + let! source = fsxFile.GetSource() |> NodeCode.AwaitTask + + let! closure = + ComputeScriptClosure + fsxFile.FileName + source + defaultFSharpBinariesDir + useSimpleResolution + None + None + None + None + projectSnapshot - tcConfigB.projectReferences <- projectReferences + return (Some closure) + } + | _ -> node { return None } - tcConfigB.useSimpleResolution <- (getSwitchValue useSimpleResolutionSwitch) |> Option.isSome + let sdkDirOverride = + match loadClosureOpt with + | None -> None + | Some loadClosure -> loadClosure.SdkDirOverride + + // see also fsc.fs: runFromCommandLineToImportingAssemblies(), as there are many similarities to where the PS creates a tcConfigB + let tcConfigB = + TcConfigBuilder.CreateNew( + legacyReferenceResolver, + defaultFSharpBinariesDir, + implicitIncludeDir = projectSnapshot.ProjectDirectory, + reduceMemoryUsage = ReduceMemoryFlag.Yes, + isInteractive = useScriptResolutionRules, + isInvalidationSupported = true, + defaultCopyFSharpCore = CopyFSharpCoreFlag.No, + tryGetMetadataSnapshot = tryGetMetadataSnapshot, + sdkDirOverride = sdkDirOverride, + rangeForErrors = range0 + ) - // Apply command-line arguments and collect more source files if they are in the arguments - let sourceFilesNew = - ApplyCommandLineArgs(tcConfigB, projectSnapshot.SourceFileNames, commandLineArgs) + tcConfigB.primaryAssembly <- + match loadClosureOpt with + | None -> PrimaryAssembly.Mscorlib + | Some loadClosure -> + if loadClosure.UseDesktopFramework then + PrimaryAssembly.Mscorlib + else + PrimaryAssembly.System_Runtime - // Never open PDB files for the language service, even if --standalone is specified - tcConfigB.openDebugInformationForLaterStaticLinking <- false + tcConfigB.resolutionEnvironment <- (LegacyResolutionEnvironment.EditingOrCompilation true) - tcConfigB.xmlDocInfoLoader <- - { new IXmlDocumentationInfoLoader with - /// Try to load xml documentation associated with an assembly by the same file path with the extension ".xml". - member _.TryLoad(assemblyFileName) = - let xmlFileName = Path.ChangeExtension(assemblyFileName, ".xml") + tcConfigB.conditionalDefines <- + let define = + if useScriptResolutionRules then + "INTERACTIVE" + else + "COMPILED" - // REVIEW: File IO - Will eventually need to change this to use a file system interface of some sort. - XmlDocumentationInfo.TryCreateFromFile(xmlFileName) - } - |> Some + define :: tcConfigB.conditionalDefines - tcConfigB.parallelReferenceResolution <- parallelReferenceResolution - tcConfigB.captureIdentifiersWhenParsing <- captureIdentifiersWhenParsing + tcConfigB.projectReferences <- projectReferences - tcConfigB, sourceFilesNew, loadClosureOpt + tcConfigB.useSimpleResolution <- useSimpleResolution + + // Apply command-line arguments and collect more source files if they are in the arguments + let sourceFilesNew = + ApplyCommandLineArgs(tcConfigB, projectSnapshot.SourceFileNames, commandLineArgs) + + // Never open PDB files for the language service, even if --standalone is specified + tcConfigB.openDebugInformationForLaterStaticLinking <- false + + tcConfigB.xmlDocInfoLoader <- + { new IXmlDocumentationInfoLoader with + /// Try to load xml documentation associated with an assembly by the same file path with the extension ".xml". + member _.TryLoad(assemblyFileName) = + let xmlFileName = Path.ChangeExtension(assemblyFileName, ".xml") + + // REVIEW: File IO - Will eventually need to change this to use a file system interface of some sort. + XmlDocumentationInfo.TryCreateFromFile(xmlFileName) + } + |> Some + + tcConfigB.parallelReferenceResolution <- parallelReferenceResolution + tcConfigB.captureIdentifiersWhenParsing <- captureIdentifiersWhenParsing + + return tcConfigB, sourceFilesNew, loadClosureOpt + } let mutable BootstrapInfoIdCounter = 0 @@ -746,7 +818,7 @@ type internal TransparentCompiler let computeBootstrapInfoInner (projectSnapshot: ProjectSnapshot) = node { - let tcConfigB, sourceFiles, loadClosureOpt = ComputeTcConfigBuilder projectSnapshot + let! tcConfigB, sourceFiles, loadClosureOpt = ComputeTcConfigBuilder projectSnapshot // If this is a builder for a script, re-apply the settings inferred from the // script and its load closure to the configuration. @@ -1442,7 +1514,17 @@ type internal TransparentCompiler let tcDiagnostics = [| yield! extraDiagnostics; yield! tcDiagnostics |] - let loadClosure = None // TODO: script support + let! loadClosure = + ComputeScriptClosure + fileName + file.Source + tcConfig.fsharpBinariesDir + tcConfig.useSimpleResolution + (Some tcConfig.useFsiAuxLib) + (Some tcConfig.useSdkRefs) + tcConfig.sdkDirOverride + (Some tcConfig.assumeDotNetFramework) + projectSnapshot let typedResults = FSharpCheckFileResults.Make( @@ -1465,7 +1547,7 @@ type internal TransparentCompiler tcResolutions, tcSymbolUses, tcEnv.NameEnv, - loadClosure, + Some loadClosure, checkedImplFileOpt, tcOpenDeclarations ) @@ -1799,7 +1881,7 @@ type internal TransparentCompiler // Activity.start "ParseFile" [| Activity.Tags.fileName, fileName |> Path.GetFileName |] // TODO: might need to deal with exceptions here: - let tcConfigB, sourceFileNames, _ = ComputeTcConfigBuilder projectSnapshot + let! tcConfigB, sourceFileNames, _ = ComputeTcConfigBuilder projectSnapshot let tcConfig = TcConfig.Create(tcConfigB, validate = true) diff --git a/src/Compiler/Service/TransparentCompiler.fsi b/src/Compiler/Service/TransparentCompiler.fsi index 00167ccc67f..14562f34f15 100644 --- a/src/Compiler/Service/TransparentCompiler.fsi +++ b/src/Compiler/Service/TransparentCompiler.fsi @@ -132,6 +132,8 @@ type internal CompilerCaches = member TcIntermediate: AsyncMemoize<(string * (string * string)), (string * int), TcIntermediate> + member ScriptClosure: AsyncMemoize<(string * (string * string)), string, LoadClosure> + member TcLastFile: AsyncMemoizeDisabled type internal TransparentCompiler = diff --git a/src/Compiler/Symbols/SymbolHelpers.fs b/src/Compiler/Symbols/SymbolHelpers.fs index 3c669d7a64c..af89176ff6e 100644 --- a/src/Compiler/Symbols/SymbolHelpers.fs +++ b/src/Compiler/Symbols/SymbolHelpers.fs @@ -284,7 +284,7 @@ module internal SymbolHelpers = | Item.DelegateCtor ty | Item.Types(_, ty :: _) -> match ty with - | AbbrevOrAppTy tcref -> + | AbbrevOrAppTy(tcref, _) -> mkXmlComment (GetXmlDocSigOfEntityRef infoReader m tcref) | _ -> FSharpXmlDoc.None @@ -462,8 +462,8 @@ module internal SymbolHelpers = | Item.UnqualifiedType tcrefs1, Item.UnqualifiedType tcrefs2 -> (tcrefs1, tcrefs2) ||> List.forall2 (fun tcref1 tcref2 -> tyconRefEq g tcref1 tcref2) - | Item.Types(_, [AbbrevOrAppTy tcref1]), Item.UnqualifiedType([tcref2]) -> tyconRefEq g tcref1 tcref2 - | Item.UnqualifiedType([tcref1]), Item.Types(_, [AbbrevOrAppTy tcref2]) -> tyconRefEq g tcref1 tcref2 + | Item.Types(_, [AbbrevOrAppTy(tcref1, _)]), Item.UnqualifiedType([tcref2]) -> tyconRefEq g tcref1 tcref2 + | Item.UnqualifiedType([tcref1]), Item.Types(_, [AbbrevOrAppTy(tcref2, _)]) -> tyconRefEq g tcref1 tcref2 | _ -> false) member x.GetHashCode item = @@ -648,7 +648,7 @@ module internal SymbolHelpers = | Item.Types(_, tys) -> let doc = match tys with - | AbbrevOrAppTy tcref :: _ -> + | AbbrevOrAppTy(tcref, _) :: _ -> if tyconRefUsesLocalXmlDoc g.compilingFSharpCore tcref || tcref.XmlDoc.NonEmpty then Some tcref.XmlDoc else diff --git a/src/Compiler/Symbols/Symbols.fs b/src/Compiler/Symbols/Symbols.fs index 6319af1089c..b5b244a6e5c 100644 --- a/src/Compiler/Symbols/Symbols.fs +++ b/src/Compiler/Symbols/Symbols.fs @@ -292,12 +292,12 @@ type FSharpSymbol(cenv: SymbolEnv, item: unit -> Item, access: FSharpSymbol -> C | Item.CtorGroup(_, cinfo :: _) -> FSharpMemberOrFunctionOrValue(cenv, C cinfo, item) :> _ - | Item.DelegateCtor (AbbrevOrAppTy tcref) -> - FSharpEntity(cenv, tcref) :>_ + | Item.DelegateCtor (AbbrevOrAppTy(tcref, tyargs)) + | Item.Types(_, AbbrevOrAppTy(tcref, tyargs) :: _) -> + FSharpEntity(cenv, tcref, tyargs) :>_ - | Item.UnqualifiedType(tcref :: _) - | Item.Types(_, AbbrevOrAppTy tcref :: _) -> - FSharpEntity(cenv, tcref) :>_ + | Item.UnqualifiedType(tcref :: _) -> + FSharpEntity(cenv, tcref) :> _ | Item.ModuleOrNamespaces(modref :: _) -> FSharpEntity(cenv, modref) :> _ @@ -355,7 +355,7 @@ type FSharpSymbol(cenv: SymbolEnv, item: unit -> Item, access: FSharpSymbol -> C member sym.TryGetAttribute<'T>() = sym.Attributes |> Seq.tryFind (fun attr -> attr.IsAttribute<'T>()) -type FSharpEntity(cenv: SymbolEnv, entity: EntityRef) = +type FSharpEntity(cenv: SymbolEnv, entity: EntityRef, tyargs: TType list) = inherit FSharpSymbol(cenv, (fun () -> checkEntityIsResolved entity @@ -383,6 +383,10 @@ type FSharpEntity(cenv: SymbolEnv, entity: EntityRef) = | None -> false | Some ccu -> ccuEq ccu cenv.g.fslibCcu + new(cenv: SymbolEnv, tcref: TyconRef) = + let _, _, tyargs = FreshenTypeInst cenv.g range0 (tcref.Typars range0) + FSharpEntity(cenv, tcref, tyargs) + member _.Entity = entity member _.LogicalName = @@ -480,6 +484,10 @@ type FSharpEntity(cenv: SymbolEnv, entity: EntityRef) = checkIsResolved() entity.TyparsNoRange |> List.map (fun tp -> FSharpGenericParameter(cenv, tp)) |> makeReadOnlyCollection + member _.GenericArguments = + checkIsResolved() + tyargs |> List.map (fun ty -> FSharpType(cenv, ty)) |> makeReadOnlyCollection + member _.IsMeasure = isResolvedAndFSharp() && (entity.TypeOrMeasureKind = TyparKind.Measure) @@ -718,7 +726,7 @@ type FSharpEntity(cenv: SymbolEnv, entity: EntityRef) = if isUnresolved() then makeReadOnlyCollection [] else entity.ModuleOrNamespaceType.AllEntities |> QueueList.toList - |> List.map (fun x -> FSharpEntity(cenv, entity.NestedTyconRef x)) + |> List.map (fun x -> FSharpEntity(cenv, entity.NestedTyconRef x, tyargs)) |> makeReadOnlyCollection member _.UnionCases = @@ -1688,7 +1696,7 @@ type FSharpMemberOrFunctionOrValue(cenv, d:FSharpMemberOrValData, item) = | Some v -> v | None -> failwith "DeclarationLocation property not available" - member _.DeclaringEntity = + member _.DeclaringEntity: FSharpEntity option = checkIsResolved() match d with | E e -> FSharpEntity(cenv, e.DeclaringTyconRef) |> Some @@ -1699,12 +1707,16 @@ type FSharpMemberOrFunctionOrValue(cenv, d:FSharpMemberOrValData, item) = | ParentNone -> None | Parent p -> FSharpEntity(cenv, p) |> Some - member _.ApparentEnclosingEntity = + member _.ApparentEnclosingEntity: FSharpEntity = + let createEntity (ttype: TType) = + let tcref, tyargs = destAppTy cenv.g ttype + FSharpEntity(cenv, tcref, tyargs) + checkIsResolved() match d with - | E e -> FSharpEntity(cenv, e.ApparentEnclosingTyconRef) - | P p -> FSharpEntity(cenv, p.ApparentEnclosingTyconRef) - | M m | C m -> FSharpEntity(cenv, m.ApparentEnclosingTyconRef) + | E e -> createEntity e.ApparentEnclosingType + | P p -> createEntity p.ApparentEnclosingType + | M m | C m -> createEntity m.ApparentEnclosingType | V v -> match v.ApparentEnclosingEntity with | ParentNone -> invalidOp "the value or member doesn't have a logical parent" @@ -2453,7 +2465,7 @@ type FSharpType(cenv, ty:TType) = let isUnresolved() = DiagnosticsLogger.protectAssemblyExploration true <| fun () -> match stripTyparEqns ty with - | TType_app (tcref, _, _) -> FSharpEntity(cenv, tcref).IsUnresolved + | TType_app (tcref, tyargs, _) -> FSharpEntity(cenv, tcref, tyargs).IsUnresolved | TType_measure (Measure.Const tcref) -> FSharpEntity(cenv, tcref).IsUnresolved | TType_measure (Measure.Prod _) -> FSharpEntity(cenv, cenv.g.measureproduct_tcr).IsUnresolved | TType_measure Measure.One -> FSharpEntity(cenv, cenv.g.measureone_tcr).IsUnresolved @@ -2497,7 +2509,7 @@ type FSharpType(cenv, ty:TType) = member _.TypeDefinition = protect <| fun () -> match stripTyparEqns ty with - | TType_app (tcref, _, _) -> FSharpEntity(cenv, tcref) + | TType_app (tcref, tyargs, _) -> FSharpEntity(cenv, tcref, tyargs) | TType_measure (Measure.Const tcref) -> FSharpEntity(cenv, tcref) | TType_measure (Measure.Prod _) -> FSharpEntity(cenv, cenv.g.measureproduct_tcr) | TType_measure Measure.One -> FSharpEntity(cenv, cenv.g.measureone_tcr) diff --git a/src/Compiler/Symbols/Symbols.fsi b/src/Compiler/Symbols/Symbols.fsi index 4050073c704..5530ba53ec5 100644 --- a/src/Compiler/Symbols/Symbols.fsi +++ b/src/Compiler/Symbols/Symbols.fsi @@ -265,6 +265,10 @@ type FSharpEntity = /// Get the generic parameters, possibly including unit-of-measure parameters member GenericParameters: IList + + /// Get the generic parameters, possibly including unit-of-measure parameters + member GenericArguments: IList + #if !NO_TYPEPROVIDERS /// Get the static parameters for a provided type member StaticParameters: IList diff --git a/src/Compiler/SyntaxTree/SyntaxTreeOps.fs b/src/Compiler/SyntaxTree/SyntaxTreeOps.fs index d6c65234237..a02591eb650 100644 --- a/src/Compiler/SyntaxTree/SyntaxTreeOps.fs +++ b/src/Compiler/SyntaxTree/SyntaxTreeOps.fs @@ -1062,3 +1062,9 @@ let (|TypesForTypar|) (t: SynType) = | _ -> continuation [ t ] visit id t + +[] +let (|Get_OrSet_Ident|_|) (ident: Ident) = + if ident.idText.StartsWithOrdinal("get_") then ValueSome() + elif ident.idText.StartsWithOrdinal("set_") then ValueSome() + else ValueNone diff --git a/src/Compiler/SyntaxTree/SyntaxTreeOps.fsi b/src/Compiler/SyntaxTree/SyntaxTreeOps.fsi index 5ee198564ed..6136886a3cc 100644 --- a/src/Compiler/SyntaxTree/SyntaxTreeOps.fsi +++ b/src/Compiler/SyntaxTree/SyntaxTreeOps.fsi @@ -355,3 +355,7 @@ val getTypeFromTuplePath: path: SynTupleTypeSegment list -> SynType list val (|MultiDimensionArrayType|_|): t: SynType -> (int * SynType * range) voption val (|TypesForTypar|): t: SynType -> SynType list + +/// Generated get_XYZ or set_XYZ ident text +[] +val (|Get_OrSet_Ident|_|): Ident -> unit voption diff --git a/src/Compiler/TypedTree/TypedTreeBasics.fs b/src/Compiler/TypedTree/TypedTreeBasics.fs index 651eadd0d65..c71994685d3 100644 --- a/src/Compiler/TypedTree/TypedTreeBasics.fs +++ b/src/Compiler/TypedTree/TypedTreeBasics.fs @@ -265,7 +265,7 @@ let stripUnitEqns unt = stripUnitEqnsAux false unt /// Detect a use of a nominal type, including type abbreviations. let (|AbbrevOrAppTy|_|) (ty: TType) = match stripTyparEqns ty with - | TType_app (tcref, _, _) -> Some tcref + | TType_app (tcref, tinst, _) -> Some(tcref, tinst) | _ -> None //--------------------------------------------------------------------------- diff --git a/src/Compiler/TypedTree/TypedTreeBasics.fsi b/src/Compiler/TypedTree/TypedTreeBasics.fsi index 8a73a609316..ea1f5979262 100644 --- a/src/Compiler/TypedTree/TypedTreeBasics.fsi +++ b/src/Compiler/TypedTree/TypedTreeBasics.fsi @@ -135,7 +135,7 @@ val stripTyparEqns: ty: TType -> TType val stripUnitEqns: unt: Measure -> Measure /// Detect a use of a nominal type, including type abbreviations. -val (|AbbrevOrAppTy|_|): ty: TType -> TyconRef option +val (|AbbrevOrAppTy|_|): ty: TType -> (TyconRef * TypeInst) option val mkLocalValRef: v: Val -> ValRef diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/UnionTypes/UnionTypes.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/UnionTypes/UnionTypes.fs index ea40ea51cb1..e30a31edacf 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/UnionTypes/UnionTypes.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/UnionTypes/UnionTypes.fs @@ -609,3 +609,43 @@ module UnionTypes = |> withDiagnostics [ (Warning 42, Line 11, Col 12, Line 11, Col 24, "This construct is deprecated: it is only for use in the F# library") ] + + + //SOURCE=W_UnionCaseProduction01.fsx SCFLAGS="-a --test:ErrorRanges" # W_UnionCaseProduction01.fsx + [] + let ``UnionCaseInitialization_repro16431`` () = + + let testFs = + SourceCodeFileKind.Create( + "testFs.fs", + $""" +module Test + +type ABC = + | A + | B + | C of int + + static let c75' = ABC.C 75 + static member c75 = c75' + + static let ab' = [ A; B ] + static member ab = ab' + """) + + let programFs = + SourceCodeFileKind.Create( + "programFs.fs", + $""" +open Test + +if (sprintf "%%A" ABC.c75) <> "C 75" then failwith (sprintf "Failed: printing 'ABC.c75': Expected output: 'C 75' Actual output: '%%A'" ABC.c75) +if (sprintf "%%A" ABC.ab) <> "[A; B]" then failwith (sprintf "Failed: printing 'ABC.ab: Expected: '[A; B]' Actual: '%%A'" ABC.ab) + """) + + (fsFromString testFs) + |> FS + |> withAdditionalSourceFiles [programFs] + |> asExe + |> compileAndRun + |> shouldSucceed diff --git a/tests/FSharp.Compiler.ComponentTests/ErrorMessages/TailCallAttribute.fs b/tests/FSharp.Compiler.ComponentTests/ErrorMessages/TailCallAttribute.fs index 9bad5b4d0af..56572879715 100644 --- a/tests/FSharp.Compiler.ComponentTests/ErrorMessages/TailCallAttribute.fs +++ b/tests/FSharp.Compiler.ComponentTests/ErrorMessages/TailCallAttribute.fs @@ -1512,3 +1512,29 @@ module Microsoft.FSharp.Core Message = "The member or function 'f' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way." } ] + + [] + let ``Warn for recursive call in list comprehension`` () = + """ +namespace N + + module M = + + [] + let rec reverse (input: list<'t>) = + match input with + | head :: tail -> [ yield! reverse tail; head ] + | [] -> [] + """ + |> FSharp + |> compile + |> shouldFail + |> withResults [ + { Error = Warning 3569 + Range = { StartLine = 9 + StartColumn = 40 + EndLine = 9 + EndColumn = 52 } + Message = + "The member or function 'reverse' has the 'TailCallAttribute' attribute, but is not being used in a tail recursive way." } + ] diff --git a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs index 5ff288185c4..664b79d4e0e 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs +++ b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/TransparentCompiler.fs @@ -842,3 +842,57 @@ let ``TypeCheck last file in project with transparent compiler`` useTransparentC clearCache checkFile lastFile expectOk } + +[] +let ``LoadClosure for script is computed once`` () = + let project = SyntheticProject.CreateForScript( + sourceFile "First" []) + + let cacheEvents = ConcurrentQueue() + + ProjectWorkflowBuilder(project, useTransparentCompiler = true) { + withChecker (fun checker -> + async { + do! Async.Sleep 50 // wait for events from initial project check + checker.Caches.ScriptClosure.OnEvent cacheEvents.Enqueue + }) + + checkFile "First" expectOk + } |> ignore + + let closureComputations = + cacheEvents + |> Seq.groupBy (fun (_e, (_l, (f, _p), _)) -> Path.GetFileName f) + |> Seq.map (fun (k, g) -> k, g |> Seq.map fst |> Seq.toList) + |> Map + + Assert.Empty(closureComputations) + +[] +let ``LoadClosure for script is recomputed after changes`` () = + let project = SyntheticProject.CreateForScript( + sourceFile "First" []) + + let cacheEvents = ConcurrentQueue() + + ProjectWorkflowBuilder(project, useTransparentCompiler = true) { + withChecker (fun checker -> + async { + do! Async.Sleep 50 // wait for events from initial project check + checker.Caches.ScriptClosure.OnEvent cacheEvents.Enqueue + }) + + checkFile "First" expectOk + updateFile "First" updateInternal + checkFile "First" expectOk + updateFile "First" updatePublicSurface + checkFile "First" expectOk + } |> ignore + + let closureComputations = + cacheEvents + |> Seq.groupBy (fun (_e, (_l, (f, _p), _)) -> Path.GetFileName f) + |> Seq.map (fun (k, g) -> k, g |> Seq.map fst |> Seq.toList) + |> Map + + Assert.Equal([Weakened; Requested; Started; Finished; Weakened; Requested; Started; Finished], closureComputations["FileFirst.fs"]) \ No newline at end of file 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 b6eeea8f992..e53606d2de3 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 @@ -4868,8 +4868,10 @@ FSharp.Compiler.Symbols.FSharpEntity: System.Collections.Generic.IList`1[FSharp. FSharp.Compiler.Symbols.FSharpEntity: System.Collections.Generic.IList`1[FSharp.Compiler.Symbols.FSharpStaticParameter] get_StaticParameters() FSharp.Compiler.Symbols.FSharpEntity: System.Collections.Generic.IList`1[FSharp.Compiler.Symbols.FSharpType] AllInterfaces FSharp.Compiler.Symbols.FSharpEntity: System.Collections.Generic.IList`1[FSharp.Compiler.Symbols.FSharpType] DeclaredInterfaces +FSharp.Compiler.Symbols.FSharpEntity: System.Collections.Generic.IList`1[FSharp.Compiler.Symbols.FSharpType] GenericArguments FSharp.Compiler.Symbols.FSharpEntity: System.Collections.Generic.IList`1[FSharp.Compiler.Symbols.FSharpType] get_AllInterfaces() FSharp.Compiler.Symbols.FSharpEntity: System.Collections.Generic.IList`1[FSharp.Compiler.Symbols.FSharpType] get_DeclaredInterfaces() +FSharp.Compiler.Symbols.FSharpEntity: System.Collections.Generic.IList`1[FSharp.Compiler.Symbols.FSharpType] get_GenericArguments() FSharp.Compiler.Symbols.FSharpEntity: System.Collections.Generic.IList`1[FSharp.Compiler.Symbols.FSharpUnionCase] UnionCases FSharp.Compiler.Symbols.FSharpEntity: System.Collections.Generic.IList`1[FSharp.Compiler.Symbols.FSharpUnionCase] get_UnionCases() FSharp.Compiler.Symbols.FSharpEntity: System.String AccessPath 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 b6eeea8f992..e53606d2de3 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 @@ -4868,8 +4868,10 @@ FSharp.Compiler.Symbols.FSharpEntity: System.Collections.Generic.IList`1[FSharp. FSharp.Compiler.Symbols.FSharpEntity: System.Collections.Generic.IList`1[FSharp.Compiler.Symbols.FSharpStaticParameter] get_StaticParameters() FSharp.Compiler.Symbols.FSharpEntity: System.Collections.Generic.IList`1[FSharp.Compiler.Symbols.FSharpType] AllInterfaces FSharp.Compiler.Symbols.FSharpEntity: System.Collections.Generic.IList`1[FSharp.Compiler.Symbols.FSharpType] DeclaredInterfaces +FSharp.Compiler.Symbols.FSharpEntity: System.Collections.Generic.IList`1[FSharp.Compiler.Symbols.FSharpType] GenericArguments FSharp.Compiler.Symbols.FSharpEntity: System.Collections.Generic.IList`1[FSharp.Compiler.Symbols.FSharpType] get_AllInterfaces() FSharp.Compiler.Symbols.FSharpEntity: System.Collections.Generic.IList`1[FSharp.Compiler.Symbols.FSharpType] get_DeclaredInterfaces() +FSharp.Compiler.Symbols.FSharpEntity: System.Collections.Generic.IList`1[FSharp.Compiler.Symbols.FSharpType] get_GenericArguments() FSharp.Compiler.Symbols.FSharpEntity: System.Collections.Generic.IList`1[FSharp.Compiler.Symbols.FSharpUnionCase] UnionCases FSharp.Compiler.Symbols.FSharpEntity: System.Collections.Generic.IList`1[FSharp.Compiler.Symbols.FSharpUnionCase] get_UnionCases() FSharp.Compiler.Symbols.FSharpEntity: System.String AccessPath diff --git a/tests/FSharp.Test.Utilities/ProjectGeneration.fs b/tests/FSharp.Test.Utilities/ProjectGeneration.fs index 144e535bccb..2236eee988d 100644 --- a/tests/FSharp.Test.Utilities/ProjectGeneration.fs +++ b/tests/FSharp.Test.Utilities/ProjectGeneration.fs @@ -240,7 +240,8 @@ type SyntheticProject = NugetReferences: Reference list FrameworkReferences: Reference list /// If set to true this project won't cause an exception if there are errors in the initial check - SkipInitialCheck: bool } + SkipInitialCheck: bool + UseScriptResolutionRules: bool } static member Create(?name: string) = let name = defaultArg name $"TestProject_{Guid.NewGuid().ToString()[..7]}" @@ -255,13 +256,17 @@ type SyntheticProject = AutoAddModules = true NugetReferences = [] FrameworkReferences = [] - SkipInitialCheck = false } + SkipInitialCheck = false + UseScriptResolutionRules = false } static member Create([] sourceFiles: SyntheticSourceFile[]) = { SyntheticProject.Create() with SourceFiles = sourceFiles |> List.ofArray } static member Create(name: string, [] sourceFiles: SyntheticSourceFile[]) = { SyntheticProject.Create(name) with SourceFiles = sourceFiles |> List.ofArray } + + static member CreateForScript(scriptFile: SyntheticSourceFile) = + { SyntheticProject.Create() with SourceFiles = [scriptFile]; UseScriptResolutionRules = true } member this.Find fileId = this.SourceFiles @@ -339,7 +344,7 @@ type SyntheticProject = [| for p in this.DependsOn do FSharpReferencedProject.FSharpReference(p.OutputFilename, p.GetProjectOptions checker) |] IsIncompleteTypeCheckEnvironment = false - UseScriptResolutionRules = false + UseScriptResolutionRules = this.UseScriptResolutionRules LoadTime = DateTime() UnresolvedReferences = None OriginalLoadReferences = [] diff --git a/tests/service/Symbols.fs b/tests/service/Symbols.fs index b3d6261d60a..a186b0b49f9 100644 --- a/tests/service/Symbols.fs +++ b/tests/service/Symbols.fs @@ -327,7 +327,7 @@ open System findSymbolUseByName "IDisposable" checkResults |> ignore - [] + [] let ``Interface 04 - Type arg`` () = let _, checkResults = getParseAndCheckResults """ open System.Collections.Generic @@ -335,7 +335,8 @@ open System.Collections.Generic IList """ let symbolUse = findSymbolUseByName "IList`1" checkResults - let _, typeArg = symbolUse.GenericArguments[0] + let symbol = symbolUse.Symbol :?> FSharpEntity + let typeArg = symbol.GenericArguments[0] typeArg.Format(symbolUse.DisplayContext) |> shouldEqual "int" [] @@ -351,7 +352,8 @@ type I<'T> = getSymbolUses checkResults |> Seq.findBack (fun symbolUse -> symbolUse.Symbol.DisplayName = "I") - let _, typeArg = symbolUse.GenericArguments[0] + let symbol = symbolUse.Symbol :?> FSharpEntity + let typeArg = symbol.GenericArguments[0] typeArg.Format(symbolUse.DisplayContext) |> shouldEqual "int" [] @@ -367,9 +369,18 @@ type I<'T> = getSymbolUses checkResults |> Seq.findBack (fun symbolUse -> symbolUse.Symbol.DisplayName = "I") - let _, typeArg = symbolUse.GenericArguments[0] + let symbol = symbolUse.Symbol :?> FSharpEntity + let typeArg = symbol.GenericArguments[0] typeArg.Format(symbolUse.DisplayContext) |> shouldEqual "int" + [] + let ``Operator 01 - Type arg`` () = + let _, checkResults = getParseAndCheckResults """ +[1] |> ignore +""" + let symbolUses = checkResults.GetAllUsesOfAllSymbolsInFile() + () + [] let ``FSharpType.Format can use prefix representations`` () = let _, checkResults = getParseAndCheckResults """ @@ -1203,3 +1214,24 @@ type T() = let i = 1 """ assertHasSymbolUsages ["i"] checkResults + +module Event = + [] + let ``CLIEvent member does not produce additional property symbol`` () = + let _, checkResults = getParseAndCheckResults """ +type T() = + [] + member this.Event = Event().Publish +""" + let allSymbols = + checkResults.GetSymbolUsesAtLocation(4, 21, " member this.Event = Event().Publish", [ "Event" ]) + + let hasPropertySymbols = + allSymbols + |> List.exists (fun su -> + match su.Symbol with + | :? FSharpMemberOrFunctionOrValue as mfv -> mfv.IsProperty + | _ -> false + ) + + Assert.False hasPropertySymbols diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs b/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs index 5b4e0e994f1..60cbf8ccd76 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs @@ -28,7 +28,38 @@ module private Patterns = [] module SourceText = - /// Returns true if the given span contains an expression + /// E.g., something like: + /// + /// let … = (␤ + /// … + /// ) + [] + let (|TrailingOpen|_|) (span: TextSpan) (sourceText: SourceText) = + let linePosition = sourceText.Lines.GetLinePosition span.Start + let line = (sourceText.Lines.GetLineFromPosition span.Start).ToString() + + if + line.AsSpan(0, linePosition.Character).LastIndexOfAnyExcept(' ', '(') >= 0 + && line.AsSpan(linePosition.Character).IndexOfAnyExcept('(', ' ') < 0 + then + ValueSome TrailingOpen + else + ValueNone + + /// Trim only spaces from the start if there is something else + /// before the open paren on the same line (or else we could move + /// the whole inner expression up a line); otherwise trim all whitespace + // from start and end. + let (|Trim|) (span: TextSpan) (sourceText: SourceText) = + let linePosition = sourceText.Lines.GetLinePosition span.Start + let line = (sourceText.Lines.GetLineFromPosition span.Start).ToString() + + if line.AsSpan(0, linePosition.Character).LastIndexOfAnyExcept(' ', '(') >= 0 then + fun (s: string) -> s.TrimEnd().TrimStart ' ' + else + fun (s: string) -> s.Trim() + + /// Returns the offsides diff if the given span contains an expression /// whose indentation would be made invalid if the open paren /// were removed (because the offside line would be shifted), e.g., /// @@ -43,70 +74,32 @@ module private Patterns = /// // Valid. /// ◌let x = 2 /// x◌ - let containsSensitiveIndentation (span: TextSpan) (sourceText: SourceText) = + [] + let (|OffsidesDiff|_|) (span: TextSpan) (sourceText: SourceText) = let startLinePosition = sourceText.Lines.GetLinePosition span.Start let endLinePosition = sourceText.Lines.GetLinePosition span.End - let startLine = startLinePosition.Line - let startCol = startLinePosition.Character - let endLine = endLinePosition.Line + let startLineNo = startLinePosition.Line + let endLineNo = endLinePosition.Line - if startLine = endLine then - false + if startLineNo = endLineNo then + ValueNone else - let rec loop offsides lineNo startCol = - if lineNo <= endLine then + let rec loop innerOffsides lineNo startCol = + if lineNo <= endLineNo then let line = sourceText.Lines[lineNo].ToString() - match offsides with - | ValueNone -> - let i = line.AsSpan(startCol).IndexOfAnyExcept(' ', ')') - - if i >= 0 then - loop (ValueSome(i + startCol)) (lineNo + 1) 0 - else - loop offsides (lineNo + 1) 0 - - | ValueSome offsidesCol -> - let i = line.AsSpan(0, min offsidesCol line.Length).IndexOfAnyExcept(' ', ')') - i <= offsidesCol || loop offsides (lineNo + 1) 0 + match line.AsSpan(startCol).IndexOfAnyExcept(' ', ')') with + | -1 -> loop innerOffsides (lineNo + 1) 0 + | i -> loop (i + startCol) (lineNo + 1) 0 else - false + ValueSome(startLinePosition.Character - innerOffsides) - loop ValueNone startLine startCol + loop startLinePosition.Character startLineNo (startLinePosition.Character + 1) - let hasPrecedingConstructOnSameLine (span: TextSpan) (sourceText: SourceText) = - let linePosition = sourceText.Lines.GetLinePosition span.Start - let line = (sourceText.Lines.GetLineFromPosition span.Start).ToString() - line.AsSpan(0, linePosition.Character).LastIndexOfAnyExcept(' ', '(') >= 0 - - let followingLineMovesOffsidesRightward (span: TextSpan) (sourceText: SourceText) = - let startLinePosition = sourceText.Lines.GetLinePosition span.Start - let startLine = startLinePosition.Line - let endLinePosition = sourceText.Lines.GetLinePosition span.End - let endLine = endLinePosition.Line - let offsides = startLinePosition.Character - - let rec loop lineNo = - if lineNo <= endLine then - let line = sourceText.Lines[lineNo].ToString().AsSpan() - let i = line.IndexOfAnyExcept("*/%-+:^@><=!|0$.?) ".AsSpan()) - i > offsides || loop (lineNo + 1) - else - false - - loop (startLine + 1) - - [] - let (|ContainsSensitiveIndentation|_|) span sourceText = - toPat (containsSensitiveIndentation span) sourceText - - [] - let (|HasPrecedingConstructOnSameLine|_|) span sourceText = - toPat (hasPrecedingConstructOnSameLine span) sourceText - - [] - let (|FollowingLineMovesOffsidesRightward|_|) span sourceText = - toPat (followingLineMovesOffsidesRightward span) sourceText + let (|ShiftLeft|NoShift|ShiftRight|) n = + if n < 0 then ShiftLeft -n + elif n = 0 then NoShift + else ShiftRight n [] type internal FSharpRemoveUnnecessaryParenthesesCodeFixProvider [] () = @@ -154,52 +147,51 @@ type internal FSharpRemoveUnnecessaryParenthesesCodeFixProvider [ - let (|ShouldPutSpaceBefore|_|) (s: string) = - // "……(……)" - // ↑↑ ↑ - match sourceText[max (context.Span.Start - 2) 0], sourceText[max (context.Span.Start - 1) 0], s[1] with - | _, _, ('\n' | '\r') -> None - | '[', '|', (Punctuation | LetterOrDigit) -> None - | _, '[', '<' -> Some ShouldPutSpaceBefore - | _, ('(' | '[' | '{'), _ -> None - | _, '>', _ -> Some ShouldPutSpaceBefore - | ' ', '=', _ -> Some ShouldPutSpaceBefore - | _, '=', ('(' | '[' | '{') -> None - | _, '=', (Punctuation | Symbol) -> Some ShouldPutSpaceBefore - | _, LetterOrDigit, '(' -> None - | _, (LetterOrDigit | '`'), _ -> Some ShouldPutSpaceBefore - | _, (Punctuation | Symbol), (Punctuation | Symbol) -> Some ShouldPutSpaceBefore - | _ -> None - - let (|ShouldPutSpaceAfter|_|) (s: string) = - // "(……)…" - // ↑ ↑ - match s[s.Length - 2], sourceText[min context.Span.End (sourceText.Length - 1)] with - | '>', ('|' | ']') -> Some ShouldPutSpaceAfter - | _, (')' | ']' | '[' | '}' | '.' | ';' | ',' | '|') -> None - | (Punctuation | Symbol), (Punctuation | Symbol | LetterOrDigit) -> Some ShouldPutSpaceAfter - | LetterOrDigit, LetterOrDigit -> Some ShouldPutSpaceAfter - | _ -> None - - let (|NewOffsidesOnFirstLine|_|) (s: string) = - let s = s.AsSpan 1 // (… - let newline = s.IndexOfAny('\n', '\r') - - if newline < 0 || s.Slice(0, newline).IndexOfAnyExcept(@"\r\n ".AsSpan()) >= 0 then - Some NewOffsidesOnFirstLine - else - None + let adjusted = + match sourceText with + | TrailingOpen context.Span -> txt[1 .. txt.Length - 2].TrimEnd() + + | Trim context.Span trim & OffsidesDiff context.Span spaces -> + match spaces with + | NoShift -> trim txt[1 .. txt.Length - 2] + | ShiftLeft spaces -> trim (txt[1 .. txt.Length - 2].Replace("\n" + String(' ', spaces), "\n")) + | ShiftRight spaces -> trim (txt[1 .. txt.Length - 2].Replace("\n", "\n" + String(' ', spaces))) + + | _ -> txt[1 .. txt.Length - 2].Trim() let newText = - match txt, sourceText with - | ShouldPutSpaceBefore & ShouldPutSpaceAfter, _ -> " " + txt[1 .. txt.Length - 2] + " " - | ShouldPutSpaceBefore, _ -> " " + txt[1 .. txt.Length - 2] - | ShouldPutSpaceAfter, _ -> txt[1 .. txt.Length - 2] + " " - | NewOffsidesOnFirstLine, - ContainsSensitiveIndentation context.Span & (HasPrecedingConstructOnSameLine context.Span | FollowingLineMovesOffsidesRightward context.Span) -> - txt[1 .. txt.Length - 2].Replace("\n ", "\n") - | NewOffsidesOnFirstLine, ContainsSensitiveIndentation context.Span -> " " + txt[1 .. txt.Length - 2] - | _ -> txt[1 .. txt.Length - 2] + let (|ShouldPutSpaceBefore|_|) (s: string) = + // "……(……)" + // ↑↑ ↑ + match sourceText[max (context.Span.Start - 2) 0], sourceText[max (context.Span.Start - 1) 0], s[0] with + | _, _, ('\n' | '\r') -> None + | '[', '|', (Punctuation | LetterOrDigit) -> None + | _, '[', '<' -> Some ShouldPutSpaceBefore + | _, ('(' | '[' | '{'), _ -> None + | _, '>', _ -> Some ShouldPutSpaceBefore + | ' ', '=', _ -> Some ShouldPutSpaceBefore + | _, '=', ('(' | '[' | '{') -> None + | _, '=', (Punctuation | Symbol) -> Some ShouldPutSpaceBefore + | _, LetterOrDigit, '(' -> None + | _, (LetterOrDigit | '`'), _ -> Some ShouldPutSpaceBefore + | _, (Punctuation | Symbol), (Punctuation | Symbol) -> Some ShouldPutSpaceBefore + | _ -> None + + let (|ShouldPutSpaceAfter|_|) (s: string) = + // "(……)…" + // ↑ ↑ + match s[s.Length - 1], sourceText[min context.Span.End (sourceText.Length - 1)] with + | '>', ('|' | ']') -> Some ShouldPutSpaceAfter + | _, (')' | ']' | '[' | '}' | '.' | ';' | ',' | '|') -> None + | (Punctuation | Symbol), (Punctuation | Symbol | LetterOrDigit) -> Some ShouldPutSpaceAfter + | LetterOrDigit, LetterOrDigit -> Some ShouldPutSpaceAfter + | _ -> None + + match adjusted with + | ShouldPutSpaceBefore & ShouldPutSpaceAfter -> " " + adjusted + " " + | ShouldPutSpaceBefore -> " " + adjusted + | ShouldPutSpaceAfter -> adjusted + " " + | adjusted -> adjusted return ValueSome diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs index cd3bbdd2bfc..ff57ffac6d8 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs @@ -244,7 +244,6 @@ let _ = async { return 1 - } " @@ -343,7 +342,7 @@ let _ = " let x = 2 - + 2 + + 2 in x " @@ -362,9 +361,9 @@ let _ = 2 - - - + 2 + + + + 2 in x " @@ -399,7 +398,7 @@ let _ = ", " let x = 2 - + 2 + + 2 in x " @@ -410,7 +409,7 @@ let _ = ", " let x = 2 - + 2 + + 2 in x " @@ -421,7 +420,7 @@ let _ = ", " let x = x - +y + +y in x " @@ -443,7 +442,7 @@ let _ = ", " let x = 2 - <<< 2 + <<< 2 in x " @@ -456,7 +455,7 @@ in x " let (<<<<<<<<) = (<<<) let x = 2 -<<<<<<<< 2 + <<<<<<<< 2 in x " @@ -506,7 +505,7 @@ in x let y = 2 - + 2 + + 2 in x + y " @@ -533,8 +532,7 @@ in x " x < 2 - + 3 - + + 3 " // LetOrUse @@ -543,6 +541,83 @@ in x "let x = 3 in let y =(4) in x + y", "let x = 3 in let y = 4 in x + y" "let x = 3 in let y=(4) in x + y", "let x = 3 in let y=4 in x + y" + " + let _ = + let _ = 3 + ( + 44 + ) + ", + " + let _ = + let _ = 3 + 44 + " + + " + let (++++++) = (+) + let x = + () + ( + 2 + ++++++ 2 + ) + in x + ", + " + let (++++++) = (+) + let x = + () + 2 + ++++++ 2 + in x + " + + " + let (!+++++) = (+) + let x = + () + ( + !+++++2 + ) + in x + ", + " + let (!+++++) = (+) + let x = + () + !+++++2 + in x + " + + " + let _ = + ( + printfn \"\" + +2 + ) + ", + " + let _ = + printfn \"\" + +2 + " + + " + let _ = + let x = 3 + ( + let y = 99 + y - x + ) + ", + " + let _ = + let x = 3 + let y = 99 + y - x + " + // TryWith "try (raise null) with _ -> reraise ()", "try raise null with _ -> reraise ()" "try raise null with (_) -> reraise ()", "try raise null with _ -> reraise ()" @@ -569,6 +644,82 @@ in x """ printfn "1"; (printfn "2") """, """ printfn "1"; printfn "2" """ "let x = 3; (5) in x", "let x = 3; 5 in x" + " + let _ = + let y = 100 + let x = 3 + ( + let y = 99 + ignore (y - x) + ) + x + y + ", + " + let _ = + let y = 100 + let x = 3 + ( + let y = 99 + ignore (y - x) + ) + x + y + " + + " + let _ = + let y = 100 + let x = 3 + ( + let y = 99 + ignore (y - x) + ) + x + ", + " + let _ = + let y = 100 + let x = 3 + let y = 99 + ignore (y - x) + x + " + + " + let f y = + let x = 3 + ( + let y = 99 + ignore (y - x) + ) + x + y + ", + " + let f y = + let x = 3 + ( + let y = 99 + ignore (y - x) + ) + x + y + " + + " + let f y = + let x = 3 + ( + let y = 99 + ignore (y - x) + ) + x + ", + " + let f y = + let x = 3 + let y = 99 + ignore (y - x) + x + " + // IfThenElse "if (3 = 3) then 3 else 3", "if 3 = 3 then 3 else 3" "if 3 = 3 then (3) else 3", "if 3 = 3 then 3 else 3" @@ -655,7 +806,7 @@ in x """ let mutable x = 3 x <- 3 - <<< 3 + <<< 3 """ // DotIndexedGet @@ -818,6 +969,10 @@ in x "$\"{(1, 2):N0}\"", "$\"{(1, 2):N0}\"" "$\"{(1, 2),-3}\"", "$\"{(1, 2),-3}\"" + "$\"{((); ())}\"", "$\"{((); ())}\"" + "$\"{(do (); ())}\"", "$\"{do (); ()}\"" + "$\"{(let x = 3 in ignore x; 99)}\"", "$\"{let x = 3 in ignore x; 99}\"" + """ $"{(3 + LanguagePrimitives.GenericZero):N0}" """,