diff --git a/src/Compiler/Checking/CheckExpressions.fs b/src/Compiler/Checking/CheckExpressions.fs index b031c119319e..5c2239b073e4 100644 --- a/src/Compiler/Checking/CheckExpressions.fs +++ b/src/Compiler/Checking/CheckExpressions.fs @@ -5643,11 +5643,17 @@ and TcExprUndelayed (cenv: cenv) (overallTy: OverallTy) env tpenv (synExpr: SynE TcNonControlFlowExpr env <| fun env -> TcExprTuple cenv overallTy env tpenv (isExplicitStruct, args, m) - | SynExpr.AnonRecd (isStruct, withExprOpt, unsortedFieldExprs, mWholeExpr, _) -> - TcNonControlFlowExpr env <| fun env -> - TcPossiblyPropagatingExprLeafThenConvert (fun ty -> isAnonRecdTy g ty || isTyparTy g ty) cenv overallTy env mWholeExpr (fun overallTy -> - TcAnonRecdExpr cenv overallTy env tpenv (isStruct, withExprOpt, unsortedFieldExprs, mWholeExpr) - ) + | SynExpr.AnonRecd (isStruct, withExprOpt, unsortedFieldExprs, mWholeExpr, trivia) -> + match withExprOpt with + | None + | Some(SynExpr.Ident _, _) -> + TcNonControlFlowExpr env <| fun env -> + TcPossiblyPropagatingExprLeafThenConvert (fun ty -> isAnonRecdTy g ty || isTyparTy g ty) cenv overallTy env mWholeExpr (fun overallTy -> + TcAnonRecdExpr cenv overallTy env tpenv (isStruct, withExprOpt, unsortedFieldExprs, mWholeExpr) + ) + | Some withExpr -> + BindOriginalRecdExpr withExpr (fun withExpr -> SynExpr.AnonRecd (isStruct, withExpr, unsortedFieldExprs, mWholeExpr, trivia)) + |> TcExpr cenv overallTy env tpenv | SynExpr.ArrayOrList (isArray, args, m) -> TcNonControlFlowExpr env <| fun env -> @@ -5673,8 +5679,14 @@ and TcExprUndelayed (cenv: cenv) (overallTy: OverallTy) env tpenv (synExpr: SynE TcExprObjectExpr cenv overallTy env tpenv (synObjTy, argopt, binds, extraImpls, mNewExpr, m) | SynExpr.Record (inherits, withExprOpt, synRecdFields, mWholeExpr) -> - TcNonControlFlowExpr env <| fun env -> - TcExprRecord cenv overallTy env tpenv (inherits, withExprOpt, synRecdFields, mWholeExpr) + match withExprOpt with + | None + | Some(SynExpr.Ident _, _) -> + TcNonControlFlowExpr env <| fun env -> + TcExprRecord cenv overallTy env tpenv (inherits, withExprOpt, synRecdFields, mWholeExpr) + | Some withExpr -> + BindOriginalRecdExpr withExpr (fun withExpr -> SynExpr.Record (inherits, withExpr, synRecdFields, mWholeExpr)) + |> TcExpr cenv overallTy env tpenv | SynExpr.While (spWhile, synGuardExpr, synBodyExpr, m) -> TcExprWhileLoop cenv overallTy env tpenv (spWhile, synGuardExpr, synBodyExpr, m) @@ -6813,6 +6825,12 @@ and TcObjectExprBinding (cenv: cenv) (env: TcEnv) implTy tpenv (absSlotInfo, bin let logicalMethId = id let memberFlags = OverrideMemberFlags SynMemberKind.Member bindingRhs, logicalMethId, memberFlags + + | SynPat.Named (SynIdent(id,_), _, _, _), Some memberFlags -> + CheckMemberFlags None NewSlotsOK OverridesOK memberFlags mBinding + let bindingRhs = PushOnePatternToRhs cenv true (mkSynThisPatVar (ident (CompilerGeneratedName "this", id.idRange))) bindingRhs + let logicalMethId = id + bindingRhs, logicalMethId, memberFlags | SynPat.InstanceMember(thisId, memberId, _, _, _), Some memberFlags -> CheckMemberFlags None NewSlotsOK OverridesOK memberFlags mBinding @@ -7018,7 +7036,7 @@ and TcObjectExpr (cenv: cenv) env tpenv (objTy, realObjTy, argopt, binds, extraI DispatchSlotChecking.CheckOverridesAreAllUsedOnce (env.DisplayEnv, g, cenv.infoReader, true, implTy, dispatchSlotsKeyed, availPriorOverrides, overrideSpecs) - DispatchSlotChecking.CheckDispatchSlotsAreImplemented (env.DisplayEnv, cenv.infoReader, m, env.NameEnv, cenv.tcSink, false, implTy, dispatchSlots, availPriorOverrides, overrideSpecs) |> ignore) + DispatchSlotChecking.CheckDispatchSlotsAreImplemented (env.DisplayEnv, cenv.infoReader, m, env.NameEnv, cenv.tcSink, false, true, implTy, dispatchSlots, availPriorOverrides, overrideSpecs) |> ignore) // 3. create the specs of overrides let allTypeImpls = @@ -7026,9 +7044,9 @@ and TcObjectExpr (cenv: cenv) env tpenv (objTy, realObjTy, argopt, binds, extraI let overrides' = [ for overrideMeth in overrides do let overrideInfo, (_, thisVal, methodVars, bindingAttribs, bindingBody) = overrideMeth - let (Override(_, _, id, mtps, _, _, _, isFakeEventProperty, _, _)) = overrideInfo + let (Override(_, _, id, mtps, _, _, _, isFakeEventProperty, _, isInstance)) = overrideInfo - if not isFakeEventProperty then + if not isFakeEventProperty && isInstance then let searchForOverride = dispatchSlotsKeyed |> NameMultiMap.find id.idText @@ -11249,7 +11267,7 @@ and ApplyAbstractSlotInference (cenv: cenv) (envinner: TcEnv) (_: Val option) (a if instanceExpected then errorR(Error(FSComp.SR.tcNoMemberFoundForOverride(), memberId.idRange)) else - errorR (Error(FSComp.SR.tcNoStaticMemberFoundForOverride (), memberId.idRange)) + errorR(Error(FSComp.SR.tcNoStaticMemberFoundForOverride (), memberId.idRange)) [] | slot :: _ as slots -> diff --git a/src/Compiler/Checking/CheckRecordSyntaxHelpers.fs b/src/Compiler/Checking/CheckRecordSyntaxHelpers.fs index a1d36d635931..8825b05a567b 100644 --- a/src/Compiler/Checking/CheckRecordSyntaxHelpers.fs +++ b/src/Compiler/Checking/CheckRecordSyntaxHelpers.fs @@ -11,6 +11,9 @@ open FSharp.Compiler.SyntaxTreeOps open FSharp.Compiler.Text.Position open FSharp.Compiler.Text.Range open FSharp.Compiler.TypedTree +open FSharp.Compiler.Xml +open FSharp.Compiler.SyntaxTrivia +open TypedTreeOps /// Merges updates to nested record fields on the same level in record copy-and-update. /// @@ -146,3 +149,29 @@ let TransformAstForNestedUpdates (cenv: TcFileState) (env: TcEnv) overallTy (lid (accessIds, outerFieldId), Some(synExprRecd (recdExprCopyInfo (fields |> List.map fst) withExpr) outerFieldId rest exprBeingAssigned) + +/// When the original expression in copy-and-update is more complex than `{ x with ... }`, like `{ f () with ... }`, +/// we bind it first, so that it's not evaluated multiple times during a nested update +let BindOriginalRecdExpr (withExpr: SynExpr * BlockSeparator) mkRecdExpr = + let originalExpr, blockSep = withExpr + let mOrigExprSynth = originalExpr.Range.MakeSynthetic() + let id = mkSynId mOrigExprSynth "bind@" + let withExpr = SynExpr.Ident id, blockSep + + let binding = + mkSynBinding + (PreXmlDoc.Empty, mkSynPatVar None id) + (None, + false, + false, + mOrigExprSynth, + DebugPointAtBinding.NoneAtSticky, + None, + originalExpr, + mOrigExprSynth, + [], + [], + None, + SynBindingTrivia.Zero) + + SynExpr.LetOrUse(false, false, [ binding ], mkRecdExpr (Some withExpr), mOrigExprSynth, SynExprLetOrUseTrivia.Zero) diff --git a/src/Compiler/Checking/CheckRecordSyntaxHelpers.fsi b/src/Compiler/Checking/CheckRecordSyntaxHelpers.fsi index f239c8243618..4e4f40d75045 100644 --- a/src/Compiler/Checking/CheckRecordSyntaxHelpers.fsi +++ b/src/Compiler/Checking/CheckRecordSyntaxHelpers.fsi @@ -3,7 +3,6 @@ module internal FSharp.Compiler.CheckRecordSyntaxHelpers open FSharp.Compiler.CheckBasics -open FSharp.Compiler.NameResolution open FSharp.Compiler.Syntax open FSharp.Compiler.Text open FSharp.Compiler.TypedTree @@ -19,3 +18,6 @@ val TransformAstForNestedUpdates<'a> : exprBeingAssigned: SynExpr -> withExpr: SynExpr * (range * 'a) -> (Ident list * Ident) * SynExpr option + +val BindOriginalRecdExpr: + withExpr: SynExpr * BlockSeparator -> mkRecdExpr: ((SynExpr * BlockSeparator) option -> SynExpr) -> SynExpr diff --git a/src/Compiler/Checking/MethodOverrides.fs b/src/Compiler/Checking/MethodOverrides.fs index 080e790374e1..f467dc8db259 100644 --- a/src/Compiler/Checking/MethodOverrides.fs +++ b/src/Compiler/Checking/MethodOverrides.fs @@ -324,6 +324,7 @@ module DispatchSlotChecking = let CheckDispatchSlotsAreImplemented (denv, infoReader: InfoReader, m, nenv, sink: TcResultsSink, isOverallTyAbstract, + isObjExpr: bool, reqdTy, dispatchSlots: RequiredSlot list, availPriorOverrides: OverrideInfo list, @@ -331,7 +332,7 @@ module DispatchSlotChecking = let g = infoReader.g let amap = infoReader.amap - let isReqdTyInterface = isInterfaceTy g reqdTy + let isReqdTyInterface = isInterfaceTy g reqdTy let showMissingMethodsAndRaiseErrors = (isReqdTyInterface || not isOverallTyAbstract) let mutable res = true @@ -392,7 +393,7 @@ module DispatchSlotChecking = noimpl() | [ overrideBy ] -> - let (Override(_, _, _, methTypars, _, argTys, _, _, _, _)) = overrideBy + let (Override(_, _, _, methTypars, _, argTys, _, _, _, isInstance)) = overrideBy let moreThanOnePossibleDispatchSlot = dispatchSlots @@ -401,18 +402,21 @@ module DispatchSlotChecking = |> not let (CompiledSig (vargTys, _, fvmethTypars, _)) = compiledSig - - if moreThanOnePossibleDispatchSlot then - noimpl() - - elif argTys.Length <> vargTys.Length then - fail(Error(FSComp.SR.typrelMemberDoesNotHaveCorrectNumberOfArguments(FormatOverride denv overrideBy, FormatMethInfoSig g amap m denv dispatchSlot), overrideBy.Range)) - elif methTypars.Length <> fvmethTypars.Length then - fail(Error(FSComp.SR.typrelMemberDoesNotHaveCorrectNumberOfTypeParameters(FormatOverride denv overrideBy, FormatMethInfoSig g amap m denv dispatchSlot), overrideBy.Range)) - elif not (IsTyparKindMatch compiledSig overrideBy) then - fail(Error(FSComp.SR.typrelMemberDoesNotHaveCorrectKindsOfGenericParameters(FormatOverride denv overrideBy, FormatMethInfoSig g amap m denv dispatchSlot), overrideBy.Range)) - else - fail(Error(FSComp.SR.typrelMemberCannotImplement(FormatOverride denv overrideBy, NicePrint.stringOfMethInfo infoReader m denv dispatchSlot, FormatMethInfoSig g amap m denv dispatchSlot), overrideBy.Range)) + + // Object expressions can only implement instance members + let isObjExprWithInstanceMembers = (isObjExpr && isInstance) + if isObjExprWithInstanceMembers || isInstance then + if moreThanOnePossibleDispatchSlot then + noimpl() + + elif (argTys.Length <> vargTys.Length) then + fail(Error(FSComp.SR.typrelMemberDoesNotHaveCorrectNumberOfArguments(FormatOverride denv overrideBy, FormatMethInfoSig g amap m denv dispatchSlot), overrideBy.Range)) + elif methTypars.Length <> fvmethTypars.Length then + fail(Error(FSComp.SR.typrelMemberDoesNotHaveCorrectNumberOfTypeParameters(FormatOverride denv overrideBy, FormatMethInfoSig g amap m denv dispatchSlot), overrideBy.Range)) + elif not (IsTyparKindMatch compiledSig overrideBy) then + fail(Error(FSComp.SR.typrelMemberDoesNotHaveCorrectKindsOfGenericParameters(FormatOverride denv overrideBy, FormatMethInfoSig g amap m denv dispatchSlot), overrideBy.Range)) + else + fail(Error(FSComp.SR.typrelMemberCannotImplement(FormatOverride denv overrideBy, NicePrint.stringOfMethInfo infoReader m denv dispatchSlot, FormatMethInfoSig g amap m denv dispatchSlot), overrideBy.Range)) | overrideBy :: _ -> errorR(Error(FSComp.SR.typrelOverloadNotFound(FormatMethInfoSig g amap m denv dispatchSlot, FormatMethInfoSig g amap m denv dispatchSlot), overrideBy.Range)) @@ -599,11 +603,18 @@ module DispatchSlotChecking = match relevantVirts |> List.filter (fun dispatchSlot -> IsPartialMatch g dispatchSlot (CompiledSigOfMeth g amap m dispatchSlot) overrideBy) with | [dispatchSlot] -> errorR(OverrideDoesntOverride(denv, overrideBy, Some dispatchSlot, g, amap, m)) - | _ -> + | _ -> match relevantVirts |> List.filter (fun dispatchSlot -> IsNameMatch dispatchSlot overrideBy) with - | [] -> errorR(OverrideDoesntOverride(denv, overrideBy, None, g, amap, m)) - | [dispatchSlot] -> - errorR(OverrideDoesntOverride(denv, overrideBy, Some dispatchSlot, g, amap, m)) + | [] -> + if isObjExpr && not overrideBy.IsInstance then + errorR(Error(FSComp.SR.chkStaticMembersOnObjectExpressions(), overrideBy.Range)) + else + errorR(OverrideDoesntOverride(denv, overrideBy, None, g, amap, m)) + | [dispatchSlot] -> + if isObjExpr && not overrideBy.IsInstance then + errorR(Error(FSComp.SR.chkStaticMembersOnObjectExpressions(), overrideBy.Range)) + else + errorR(OverrideDoesntOverride(denv, overrideBy, Some dispatchSlot, g, amap, m)) | possibleDispatchSlots -> let details = possibleDispatchSlots @@ -820,7 +831,7 @@ module DispatchSlotChecking = if isImplementation && not (isInterfaceTy g overallTy) then let overrides = allImmediateMembersThatMightImplementDispatchSlots |> List.map snd - let allCorrect = CheckDispatchSlotsAreImplemented (denv, infoReader, m, nenv, sink, tcaug.tcaug_abstract, reqdTy, dispatchSlots, availPriorOverrides, overrides) + let allCorrect = CheckDispatchSlotsAreImplemented (denv, infoReader, m, nenv, sink, tcaug.tcaug_abstract, false, reqdTy, dispatchSlots, availPriorOverrides, overrides) // Tell the user to mark the thing abstract if it was missing implementations if not allCorrect && not tcaug.tcaug_abstract && not (isInterfaceTy g reqdTy) then diff --git a/src/Compiler/Checking/MethodOverrides.fsi b/src/Compiler/Checking/MethodOverrides.fsi index 6468c03e8b14..b06fb16e499e 100644 --- a/src/Compiler/Checking/MethodOverrides.fsi +++ b/src/Compiler/Checking/MethodOverrides.fsi @@ -113,6 +113,7 @@ module DispatchSlotChecking = nenv: NameResolutionEnv * sink: TcResultsSink * isOverallTyAbstract: bool * + isObjExpr: bool * reqdTy: TType * dispatchSlots: RequiredSlot list * availPriorOverrides: OverrideInfo list * diff --git a/src/Compiler/Driver/CompilerDiagnostics.fs b/src/Compiler/Driver/CompilerDiagnostics.fs index 34fd76eb1bae..ca66fa31d0b5 100644 --- a/src/Compiler/Driver/CompilerDiagnostics.fs +++ b/src/Compiler/Driver/CompilerDiagnostics.fs @@ -1551,7 +1551,7 @@ type Exception with // If implementation and required slot doesn't have same "instance-ness", then tell user that. if impl.IsInstance <> minfoVirt.IsInstance then - // Requried slot is instance, meaning implementation is static, tell user that we expect instance. + // Required slot is instance, meaning implementation is static, tell user that we expect instance. if minfoVirt.IsInstance then os.AppendString(OverrideShouldBeStatic().Format) else diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index fb476d40f213..c96c69291944 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1730,3 +1730,4 @@ featureUnmanagedConstraintCsharpInterop,"Interop between C#'s and F#'s unmanaged 3584,tcDotLambdaAtNotSupportedExpression,"Shorthand lambda syntax is only supported for atomic expressions, such as method, property, field or indexer on the implied '_' argument. For example: 'let f = _.Length'." 3855,tcNoStaticMemberFoundForOverride,"No static abstract member was found that corresponds to this override" 3859,tcNoStaticPropertyFoundForOverride,"No static abstract property was found that corresponds to this override" +3860,chkStaticMembersOnObjectExpressions,"Static members are not allowed in object expressions." \ No newline at end of file diff --git a/src/Compiler/Service/ServiceAnalysis.fs b/src/Compiler/Service/ServiceAnalysis.fs index 3bc2eeb973e0..bf856cfd95a6 100644 --- a/src/Compiler/Service/ServiceAnalysis.fs +++ b/src/Compiler/Service/ServiceAnalysis.fs @@ -456,6 +456,8 @@ module UnusedDeclarations = module UnnecessaryParentheses = open System + let (|Ident|) (ident: Ident) = ident.idText + /// Represents an expression's precedence, or, /// for a few few types of expression whose exact /// kind can be significant, the expression's exact kind. @@ -873,11 +875,27 @@ module UnnecessaryParentheses = | SynExpr.DotIndexedGet(objectExpr = SynExpr.Paren(expr = Is inner)) -> ValueSome(Dot, Left) | _ -> ValueNone + /// Matches a SynExpr.App nested in a sequence of dot-gets. + /// + /// x.M.N().O + [] + let (|NestedApp|_|) expr = + let rec loop = + function + | SynExpr.DotGet (expr = expr) + | SynExpr.DotIndexedGet (objectExpr = expr) -> loop expr + | SynExpr.App _ -> ValueSome NestedApp + | _ -> ValueNone + + loop expr + /// Returns the given expression's precedence, if applicable. [] let (|InnerBinaryExpr|_|) expr : Precedence voption = match expr with | SynExpr.Tuple(isStruct = false) -> ValueSome Comma + | SynExpr.DotGet(expr = NestedApp) + | SynExpr.DotIndexedGet(objectExpr = NestedApp) -> ValueSome Apply | SynExpr.DotGet _ | SynExpr.DotIndexedGet _ -> ValueSome Dot | PrefixApp prec -> ValueSome prec @@ -936,6 +954,13 @@ module UnnecessaryParentheses = | SynExpr.IfThenElse _ as expr -> Some expr | _ -> None) + /// Matches a dangling sequential expression. + [] + let (|Sequential|_|) = + dangling (function + | SynExpr.Sequential _ as expr -> Some expr + | _ -> None) + /// Matches a dangling try-with or try-finally construct. [] let (|Try|_|) = @@ -1192,6 +1217,19 @@ module UnnecessaryParentheses = SyntaxNode.SynExpr (SynExpr.App _) :: SyntaxNode.SynExpr (SynExpr.App(argExpr = SynExpr.ArrayOrListComputed(isArray = false))) :: _ -> ValueNone + // Parens must stay around binary equals expressions in argument + // position lest they be interpreted as named argument assignments: + // + // o.M((x = y)) + // o.N((x = y), z) + | SynExpr.Paren(expr = SynExpr.Paren(expr = InfixApp (Eq, _))), + SyntaxNode.SynExpr (SynExpr.App(funcExpr = SynExpr.LongIdent _)) :: _ + | SynExpr.Paren(expr = InfixApp (Eq, _)), + SyntaxNode.SynExpr (SynExpr.Paren _) :: SyntaxNode.SynExpr (SynExpr.App(funcExpr = SynExpr.LongIdent _)) :: _ + | SynExpr.Paren(expr = InfixApp (Eq, _)), + SyntaxNode.SynExpr (SynExpr.Tuple(isStruct = false)) :: SyntaxNode.SynExpr (SynExpr.Paren _) :: SyntaxNode.SynExpr (SynExpr.App(funcExpr = SynExpr.LongIdent _)) :: _ -> + ValueNone + // The :: operator is parsed differently from other symbolic infix operators, // so we need to give it special treatment. @@ -1249,6 +1287,8 @@ module UnnecessaryParentheses = match outer, inner with | ConfusableWithTypeApp, _ -> ValueNone + | SynExpr.IfThenElse _, Dangling.Sequential _ -> ValueNone + | SynExpr.IfThenElse (trivia = trivia), Dangling.IfThen ifThenElse when problematic ifThenElse.Range trivia.ThenKeyword || trivia.ElseKeyword |> Option.exists (problematic ifThenElse.Range) @@ -1258,15 +1298,44 @@ module UnnecessaryParentheses = | SynExpr.TryFinally (trivia = trivia), Dangling.Try tryExpr when problematic tryExpr.Range trivia.FinallyKeyword -> ValueNone - | (SynExpr.Match (clauses = clauses) | SynExpr.MatchLambda (matchClauses = clauses) | SynExpr.MatchBang (clauses = clauses)), - Dangling.Match matchOrTry when anyProblematic matchOrTry.Range clauses -> ValueNone + | SynExpr.Match (clauses = clauses; trivia = { WithKeyword = withKeyword }), Dangling.Match matchOrTry when + problematic matchOrTry.Range withKeyword + || anyProblematic matchOrTry.Range clauses + -> + ValueNone + + | SynExpr.MatchBang (clauses = clauses; trivia = { WithKeyword = withKeyword }), Dangling.Match matchOrTry when + problematic matchOrTry.Range withKeyword + || anyProblematic matchOrTry.Range clauses + -> + ValueNone + + | SynExpr.MatchLambda (matchClauses = clauses), Dangling.Match matchOrTry when anyProblematic matchOrTry.Range clauses -> + ValueNone | SynExpr.TryWith (withCases = clauses; trivia = trivia), Dangling.Match matchOrTry when - anyProblematic matchOrTry.Range clauses - || problematic matchOrTry.Range trivia.WithKeyword + problematic matchOrTry.Range trivia.WithKeyword + || anyProblematic matchOrTry.Range clauses -> ValueNone + | SynExpr.Sequential(expr1 = SynExpr.Paren(expr = Is inner)), Dangling.Problematic _ -> ValueNone + + | SynExpr.Paren _, SynExpr.Typed _ + | SynExpr.Quote _, SynExpr.Typed _ + | SynExpr.AnonRecd _, SynExpr.Typed _ + | SynExpr.Record _, SynExpr.Typed _ + | SynExpr.While(doExpr = SynExpr.Paren(expr = Is inner)), SynExpr.Typed _ + | SynExpr.WhileBang(doExpr = SynExpr.Paren(expr = Is inner)), SynExpr.Typed _ + | SynExpr.For(doBody = Is inner), SynExpr.Typed _ + | SynExpr.ForEach(bodyExpr = Is inner), SynExpr.Typed _ + | SynExpr.Match _, SynExpr.Typed _ + | SynExpr.Do _, SynExpr.Typed _ + | SynExpr.LetOrUse(body = Is inner), SynExpr.Typed _ + | SynExpr.TryWith _, SynExpr.Typed _ + | SynExpr.TryFinally _, SynExpr.Typed _ -> ValueSome range + | _, SynExpr.Typed _ -> ValueNone + | OuterBinaryExpr inner (outerPrecedence, side), InnerBinaryExpr innerPrecedence -> let ambiguous = match Precedence.compare outerPrecedence innerPrecedence with @@ -1295,31 +1364,6 @@ module UnnecessaryParentheses = | OuterBinaryExpr inner (_, Right), (SynExpr.Sequential _ | SynExpr.LetOrUse(trivia = { InKeyword = None })) -> ValueNone | OuterBinaryExpr inner (_, Right), inner -> if dangling inner then ValueNone else ValueSome range - | SynExpr.Typed _, SynExpr.Typed _ - | SynExpr.WhileBang(whileExpr = SynExpr.Paren(expr = Is inner)), SynExpr.Typed _ - | SynExpr.While(whileExpr = SynExpr.Paren(expr = Is inner)), SynExpr.Typed _ - | SynExpr.For(identBody = Is inner), SynExpr.Typed _ - | SynExpr.For(toBody = Is inner), SynExpr.Typed _ - | SynExpr.ForEach(enumExpr = Is inner), SynExpr.Typed _ - | SynExpr.ArrayOrList _, SynExpr.Typed _ - | SynExpr.ArrayOrListComputed _, SynExpr.Typed _ - | SynExpr.IndexRange _, SynExpr.Typed _ - | SynExpr.IndexFromEnd _, SynExpr.Typed _ - | SynExpr.ComputationExpr _, SynExpr.Typed _ - | SynExpr.Lambda _, SynExpr.Typed _ - | SynExpr.Assert _, SynExpr.Typed _ - | SynExpr.App _, SynExpr.Typed _ - | SynExpr.Lazy _, SynExpr.Typed _ - | SynExpr.LongIdentSet _, SynExpr.Typed _ - | SynExpr.DotSet _, SynExpr.Typed _ - | SynExpr.Set _, SynExpr.Typed _ - | SynExpr.DotIndexedSet _, SynExpr.Typed _ - | SynExpr.NamedIndexedPropertySet _, SynExpr.Typed _ - | SynExpr.Upcast _, SynExpr.Typed _ - | SynExpr.Downcast _, SynExpr.Typed _ - | SynExpr.AddressOf _, SynExpr.Typed _ - | SynExpr.JoinIn _, SynExpr.Typed _ -> ValueNone - // new T(expr) | SynExpr.New _, AtomicExprAfterType -> ValueSome range | SynExpr.New _, _ -> ValueNone @@ -1331,7 +1375,6 @@ module UnnecessaryParentheses = | _, SynExpr.Paren _ | _, SynExpr.Quote _ | _, SynExpr.Const _ - | _, SynExpr.Typed _ | _, SynExpr.Tuple(isStruct = true) | _, SynExpr.AnonRecd _ | _, SynExpr.ArrayOrList _ @@ -1378,29 +1421,51 @@ module UnnecessaryParentheses = | _ -> ValueNone module SynPat = + [] + let (|AnyTyped|_|) pats = + if + pats + |> List.exists (function + | SynPat.Typed _ -> true + | _ -> false) + then + ValueSome AnyTyped + else + ValueNone + /// If the given pattern is a parenthesized pattern and the parentheses /// are unnecessary in the given context, returns the unnecessary parentheses' range. let unnecessaryParentheses pat path = + let (|Last|) = List.last + match pat, path with // Parens are needed in: // // let (Pattern …) = … + // let (x: …, y…) = … + // let (x: …), (y: …) = … // let! (x: …) = … // and! (x: …) = … // use! (x: …) = … // _.member M(x: …) = … // match … with (x: …) -> … + // match … with (x, y: …) -> … // function (x: …) -> … // fun (x, y, …) -> … // fun (x: …) -> … // fun (Pattern …) -> … - | SynPat.Paren _, SyntaxNode.SynExpr (SynExpr.LetOrUseBang(pat = SynPat.Paren(pat = SynPat.Typed _))) :: _ - | SynPat.Paren _, SyntaxNode.SynMatchClause (SynMatchClause(pat = SynPat.Paren(pat = SynPat.Typed _))) :: _ - | SynPat.Paren(pat = SynPat.LongIdent _), SyntaxNode.SynBinding _ :: _ - | SynPat.Paren(pat = SynPat.LongIdent _), SyntaxNode.SynExpr (SynExpr.Lambda _) :: _ - | SynPat.Paren _, SyntaxNode.SynExpr (SynExpr.Lambda(args = SynSimplePats.SimplePats(pats = _ :: _ :: _))) :: _ - | SynPat.Paren _, SyntaxNode.SynExpr (SynExpr.Lambda(args = SynSimplePats.SimplePats(pats = [ SynSimplePat.Typed _ ]))) :: _ -> - ValueNone + | SynPat.Paren (SynPat.Typed _, _), SyntaxNode.SynExpr (SynExpr.LetOrUseBang _) :: _ + | SynPat.Paren (SynPat.Typed _, _), SyntaxNode.SynPat (SynPat.Tuple _) :: SyntaxNode.SynExpr (SynExpr.LetOrUseBang _) :: _ + | SynPat.Paren (SynPat.Tuple (isStruct = false; elementPats = AnyTyped), _), SyntaxNode.SynExpr (SynExpr.LetOrUseBang _) :: _ + | SynPat.Paren (SynPat.Typed _, _), SyntaxNode.SynMatchClause _ :: _ + | SynPat.Paren (SynPat.Typed _, _), SyntaxNode.SynPat (SynPat.Tuple _) :: SyntaxNode.SynMatchClause _ :: _ + | SynPat.Paren (SynPat.Tuple (isStruct = false; elementPats = Last (SynPat.Typed _)), _), SyntaxNode.SynMatchClause _ :: _ + | SynPat.Paren (SynPat.Typed _, _), SyntaxNode.SynPat (SynPat.Tuple _) :: SyntaxNode.SynBinding _ :: _ + | SynPat.Paren (SynPat.Tuple (isStruct = false; elementPats = AnyTyped), _), SyntaxNode.SynBinding _ :: _ + | SynPat.Paren (SynPat.LongIdent _, _), SyntaxNode.SynBinding _ :: _ + | SynPat.Paren (SynPat.LongIdent _, _), SyntaxNode.SynExpr (SynExpr.Lambda _) :: _ + | SynPat.Paren (SynPat.Tuple(isStruct = false), _), SyntaxNode.SynExpr (SynExpr.Lambda(parsedData = Some _)) :: _ + | SynPat.Paren (SynPat.Typed _, _), SyntaxNode.SynExpr (SynExpr.Lambda(parsedData = Some _)) :: _ -> ValueNone // () is parsed as this in certain cases… // @@ -1415,6 +1480,24 @@ module UnnecessaryParentheses = | SynPat.Paren (SynPat.Const (SynConst.Unit, _), _), SyntaxNode.SynExpr (SynExpr.LetOrUseBang _) :: _ | SynPat.Paren (SynPat.Const (SynConst.Unit, _), _), SyntaxNode.SynMatchClause _ :: _ -> ValueNone + // (()) is required when overriding a generic member + // where unit is the generic type argument: + // + // type C<'T> = abstract M : 'T -> unit + // let _ = { new C with override _.M (()) = () } + | SynPat.Paren (SynPat.Paren (SynPat.Const (SynConst.Unit, _), _), _), + SyntaxNode.SynPat (SynPat.LongIdent _) :: SyntaxNode.SynBinding _ :: _ + | SynPat.Paren (SynPat.Const (SynConst.Unit, _), _), + SyntaxNode.SynPat (SynPat.Paren _) :: SyntaxNode.SynPat (SynPat.LongIdent _) :: SyntaxNode.SynBinding _ :: _ -> ValueNone + + // Parens are required for the first of multiple additional constructors. + // We simply require them always. + // + // type T … = + // new (x) = … + // new (x, y) = … + | SynPat.Paren _, SyntaxNode.SynPat (SynPat.LongIdent(longDotId = SynLongIdent(id = [ Ident "new" ]))) :: _ -> ValueNone + // Parens are otherwise never needed in these cases: // // let (x: …) = … @@ -1437,7 +1520,9 @@ module UnnecessaryParentheses = | SynPat.Paren (inner, range), SyntaxNode.SynPat outer :: _ -> match outer, inner with // (x :: xs) :: ys - | SynPat.ListCons(lhsPat = SynPat.Paren(pat = Is inner)), SynPat.ListCons _ -> ValueNone + // (x, xs) :: ys + | SynPat.ListCons(lhsPat = SynPat.Paren(pat = Is inner)), SynPat.ListCons _ + | SynPat.ListCons(lhsPat = SynPat.Paren(pat = Is inner)), SynPat.Tuple(isStruct = false) -> ValueNone // A as (B | C) // A as (B & C) diff --git a/src/Compiler/pars.fsy b/src/Compiler/pars.fsy index 3a9e75023cd3..1e44ef874da7 100644 --- a/src/Compiler/pars.fsy +++ b/src/Compiler/pars.fsy @@ -4885,6 +4885,28 @@ atomicExpr: let trivia: SynExprDotLambdaTrivia = { UnderscoreRange = mUnderscore ; DotRange = mDot } SynExpr.DotLambda(expr, unionRanges mUnderscore expr.Range, trivia), false } + + | UNDERSCORE DOT recover %prec dot_lambda + { let mUnderscore = rhs parseState 1 + let mDot = rhs parseState 2 + let mWhole = unionRanges mUnderscore mDot + parseState.LexBuffer.CheckLanguageFeatureAndRecover LanguageFeature.AccessorFunctionShorthand mWhole + + let mExpr = mDot.EndRange + if not $3 then reportParseErrorAt mDot (FSComp.SR.parsUnexpectedEndOfFileExpression()) + + let expr = arbExpr ("dotLambda1", mExpr) + let trivia: SynExprDotLambdaTrivia = { UnderscoreRange = mUnderscore ; DotRange = mDot } + SynExpr.DotLambda(expr, mWhole, trivia), false } + + | UNDERSCORE recover %prec dot_lambda + { let mUnderscore = rhs parseState 1 + + if not $2 then reportParseErrorAt mUnderscore (FSComp.SR.parsUnexpectedEndOfFileExpression()) + + let expr = SynExpr.Ident(Ident("_", mUnderscore)) + SynExpr.FromParseError(expr, mUnderscore), false } + | atomicExpr HIGH_PRECEDENCE_BRACK_APP atomicExpr { let arg1, _ = $1 let arg2, hpa = $3 diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index a5c36b49cf11..64231d9d6942 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -122,6 +122,11 @@ Člen nebo funkce „{0}“ má atribut „TailCallAttribute“, ale nepoužívá se koncovým (tail) rekurzivním způsobem. + + Static members are not allowed in object expressions. + Static members are not allowed in object expressions. + + The 'AssemblyKeyNameAttribute' has been deprecated. Use 'AssemblyKeyFileAttribute' instead. Atribut AssemblyKeyNameAttribute je zastaralý. Použijte místo něj AssemblyKeyFileAttribute. diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index f0569b47861a..c689aaa58400 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -122,6 +122,11 @@ Der Member oder die Funktion "{0}" weist das Attribut "TailCallAttribute" auf, wird jedoch nicht endrekursiv verwendet. + + Static members are not allowed in object expressions. + Static members are not allowed in object expressions. + + The 'AssemblyKeyNameAttribute' has been deprecated. Use 'AssemblyKeyFileAttribute' instead. "AssemblyKeyNameAttribute" gilt als veraltet. Verwenden Sie stattdessen "AssemblyKeyFileAttribute". diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index 54932f9ebd7a..b4427cbcaf76 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -122,6 +122,11 @@ El miembro o la función “{0}” tiene el atributo “TailCallAttribute”, pero no se usa de forma de recursión de cola. + + Static members are not allowed in object expressions. + Static members are not allowed in object expressions. + + The 'AssemblyKeyNameAttribute' has been deprecated. Use 'AssemblyKeyFileAttribute' instead. El elemento "AssemblyKeyNameAttribute" está en desuso. Use "AssemblyKeyFileAttribute" en su lugar. diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index 0ce159902937..349e8ad63fdb 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -122,6 +122,11 @@ Le membre ou la fonction « {0} » possède l'attribut « TailCallAttribute », mais n'est pas utilisé de manière récursive. + + Static members are not allowed in object expressions. + Static members are not allowed in object expressions. + + The 'AssemblyKeyNameAttribute' has been deprecated. Use 'AssemblyKeyFileAttribute' instead. 'AssemblyKeyNameAttribute' a été déprécié. Utilisez 'AssemblyKeyFileAttribute' à la place. diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index 9a393fdf1759..55c55facb7e0 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -122,6 +122,11 @@ Il membro o la funzione "{0}" ha l'attributo "TailCallAttribute", ma non è in uso in modo ricorsivo finale. + + Static members are not allowed in object expressions. + Static members are not allowed in object expressions. + + The 'AssemblyKeyNameAttribute' has been deprecated. Use 'AssemblyKeyFileAttribute' instead. L'attributo 'AssemblyKeyNameAttribute' è deprecato. In alternativa, usare 'AssemblyKeyFileAttribute'. diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index f1c5e05a43e9..0c06cac0f883 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -122,6 +122,11 @@ メンバーまたは関数 '{0}' には 'TailCallAttribute' 属性がありますが、末尾の再帰的な方法では使用されていません。 + + Static members are not allowed in object expressions. + Static members are not allowed in object expressions. + + The 'AssemblyKeyNameAttribute' has been deprecated. Use 'AssemblyKeyFileAttribute' instead. 'AssemblyKeyNameAttribute' は非推奨になりました。代わりに 'AssemblyKeyFileAttribute' を使用してください。 diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index 029cdc7f4321..3d9583d32223 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -122,6 +122,11 @@ 멤버 또는 함수 '{0}'에 'TailCallAttribute' 특성이 있지만 비상 재귀적인 방식으로 사용되고 있지 않습니다. + + Static members are not allowed in object expressions. + Static members are not allowed in object expressions. + + The 'AssemblyKeyNameAttribute' has been deprecated. Use 'AssemblyKeyFileAttribute' instead. 'AssemblyKeyNameAttribute'는 사용되지 않습니다. 대신 'AssemblyKeyFileAttribute'를 사용하세요. diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index d858f1917fe7..65dca15b653e 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -122,6 +122,11 @@ Składowa lub funkcja „{0}” ma atrybut „TailCallAttribute”, ale nie jest używana w sposób cykliczny końca. + + Static members are not allowed in object expressions. + Static members are not allowed in object expressions. + + The 'AssemblyKeyNameAttribute' has been deprecated. Use 'AssemblyKeyFileAttribute' instead. Element „AssemblyKeyNameAttribute” jest przestarzały. Zamiast niego użyj elementu „AssemblyKeyFileAttribute”. diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index 7cc2f3c0394f..ba4b50a2c6a2 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -122,6 +122,11 @@ O membro ou a função "{0}" tem o atributo "TailCallAttribute", mas não está sendo usado de maneira recursiva em cauda. + + Static members are not allowed in object expressions. + Static members are not allowed in object expressions. + + The 'AssemblyKeyNameAttribute' has been deprecated. Use 'AssemblyKeyFileAttribute' instead. O 'AssemblyKeyNameAttribute' foi preterido. Use o 'AssemblyKeyFileAttribute'. diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index a427f079d690..e39639368872 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -122,6 +122,11 @@ Элемент или функция "{0}" содержит атрибут "TailCallAttribute", но не используется в рекурсивном хвостовом режиме. + + Static members are not allowed in object expressions. + Static members are not allowed in object expressions. + + The 'AssemblyKeyNameAttribute' has been deprecated. Use 'AssemblyKeyFileAttribute' instead. Атрибут "AssemblyKeyNameAttribute" является устаревшим. Используйте вместо него атрибут "AssemblyKeyFileAttribute". diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index f62bf1eb5a57..a1e87d37ec84 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -122,6 +122,11 @@ Üye veya '{0}' işlevi, 'TailCallAttribute' özniteliğine sahip ancak kuyruk özyinelemeli bir şekilde kullanılmıyor. + + Static members are not allowed in object expressions. + Static members are not allowed in object expressions. + + The 'AssemblyKeyNameAttribute' has been deprecated. Use 'AssemblyKeyFileAttribute' instead. 'AssemblyKeyNameAttribute' kullanım dışı bırakıldı. Bunun yerine 'AssemblyKeyFileAttribute' kullanın. diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index feb760e2cf54..a9ed1325b643 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -122,6 +122,11 @@ 成员或函数“{0}”具有 "TailCallAttribute" 属性,但未以尾递归方式使用。 + + Static members are not allowed in object expressions. + Static members are not allowed in object expressions. + + The 'AssemblyKeyNameAttribute' has been deprecated. Use 'AssemblyKeyFileAttribute' instead. "AssemblyKeyNameAttribute" 已被弃用。请改为使用 "AssemblyKeyFileAttribute"。 diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index 425eab1dce98..6bef41283f50 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -122,6 +122,11 @@ 成員或函式 '{0}' 具有 'TailCallAttribute' 屬性,但未以尾遞迴方式使用。 + + Static members are not allowed in object expressions. + Static members are not allowed in object expressions. + + The 'AssemblyKeyNameAttribute' has been deprecated. Use 'AssemblyKeyFileAttribute' instead. 'AssemblyKeyNameAttribute' 已淘汰。請改用 'AssemblyKeyFileAttribute'。 diff --git a/src/FSharp.DependencyManager.Nuget/README.md b/src/FSharp.DependencyManager.Nuget/README.md index b24fae7442ca..87f89e7e556b 100644 --- a/src/FSharp.DependencyManager.Nuget/README.md +++ b/src/FSharp.DependencyManager.Nuget/README.md @@ -1,17 +1,24 @@ -# `dotnet fsi` Dependency Manager Plugins +# F# Interactive Dependency Manager Plugins -Since .NET 5.0, `dotnet fsi` ships with dependency manager plugins support that can be called like so: +Since F# 5.0, `dotnet fsi` and `FsiAnyCPU.exe` (the .NET Framework variant) ships with dependency manager plugins support that can be called like so: ```fsharp #r "myextension: my extension parameters" ``` +For F# Interactive to load the extensions[^1], they either need to: +* be deployed next to `fsi.dll` of dotnet SDK (or next `FsiAnyCPU.exe` for .NET Framework) +* refered to via `--compilertool:`[^2] argument + +The same goes for the tooling hosting [F# Compiler Service](https://fsharp.github.io/fsharp-compiler-docs/fcs/). + The initial [RFC](https://github.com/fsharp/fslang-design/blob/main/tooling/FST-1027-fsi-references.md) for this feature overviews how it is designed. -The current implementation follows pattern that can be deducted by refering to [implementation in DependencyProvider.fs](https://github.com/dotnet/fsharp/blob/b9687a58cee795a94eb88cf84e309767cc25f6cb/src/Compiler/DependencyManager/DependencyProvider.fs#L145-L322); due to the system working without having a staticically linked dependency, it uses a late binding approach leveraging runtime reflection to identify if an extension conforms to patterns understood by the specifics of the compiler implementation. +**More about F# Interactive References** +The current implementation follows pattern that can be deducted by referring to [implementation in DependencyProvider.fs](https://github.com/dotnet/fsharp/blob/b9687a58cee795a94eb88cf84e309767cc25f6cb/src/Compiler/DependencyManager/DependencyProvider.fs#L145-L322); due to the system working without having a statically linked dependency, it uses a late binding approach leveraging runtime reflection to identify if an extension conforms to patterns understood by the specifics of the compiler implementation. # `#r "nuget:"` [nuget](https://github.com/dotnet/fsharp/tree/main/src/fsharp/FSharp.DependencyManager.Nuget) @@ -29,7 +36,6 @@ let o = {| X = 2; Y = "Hello" |} printfn "%s" (JsonConvert.SerializeObject o) ``` - # `#r "paket:"` [paket](https://fsprojects.github.io/Paket/fsi-integration.html) Reference dependencies (nuget, git, gist, github) through [Paket package manager](https://fsprojects.github.io/Paket). @@ -49,4 +55,6 @@ X,Y for r in MyCsv.GetSample().Rows do printfn "%i = %s" r.X r.Y -``` \ No newline at end of file +``` +[^1]: [Referencing packages in F# Interactive](https://learn.microsoft.com/dotnet/fsharp/tools/fsharp-interactive/#referencing-packages-in-f-interactive) +[^2]: [F# Interactive options](https://learn.microsoft.com/dotnet/fsharp/language-reference/fsharp-interactive-options) diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/TypeConstraints/IWSAMsAndSRTPs/IWSAMsAndSRTPsTests.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/TypeConstraints/IWSAMsAndSRTPs/IWSAMsAndSRTPsTests.fs index 3363bbfb9611..0685988d23cc 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/Types/TypeConstraints/IWSAMsAndSRTPs/IWSAMsAndSRTPsTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/Types/TypeConstraints/IWSAMsAndSRTPs/IWSAMsAndSRTPsTests.fs @@ -1101,7 +1101,143 @@ module StaticAbstractBug = (Error 17, Line 9, Col 22, Line 9, Col 29, "The member 'Execute: unit -> unit' does not have the correct type to override the corresponding abstract method. Non-static member is expected.") (Error 783, Line 8, Col 15, Line 8, Col 25, "At least one override did not correctly implement its corresponding abstract member") ] + + [] + let ``Produce an error when one leaves keyword "static" when implementing IWSAM in an object expression`` () = + Fsx """ +type IOperation = + static abstract member Execute: unit -> unit + abstract member Execute2: unit -> unit + +let _ = + { new IOperation with + static member Execute() = () + member _.Execute2() = () + } + """ + |> withOptions [ "--nowarn:3536" ; "--nowarn:3535" ] + |> withLangVersion80 + |> typecheck + |> shouldFail + |> withDiagnostics [ + (Error 3860, Line 8, Col 23, Line 8, Col 30, "Static members are not allowed in object expressions.") + ] + + [] + let ``Produce an error when one leaves keyword "static" when implementing multiple IWSAM in an object expression`` () = + Fsx """ +type IOperation = + static abstract member Execute: unit -> unit + static abstract member Execute2: unit -> unit + +let _ = + { new IOperation with + static member Execute() = () + static member Execute2() = () + } + """ + |> withOptions [ "--nowarn:3536" ; "--nowarn:3535" ] + |> withLangVersion80 + |> typecheck + |> shouldFail + |> withDiagnostics [ + (Error 3860, Line 8, Col 23, Line 8, Col 30, "Static members are not allowed in object expressions."); + (Error 3860, Line 9, Col 23, Line 9, Col 31, "Static members are not allowed in object expressions.") + ] + + [] + let ``Produce an error when one leaves keyword "static" when implementing static and not static members in an object expression`` () = + Fsx """ +type IOperation = + static abstract member Execute: unit -> unit + abstract member Execute1: unit -> unit + static abstract member Execute2: unit -> unit + abstract member Execute3: bool + +let _ = + { new IOperation with + static member Execute() = () + member this.Execute1() = () + static member Execute2() = () + member this.Execute3 = true + } + """ + |> withOptions [ "--nowarn:3536" ; "--nowarn:3535" ] + |> withLangVersion80 + |> typecheck + |> shouldFail + |> withDiagnostics [ + (Error 3860, Line 10, Col 23, Line 10, Col 30, "Static members are not allowed in object expressions."); + (Error 3860, Line 12, Col 23, Line 12, Col 31, "Static members are not allowed in object expressions."); + ] + [] + let ``Produces an error when implementing only instance members from IWSAM in an object expression`` () = + Fsx """ +type ILogger = + abstract member Log: string -> unit + static abstract member Execute: string -> unit + +let consoleLogger = + { new ILogger with + member this.Log(message: string) = + printfn "%s" message + } + +consoleLogger.Log("Hello World") + """ + |> withOptions [ "--nowarn:3536" ; "--nowarn:3535" ] + |> withLangVersion80 + |> compile + |> shouldFail + |> withDiagnostics [ + (Error 366, Line 7, Col 5, Line 10, Col 6, "No implementation was given for 'static abstract ILogger.Execute: string -> unit'. Note that all interface members must be implemented and listed under an appropriate 'interface' declaration, e.g. 'interface ... with member ...'.") + ] + + [] + let ``Produces an error when implementing only instance members from IWSAM(Interface attribute) in an object expression`` () = + Fsx """ +[] +type ILogger = + abstract member Log: string -> unit + static abstract member Execute: string -> unit + +let consoleLogger = + { new ILogger with + member this.Log(message: string) = + printfn "%s" message + } + +consoleLogger.Log("Hello World") + """ + |> withOptions [ "--nowarn:3536" ; "--nowarn:3535" ] + |> withLangVersion80 + |> compile + |> shouldFail + |> withDiagnostics [ + (Error 366, Line 8, Col 5, Line 11, Col 6, "No implementation was given for 'static abstract ILogger.Execute: string -> unit'. Note that all interface members must be implemented and listed under an appropriate 'interface' declaration, e.g. 'interface ... with member ...'.") + ] + + [] + let ``No error when implementing only instance members from a type(Interface attribute) in an object expression`` () = + Fsx """ +[] +type ILogger = + abstract member Log: string -> unit + static member Execute x = x + +let consoleLogger = + { new ILogger with + member this.Log(message: string) = + printf "%A" message + } + +consoleLogger.Log("Hello World") + """ + |> withLangVersion80 + |> compile + |> shouldSucceed + [] let ``Produces errors when includes keyword "static" when implementing a generic interface in a type`` () = Fsx """ diff --git a/tests/FSharp.Compiler.ComponentTests/Language/CopyAndUpdateTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/CopyAndUpdateTests.fs index fbbf7f84be6f..118fb0fb81fa 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/CopyAndUpdateTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/CopyAndUpdateTests.fs @@ -423,4 +423,62 @@ let t7 (x: {| a: int; b: NestdRecTy |}) = {| x with c.D = "a" |} (Error 1129, Line 12, Col 55, Line 12, Col 56, "The record type 'NestdRecTy' does not contain a label 'C'.") (Error 1129, Line 13, Col 57, Line 13, Col 58, "The record type '{| a: int |}' does not contain a label 'b'.") (Error 1129, Line 14, Col 53, Line 14, Col 54, "The record type '{| a: int; b: NestdRecTy |}' does not contain a label 'c'.") - ] \ No newline at end of file + ] + +[] +let ``Nested copy-and-update works when the starting expression is not a simple identifier``() = + FSharp """ +module CopyAndUpdateTests + +type Record1 = { Foo: int; Bar: int; } + +[] +module Module = + type Record2 = { Foo: Record1; G: string } + let item: Record2 = Unchecked.defaultof + +ignore { Module.item with Foo.Foo = 3 } + """ + |> withLangVersion80 + |> typecheck + |> shouldSucceed + +[] +let ``Nested, anonymous copy-and-update works when the starting expression is not a simple identifier``() = + FSharp """ +module CopyAndUpdateTests + +type Record1 = { Foo: int; Bar: int; } + +[] +module Module = + let item = {| Foo = Unchecked.defaultof |} + +ignore {| Module.item with Foo.Foo = 3 |} + """ + |> withLangVersion80 + |> typecheck + |> shouldSucceed + +[] +let ``Nested copy-and-update evaluates the original expression once``() = + FSharp """ +module CopyAndUpdateTests + +type Record1 = { Foo: int; Bar: int; Baz: string } +type Record2 = { Foo: Record1; A: int; B: int } + +let f () = + printf "once" + { A = 1; B = 2; Foo = { Foo = 99; Bar = 98; Baz = "a" } } + +let actual = { f () with Foo.Foo = 3; Foo.Baz = "b"; A = -1 } + +let expected = { A = -1; B = 2; Foo = { Foo = 3; Bar = 98; Baz = "b" } } + +if actual <> expected then + failwith "actual does not equal expected" + """ + |> withLangVersion80 + |> compileExeAndRun + |> verifyOutput "once" \ No newline at end of file diff --git a/tests/FSharp.Compiler.ComponentTests/Language/DotLambdaTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/DotLambdaTests.fs index a91045f49a2d..f0e5c77e8657 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/DotLambdaTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/DotLambdaTests.fs @@ -173,9 +173,7 @@ let c = ( _ :> obj) """ |> withDiagnostics [ Error 10, Line 2, Col 20, Line 2, Col 21, "Unexpected symbol ')' in expression. Expected '.' or other token." Error 10, Line 3, Col 13, Line 3, Col 15, "Unexpected symbol ':>' in expression. Expected '.' or other token." - Error 583, Line 3, Col 9, Line 3, Col 10, "Unmatched '('" - Error 10, Line 4, Col 13, Line 4, Col 15, "Unexpected symbol ':>' in expression. Expected '.' or other token." - Error 583, Line 4, Col 9, Line 4, Col 10, "Unmatched '('"] + Error 10, Line 4, Col 13, Line 4, Col 15, "Unexpected symbol ':>' in expression. Expected '.' or other token."] [] let ``ToString with F# 7`` () = diff --git a/tests/service/data/SyntaxTree/Expression/DotLambda - _ Recovery - Casts.fsx b/tests/service/data/SyntaxTree/Expression/DotLambda - _ Recovery - Casts.fsx new file mode 100644 index 000000000000..6791dc6547a9 --- /dev/null +++ b/tests/service/data/SyntaxTree/Expression/DotLambda - _ Recovery - Casts.fsx @@ -0,0 +1,3 @@ +let a = ( upcast _ ) : obj +let b = ( _ :> _ ) : obj +let c = ( _ :> obj) diff --git a/tests/service/data/SyntaxTree/Expression/DotLambda - _ Recovery - Casts.fsx.bsl b/tests/service/data/SyntaxTree/Expression/DotLambda - _ Recovery - Casts.fsx.bsl new file mode 100644 index 000000000000..b65fe6bd9c6e --- /dev/null +++ b/tests/service/data/SyntaxTree/Expression/DotLambda - _ Recovery - Casts.fsx.bsl @@ -0,0 +1,70 @@ +ImplFile + (ParsedImplFileInput + ("/root/Expression/DotLambda - _ Recovery - Casts.fsx", true, + QualifiedNameOfFile DotLambda - _ Recovery - Casts$fsx, [], [], + [SynModuleOrNamespace + ([DotLambda - _ Recovery - Casts], false, AnonModule, + [Let + (false, + [SynBinding + (None, Normal, false, false, [], + PreXmlDoc ((1,0), FSharp.Compiler.Xml.XmlDocCollector), + SynValData + (None, SynValInfo ([], SynArgInfo ([], false, None)), None, + None), Named (SynIdent (a, None), false, None, (1,4--1,5)), + None, + Typed + (Paren + (InferredUpcast + (FromParseError (Ident _, (1,17--1,18)), (1,10--1,18)), + (1,8--1,9), Some (1,19--1,20), (1,8--1,20)), + LongIdent (SynLongIdent ([obj], [], [None])), (1,8--1,26)), + (1,4--1,5), Yes (1,0--1,26), { LeadingKeyword = Let (1,0--1,3) + InlineKeyword = None + EqualsRange = Some (1,6--1,7) })], + (1,0--1,26)); + Let + (false, + [SynBinding + (None, Normal, false, false, [], + PreXmlDoc ((2,0), FSharp.Compiler.Xml.XmlDocCollector), + SynValData + (None, SynValInfo ([], SynArgInfo ([], false, None)), None, + None), Named (SynIdent (b, None), false, None, (2,4--2,5)), + None, + Typed + (Paren + (Upcast + (FromParseError (Ident _, (2,10--2,11)), + Anon (2,15--2,16), (2,10--2,16)), (2,8--2,9), + Some (2,17--2,18), (2,8--2,18)), + LongIdent (SynLongIdent ([obj], [], [None])), (2,8--2,24)), + (2,4--2,5), Yes (2,0--2,24), { LeadingKeyword = Let (2,0--2,3) + InlineKeyword = None + EqualsRange = Some (2,6--2,7) })], + (2,0--2,24)); + Let + (false, + [SynBinding + (None, Normal, false, false, [], + PreXmlDoc ((3,0), FSharp.Compiler.Xml.XmlDocCollector), + SynValData + (None, SynValInfo ([], SynArgInfo ([], false, None)), None, + None), Named (SynIdent (c, None), false, None, (3,4--3,5)), + None, + Paren + (Upcast + (FromParseError (Ident _, (3,10--3,11)), + LongIdent (SynLongIdent ([obj], [], [None])), + (3,10--3,18)), (3,8--3,9), Some (3,18--3,19), + (3,8--3,19)), (3,4--3,5), Yes (3,0--3,19), + { LeadingKeyword = Let (3,0--3,3) + InlineKeyword = None + EqualsRange = Some (3,6--3,7) })], (3,0--3,19))], + PreXmlDocEmpty, [], None, (1,0--4,0), { LeadingKeyword = None })], + (true, true), { ConditionalDirectives = [] + CodeComments = [] }, set [])) + +(1,19)-(1,20) parse error Unexpected symbol ')' in expression. Expected '.' or other token. +(2,12)-(2,14) parse error Unexpected symbol ':>' in expression. Expected '.' or other token. +(3,12)-(3,14) parse error Unexpected symbol ':>' in expression. Expected '.' or other token. diff --git a/tests/service/data/SyntaxTree/Expression/DotLambda - _ Recovery - Eof.fsx b/tests/service/data/SyntaxTree/Expression/DotLambda - _ Recovery - Eof.fsx new file mode 100644 index 000000000000..3454fffae8cf --- /dev/null +++ b/tests/service/data/SyntaxTree/Expression/DotLambda - _ Recovery - Eof.fsx @@ -0,0 +1 @@ +1 |> _ \ No newline at end of file diff --git a/tests/service/data/SyntaxTree/Expression/DotLambda - _ Recovery - Eof.fsx.bsl b/tests/service/data/SyntaxTree/Expression/DotLambda - _ Recovery - Eof.fsx.bsl new file mode 100644 index 000000000000..f481a08bdd56 --- /dev/null +++ b/tests/service/data/SyntaxTree/Expression/DotLambda - _ Recovery - Eof.fsx.bsl @@ -0,0 +1,23 @@ +ImplFile + (ParsedImplFileInput + ("/root/Expression/DotLambda - _ Recovery - Eof.fsx", true, + QualifiedNameOfFile DotLambda - _ Recovery - Eof$fsx, [], [], + [SynModuleOrNamespace + ([DotLambda - _ Recovery - Eof], false, AnonModule, + [Expr + (App + (NonAtomic, false, + App + (NonAtomic, true, + LongIdent + (false, + SynLongIdent + ([op_PipeRight], [], [Some (OriginalNotation "|>")]), + None, (1,2--1,4)), Const (Int32 1, (1,0--1,1)), + (1,0--1,4)), FromParseError (Ident _, (1,5--1,6)), + (1,0--1,6)), (1,0--1,6))], PreXmlDocEmpty, [], None, (1,0--1,6), + { LeadingKeyword = None })], (true, true), + { ConditionalDirectives = [] + CodeComments = [] }, set [])) + +(1,5)-(1,6) parse error Unexpected end of input in expression diff --git a/tests/service/data/SyntaxTree/Expression/DotLambda - _ Recovery.fsx b/tests/service/data/SyntaxTree/Expression/DotLambda - _ Recovery.fsx new file mode 100644 index 000000000000..4c34cbc60d2c --- /dev/null +++ b/tests/service/data/SyntaxTree/Expression/DotLambda - _ Recovery.fsx @@ -0,0 +1 @@ +(_) diff --git a/tests/service/data/SyntaxTree/Expression/DotLambda - _ Recovery.fsx.bsl b/tests/service/data/SyntaxTree/Expression/DotLambda - _ Recovery.fsx.bsl new file mode 100644 index 000000000000..1dfda19d13b6 --- /dev/null +++ b/tests/service/data/SyntaxTree/Expression/DotLambda - _ Recovery.fsx.bsl @@ -0,0 +1,15 @@ +ImplFile + (ParsedImplFileInput + ("/root/Expression/DotLambda - _ Recovery.fsx", true, + QualifiedNameOfFile DotLambda - _ Recovery$fsx, [], [], + [SynModuleOrNamespace + ([DotLambda - _ Recovery], false, AnonModule, + [Expr + (Paren + (FromParseError (Ident _, (1,1--1,2)), (1,0--1,1), + Some (1,2--1,3), (1,0--1,3)), (1,0--1,3))], PreXmlDocEmpty, [], + None, (1,0--1,3), { LeadingKeyword = None })], (true, true), + { ConditionalDirectives = [] + CodeComments = [] }, set [])) + +(1,2)-(1,3) parse error Unexpected symbol ')' in expression. Expected '.' or other token. diff --git a/tests/service/data/SyntaxTree/Expression/DotLambda - _. Recovery - Eof.fsx b/tests/service/data/SyntaxTree/Expression/DotLambda - _. Recovery - Eof.fsx new file mode 100644 index 000000000000..fa28bf307bf9 --- /dev/null +++ b/tests/service/data/SyntaxTree/Expression/DotLambda - _. Recovery - Eof.fsx @@ -0,0 +1 @@ +1 |> _. \ No newline at end of file diff --git a/tests/service/data/SyntaxTree/Expression/DotLambda - _. Recovery - Eof.fsx.bsl b/tests/service/data/SyntaxTree/Expression/DotLambda - _. Recovery - Eof.fsx.bsl new file mode 100644 index 000000000000..878b39bfb7a0 --- /dev/null +++ b/tests/service/data/SyntaxTree/Expression/DotLambda - _. Recovery - Eof.fsx.bsl @@ -0,0 +1,26 @@ +ImplFile + (ParsedImplFileInput + ("/root/Expression/DotLambda - _. Recovery - Eof.fsx", true, + QualifiedNameOfFile DotLambda - _. Recovery - Eof$fsx, [], [], + [SynModuleOrNamespace + ([DotLambda - _; Recovery - Eof], false, AnonModule, + [Expr + (App + (NonAtomic, false, + App + (NonAtomic, true, + LongIdent + (false, + SynLongIdent + ([op_PipeRight], [], [Some (OriginalNotation "|>")]), + None, (1,2--1,4)), Const (Int32 1, (1,0--1,1)), + (1,0--1,4)), + DotLambda + (ArbitraryAfterError ("dotLambda1", (1,7--1,7)), (1,5--1,7), + { UnderscoreRange = (1,5--1,6) + DotRange = (1,6--1,7) }), (1,0--1,7)), (1,0--1,7))], + PreXmlDocEmpty, [], None, (1,0--1,7), { LeadingKeyword = None })], + (true, true), { ConditionalDirectives = [] + CodeComments = [] }, set [])) + +(1,6)-(1,7) parse error Unexpected end of input in expression diff --git a/tests/service/data/SyntaxTree/Expression/DotLambda - _. Recovery.fsx b/tests/service/data/SyntaxTree/Expression/DotLambda - _. Recovery.fsx new file mode 100644 index 000000000000..56d26b9c9d0d --- /dev/null +++ b/tests/service/data/SyntaxTree/Expression/DotLambda - _. Recovery.fsx @@ -0,0 +1 @@ +(_.) diff --git a/tests/service/data/SyntaxTree/Expression/DotLambda - _. Recovery.fsx.bsl b/tests/service/data/SyntaxTree/Expression/DotLambda - _. Recovery.fsx.bsl new file mode 100644 index 000000000000..0bc367b4df3e --- /dev/null +++ b/tests/service/data/SyntaxTree/Expression/DotLambda - _. Recovery.fsx.bsl @@ -0,0 +1,18 @@ +ImplFile + (ParsedImplFileInput + ("/root/Expression/DotLambda - _. Recovery.fsx", true, + QualifiedNameOfFile DotLambda - _. Recovery$fsx, [], [], + [SynModuleOrNamespace + ([DotLambda - _; Recovery], false, AnonModule, + [Expr + (Paren + (DotLambda + (ArbitraryAfterError ("dotLambda1", (1,3--1,3)), (1,1--1,3), + { UnderscoreRange = (1,1--1,2) + DotRange = (1,2--1,3) }), (1,0--1,1), Some (1,3--1,4), + (1,0--1,4)), (1,0--1,4))], PreXmlDocEmpty, [], None, (1,0--1,4), + { LeadingKeyword = None })], (true, true), + { ConditionalDirectives = [] + CodeComments = [] }, set [])) + +(1,3)-(1,4) parse error Unexpected symbol ')' in expression diff --git a/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs b/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs index 6ee9fa87edd3..6a5e34823e3a 100644 --- a/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs +++ b/vsintegration/src/FSharp.Editor/CodeFixes/RemoveUnnecessaryParentheses.fs @@ -15,61 +15,98 @@ open CancellableTasks module private Patterns = let inline toPat f x = if f x then ValueSome() else ValueNone - [] - let inline (|LetterOrDigit|_|) c = toPat Char.IsLetterOrDigit c - - [] - let inline (|Punctuation|_|) c = toPat Char.IsPunctuation c - - [] - let inline (|Symbol|_|) c = toPat Char.IsSymbol c - -module private SourceText = - /// Returns true 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., - /// - /// // Valid. - /// (let x = 2 - /// x) - /// - /// // Invalid. - /// ←let x = 2 - /// x◌ - /// - /// // Valid. - /// ◌let x = 2 - /// x◌ - let containsSensitiveIndentation (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 - - if startLine = endLine then - false - else - let rec loop offsides lineNo startCol = + [] + module Char = + [] + let inline (|LetterOrDigit|_|) c = toPat Char.IsLetterOrDigit c + + [] + let inline (|Punctuation|_|) c = toPat Char.IsPunctuation c + + [] + let inline (|Symbol|_|) c = toPat Char.IsSymbol c + + [] + module SourceText = + /// Returns true 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., + /// + /// // Valid. + /// (let x = 2 + /// x) + /// + /// // Invalid. + /// ←let x = 2 + /// x◌ + /// + /// // Valid. + /// ◌let x = 2 + /// x◌ + let containsSensitiveIndentation (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 + + if startLine = endLine then + false + else + let rec loop offsides lineNo startCol = + if lineNo <= endLine 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 + else + false + + loop ValueNone startLine startCol + + 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() + let line = sourceText.Lines[ lineNo ].ToString().AsSpan() + let i = line.IndexOfAnyExcept("*/%-+:^@><=!|0$.?) ".AsSpan()) + i > offsides || loop (lineNo + 1) + else + false - match offsides with - | ValueNone -> - let i = line.AsSpan(startCol).IndexOfAnyExcept(' ', ')') + loop (startLine + 1) - if i >= 0 then - loop (ValueSome(i + startCol)) (lineNo + 1) 0 - else - loop offsides (lineNo + 1) 0 + [] + let (|ContainsSensitiveIndentation|_|) span sourceText = + toPat (containsSensitiveIndentation span) sourceText - | ValueSome offsidesCol -> - let i = line.AsSpan(0, min offsidesCol line.Length).IndexOfAnyExcept(' ', ')') - i <= offsidesCol || loop offsides (lineNo + 1) 0 - else - false + [] + let (|HasPrecedingConstructOnSameLine|_|) span sourceText = + toPat (hasPrecedingConstructOnSameLine span) sourceText - loop ValueNone startLine startCol + [] + let (|FollowingLineMovesOffsidesRightward|_|) span sourceText = + toPat (followingLineMovesOffsidesRightward span) sourceText [] type internal FSharpRemoveUnnecessaryParenthesesCodeFixProvider [] () = @@ -130,7 +167,6 @@ type internal FSharpRemoveUnnecessaryParenthesesCodeFixProvider [ None | _, (LetterOrDigit | '`'), _ -> Some ShouldPutSpaceBefore | _, (Punctuation | Symbol), (Punctuation | Symbol) -> Some ShouldPutSpaceBefore - | _ when SourceText.containsSensitiveIndentation context.Span sourceText -> Some ShouldPutSpaceBefore | _ -> None let (|ShouldPutSpaceAfter|_|) (s: string) = @@ -142,11 +178,24 @@ type internal FSharpRemoveUnnecessaryParenthesesCodeFixProvider [ 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 newText = - match txt with - | ShouldPutSpaceBefore & ShouldPutSpaceAfter -> " " + txt[1 .. txt.Length - 2] + " " - | ShouldPutSpaceBefore -> " " + txt[1 .. txt.Length - 2] - | ShouldPutSpaceAfter -> txt[1 .. txt.Length - 2] + " " + 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] return diff --git a/vsintegration/src/FSharp.Editor/Common/Extensions.fs b/vsintegration/src/FSharp.Editor/Common/Extensions.fs index 76b6ff0644ff..0c70a0786750 100644 --- a/vsintegration/src/FSharp.Editor/Common/Extensions.fs +++ b/vsintegration/src/FSharp.Editor/Common/Extensions.fs @@ -578,6 +578,16 @@ open System.Runtime.CompilerServices [] type ReadOnlySpanExtensions = + [] + static member IndexOfAnyExcept(span: ReadOnlySpan, value: char) = + let mutable i = 0 + let mutable found = false + + while not found && i < span.Length do + if span[i] <> value then found <- true else i <- i + 1 + + if found then i else -1 + [] static member IndexOfAnyExcept(span: ReadOnlySpan, value0: char, value1: char) = let mutable i = 0 @@ -592,4 +602,32 @@ type ReadOnlySpanExtensions = i <- i + 1 if found then i else -1 + + [] + static member IndexOfAnyExcept(span: ReadOnlySpan, values: ReadOnlySpan) = + let mutable i = 0 + let mutable found = false + + while not found && i < span.Length do + if values.IndexOf span[i] < 0 then + found <- true + else + i <- i + 1 + + if found then i else -1 + + [] + static member LastIndexOfAnyExcept(span: ReadOnlySpan, value0: char, value1: char) = + let mutable i = span.Length - 1 + let mutable found = false + + while not found && i >= 0 do + let c = span[i] + + if c <> value0 && c <> value1 then + found <- true + else + i <- i - 1 + + if found then i else -1 #endif diff --git a/vsintegration/src/FSharp.Editor/DocComments/XMLDocumentation.fs b/vsintegration/src/FSharp.Editor/DocComments/XMLDocumentation.fs index b48991fb3505..e24b89cc609a 100644 --- a/vsintegration/src/FSharp.Editor/DocComments/XMLDocumentation.fs +++ b/vsintegration/src/FSharp.Editor/DocComments/XMLDocumentation.fs @@ -78,10 +78,12 @@ type internal TextSanitizingCollector(collector, ?lineLimit: int) = interface ITaggedTextCollector with member _.Add taggedText = - // TODO: bail out early if line limit is already hit - match taggedText.Tag with - | TextTag.Text -> reportTextLines taggedText.Text - | _ -> addTaggedTextEntry taggedText + match lineLimit with + | Some lineLimit when lineLimit < count -> () + | _ -> + match taggedText.Tag with + | TextTag.Text -> reportTextLines taggedText.Text + | _ -> addTaggedTextEntry taggedText member _.IsEmpty = isEmpty member _.EndsWithLineBreak = isEmpty || endsWithLineBreak diff --git a/vsintegration/tests/FSharp.Editor.Tests/BraceMatchingServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/BraceMatchingServiceTests.fs index 8699e6fd42ff..69015b9c7b9c 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/BraceMatchingServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/BraceMatchingServiceTests.fs @@ -32,7 +32,7 @@ type BraceMatchingServiceTests() = |> Async.RunImmediateExceptOnUI with | None -> () - | Some (left, right) -> failwith $"Found match for brace '{marker}'" + | Some _ -> failwith $"Found match for brace '{marker}'" member private this.VerifyBraceMatch(fileContents: string, startMarker: string, endMarker: string, ?langVersion: string) = let sourceText = SourceText.From(fileContents) diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs index 7ff151e60404..0a0ca440236c 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/CodeFixTestFramework.fs @@ -209,7 +209,7 @@ module Xunit = let actual = split actual try - Assert.All(Array.zip expected actual, (fun (expected, actual) -> Assert.Equal(expected, actual))) + Assert.All((expected, actual) ||> Array.zip |> Array.rev, (fun (expected, actual) -> Assert.Equal(expected, actual))) with :? Xunit.Sdk.AllException as all when all.Failures.Count = 1 -> raise all.Failures[0] diff --git a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs index a5bd9d67d45e..ca780c76db70 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CodeFixes/RemoveUnnecessaryParenthesesTests.fs @@ -103,6 +103,24 @@ let _ = "struct ((1), 1)", "struct (1, 1)" "struct (1, (1))", "struct (1, 1)" "(fun x -> x), y", "(fun x -> x), y" + "3, (null: string)", "3, (null: string)" + + " + let _ = + 1, + (true + || false + || true), + 1 + ", + " + let _ = + 1, + true + || false + || true, + 1 + " // AnonymousRecord "{| A = (1) |}", "{| A = 1 |}" @@ -155,6 +173,9 @@ let _ = "for x in [] do (ignore 3)", "for x in [] do ignore 3" // ArrayOrListComputed + "[1; 2; (if x then 3 else 4); 5]", "[1; 2; (if x then 3 else 4); 5]" + "[|1; 2; (if x then 3 else 4); 5|]", "[|1; 2; (if x then 3 else 4); 5|]" + // IndexRange "[(1)..10]", "[1..10]" "[1..(10)]", "[1..10]" @@ -222,15 +243,26 @@ let _ = "(match x with _ -> 3) > 3", "(match x with _ -> 3) > 3" "match x with 1 -> (fun x -> x) | _ -> id", "match x with 1 -> (fun x -> x) | _ -> id" + "match (try () with _ -> ()) with () -> ()", "match (try () with _ -> ()) with () -> ()" + + " + match (try () with _ -> ()) with + | () -> () + ", + " + match (try () with _ -> ()) with + | () -> () + " + " 3 > (match x with | 1 | _ -> 3) ", " - 3 > match x with - | 1 - | _ -> 3 + 3 > match x with + | 1 + | _ -> 3 " // Do @@ -257,8 +289,8 @@ let _ = in x """, """ - let x = printfn $"{y}" - 2 + let x = printfn $"{y}" + 2 in x """ @@ -273,14 +305,61 @@ let _ = in x " + " + let x = + (2 + + 2) + in x + ", + " + let x = + 2 + + 2 + in x + " + + " + let x = + + + (2 + + + + 2) + in x + ", + " + let x = + + + 2 + + + + 2 + in x + " + + " + let x = ( + 2 + + 2) + in x + ", + " + let x = + 2 + + 2 + in x + " + " let x = (2 + 2) in x ", " - let x = 2 - + 2 + let x = 2 + + 2 in x " @@ -290,8 +369,8 @@ let _ = in x ", " - let x = 2 - + 2 + let x = 2 + + 2 in x " @@ -301,8 +380,8 @@ let _ = in x ", " - let x = 2 - + 2 + let x = 2 + + 2 in x " @@ -312,8 +391,8 @@ let _ = in x ", " - let x = x - +y + let x = x + +y in x " @@ -323,8 +402,8 @@ let _ = in x ", " - let x = 2 - +2 + let x = 2 + +2 in x " @@ -334,11 +413,24 @@ let _ = in x ", " - let x = 2 - <<< 2 + let x = 2 + <<< 2 in x " + " +let (<<<<<<<<) = (<<<) +let x = (2 +<<<<<<<< 2) +in x + ", + " +let (<<<<<<<<) = (<<<) +let x = 2 +<<<<<<<< 2 +in x + " + " let x = ( 2 @@ -384,8 +476,8 @@ let _ = ) let y = - 2 - + 2 + 2 + + 2 in x + y " @@ -411,9 +503,9 @@ let _ = ", " x < - 2 - + 3 - + 2 + + 3 + " // LetOrUse @@ -469,6 +561,21 @@ let _ = else 3 " + """ + if + (printfn "1" + true) + then + () + """, + """ + if + (printfn "1" + true) + then + () + """ + // LongIdent "(|Failure|_|) null", "(|Failure|_|) null" @@ -477,6 +584,7 @@ let _ = // DotGet "([]).Length", "[].Length" + "([] : int list).Length", "([] : int list).Length" // DotLambda "[{| A = x |}] |> List.map (_.A)", "[{| A = x |}] |> List.map _.A" @@ -516,8 +624,8 @@ let _ = """, """ let mutable x = 3 - x <- 3 - <<< 3 + x <- 3 + <<< 3 """ // DotIndexedGet @@ -671,6 +779,22 @@ let _ = // LibraryOnlyILAssembly """(# "ldlen.multi 2 0" array : int #)""", """(# "ldlen.multi 2 0" array : int #)""" + + // Miscellaneous + " + (match x with + | 1 -> () + | _ -> ()) + + y + ", + " + match x with + | 1 -> () + | _ -> () + + y + " } [] @@ -757,6 +881,7 @@ let _ = // Typed "id (x : int)", "id (x : int)" + """ "abc".Contains (x : char, StringComparison.Ordinal) """, """ "abc".Contains (x : char, StringComparison.Ordinal) """ // Tuple "id (x, y)", "id (x, y)" @@ -968,9 +1093,14 @@ let _ = """id ("x").[0]""", """id "x".[0]""" """(id("x")).[0]""", """(id "x").[0]""" """(id "x").[0]""", """(id "x").[0]""" + """id ("".ToCharArray().[0])""", """id ("".ToCharArray().[0])""" + """id ("".ToCharArray()[0])""", """id ("".ToCharArray()[0])""" + """id ("".ToCharArray()[0])""", """id ("".ToCharArray()[0])""" // DotIndexedSet "id ([|x|].[y] <- z)", "id ([|x|].[y] <- z)" + """id ("".ToCharArray().[0] <- '0')""", """id ("".ToCharArray().[0] <- '0')""" + """id ("".ToCharArray()[0] <- '0')""", """id ("".ToCharArray()[0] <- '0')""" // NamedIndexedPropertySet "let xs = [|0|] in id (xs.Item 0 <- 0)", "let xs = [|0|] in id (xs.Item 0 <- 0)" @@ -1036,6 +1166,10 @@ let _ = // InterpolatedString """ id ($"{x}") """, """ id $"{x}" """ + + // Miscellaneous + "System.Threading.Tasks.Task.CompletedTask.ConfigureAwait((x = x))", + "System.Threading.Tasks.Task.CompletedTask.ConfigureAwait((x = x))" } [] @@ -1144,6 +1278,17 @@ let _ = (2 + 2) { return 5 } val Message2 : string new (str1, str2) = { inherit exn (str1); Message2 = str2 } " + + " + type T = static member M (x : bool, y) = () + let x = 3 + T.M ((x = 4), 5) + ", + " + type T = static member M (x : bool, y) = () + let x = 3 + T.M ((x = 4), 5) + " } [] @@ -1436,6 +1581,8 @@ match Unchecked.defaultof<_> with "fun (_) -> ()", "fun _ -> ()" "fun (x) -> x", "fun x -> x" "fun (x: int) -> x", "fun (x: int) -> x" + "fun x (y: int) -> x", "fun x (y: int) -> x" + "fun x (y, z) -> x", "fun x (y, z) -> x" "fun x (y) -> x", "fun x y -> x" "fun x -> fun (y) -> x", "fun x -> fun y -> x" "fun (Lazy x) -> x", "fun (Lazy x) -> x" @@ -1447,6 +1594,8 @@ match Unchecked.defaultof<_> with "function (_) -> ()", "function _ -> ()" "function (x) -> x", "function x -> x" "function (x: int) -> x", "function (x: int) -> x" + "function (x: int, y) -> x", "function x: int, y -> x" + "function (x, y: int) -> x", "function (x, y: int) -> x" "function (Lazy x) -> x", "function Lazy x -> x" "function (1 | 2) -> () | _ -> ()", "function 1 | 2 -> () | _ -> ()" "function (x & y) -> x, y", "function x & y -> x, y" @@ -1465,6 +1614,8 @@ match Unchecked.defaultof<_> with "match x with (_) -> ()", "match x with _ -> ()" "match x with (x) -> x", "match x with x -> x" "match x with (x: int) -> x", "match x with (x: int) -> x" + "match x, y with (x: int, y) -> x", "match x, y with x: int, y -> x" + "match x, y with (x, y: int) -> x", "match x, y with (x, y: int) -> x" "match x with (Lazy x) -> x", "match x with Lazy x -> x" "match x with (x, y) -> x, y", "match x with x, y -> x, y" "match x with (struct (x, y)) -> x, y", "match x with struct (x, y) -> x, y" @@ -1481,6 +1632,8 @@ match Unchecked.defaultof<_> with "let (x) = y in ()", "let x = y in ()" "let (x: int) = y in ()", "let x: int = y in ()" "let (x, y) = x, y in ()", "let x, y = x, y in ()" + "let (x: int, y) = x, y in ()", "let (x: int, y) = x, y in ()" + "let (x: int -> int), (y: int) = x, y in ()", "let (x: int -> int), (y: int) = x, y in ()" "let (struct (x, y)) = x, y in ()", "let struct (x, y) = x, y in ()" "let (x & y) = z in ()", "let x & y = z in ()" "let (x as y) = z in ()", "let x as y = z in ()" @@ -1580,6 +1733,50 @@ match Unchecked.defaultof<_> with "type T = member _.M(struct (x, y)) = x, y", "type T = member _.M struct (x, y) = x, y" "type T = member _.M(?x) = ()", "type T = member _.M ?x = ()" "type T = member _.M(?x: int) = ()", "type T = member _.M(?x: int) = ()" + + // See https://github.com/dotnet/fsharp/issues/16254. + " + type C = abstract M : unit -> unit + let _ = { new C with override _.M (()) = () } + ", + " + type C = abstract M : unit -> unit + let _ = { new C with override _.M (()) = () } + " + + // See https://github.com/dotnet/fsharp/issues/16254. + " + type C<'T> = abstract M : 'T -> unit + let _ = { new C with override _.M (()) = () } + ", + " + type C<'T> = abstract M : 'T -> unit + let _ = { new C with override _.M (()) = () } + " + + // See https://github.com/dotnet/fsharp/issues/16257. + " + type T (x, y) = + new (x, y, z) = T (x, y) + new (x) = T (x, 3) + ", + " + type T (x, y) = + new (x, y, z) = T (x, y) + new (x) = T (x, 3) + " + + // See https://github.com/dotnet/fsharp/issues/16257. + " + type T (x, y) = + new (x) = T (x, 3) + new (x, y, z) = T (x, y) + ", + " + type T (x, y) = + new (x) = T (x, 3) + new (x, y, z) = T (x, y) + " } [] @@ -1626,6 +1823,8 @@ match Unchecked.defaultof<_> with "(A as B) :: C", "(A as B) :: C" "(A as B) as C", "A as B as C" "(A as B), C", "(A as B), C" + "(A, B) :: C", "(A, B) :: C" + "(struct (A, B)) :: C", "struct (A, B) :: C" } [] diff --git a/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs index 0041a7dd4bbf..e94a5f694363 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs @@ -29,7 +29,7 @@ module CompletionProviderTests = let caretPosition = fileContents.IndexOf(marker) + marker.Length let document = - RoslynTestHelpers.CreateSolution(fileContents) + RoslynTestHelpers.CreateSolution(fileContents, extraFSharpProjectOtherOptions = Array.ofSeq opts) |> RoslynTestHelpers.GetSingleDocument let results = @@ -77,7 +77,7 @@ module CompletionProviderTests = let caretPosition = fileContents.IndexOf(marker) + marker.Length let document = - RoslynTestHelpers.CreateSolution(fileContents) + RoslynTestHelpers.CreateSolution(fileContents, extraFSharpProjectOtherOptions = Array.ofSeq opts) |> RoslynTestHelpers.GetSingleDocument let actual = @@ -1990,3 +1990,20 @@ match { A = 1; B = 2 } with """ VerifyCompletionList(fileContents, "| { f = ()", [ "A"; "B"; "C"; "D" ], []) + + [] + let ``issue #16260 [TO-BE-IMPROVED] operators are fumbling for now`` () = + let fileContents = + """ +module Ops = +let (|>>) a b = a + b +module Foo = + let (|>>) a b = a + b +Ops.Foo.() +Ops.Foo.( +Ops.( +Ops.() +""" + + VerifyCompletionList(fileContents, "Ops.Foo.(", [], [ "|>>"; "(|>>)" ]) + VerifyCompletionList(fileContents, "Ops.(", [], [ "|>>"; "(|>>)" ]) diff --git a/vsintegration/tests/FSharp.Editor.Tests/DocumentDiagnosticAnalyzerTests.fs b/vsintegration/tests/FSharp.Editor.Tests/DocumentDiagnosticAnalyzerTests.fs index e1abc4910250..27db356ec4b0 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/DocumentDiagnosticAnalyzerTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/DocumentDiagnosticAnalyzerTests.fs @@ -14,11 +14,11 @@ type DocumentDiagnosticAnalyzerTests() = let startMarker = "(*start*)" let endMarker = "(*end*)" - let getDiagnostics (fileContents: string) = + member private _.getDiagnostics(fileContents: string, ?additionalFlags) = let task = cancellableTask { let document = - RoslynTestHelpers.CreateSolution(fileContents) + RoslynTestHelpers.CreateSolution(fileContents, ?extraFSharpProjectOtherOptions = additionalFlags) |> RoslynTestHelpers.GetSingleDocument let! syntacticDiagnostics = FSharpDocumentDiagnosticAnalyzer.GetDiagnostics(document, DiagnosticsType.Syntax) @@ -30,14 +30,14 @@ type DocumentDiagnosticAnalyzerTests() = task.Result member private this.VerifyNoErrors(fileContents: string, ?additionalFlags: string[]) = - let errors = getDiagnostics fileContents + let errors = this.getDiagnostics (fileContents, ?additionalFlags = additionalFlags) if not errors.IsEmpty then failwith $"There should be no errors generated: {errors}" member private this.VerifyErrorAtMarker(fileContents: string, expectedMarker: string, ?expectedMessage: string) = let errors = - getDiagnostics fileContents + this.getDiagnostics fileContents |> Seq.filter (fun e -> e.Severity = DiagnosticSeverity.Error) |> Seq.toArray @@ -68,7 +68,7 @@ type DocumentDiagnosticAnalyzerTests() = expectedSeverity: DiagnosticSeverity ) = let errors = - getDiagnostics fileContents + this.getDiagnostics fileContents |> Seq.filter (fun e -> e.Severity = expectedSeverity) |> Seq.toArray @@ -99,7 +99,7 @@ type DocumentDiagnosticAnalyzerTests() = ) = // TODO: once workaround (https://github.com/dotnet/fsharp/pull/15982) will not be needed, this should be reverted back to normal method (see PR) let errors = - getDiagnostics fileContents + this.getDiagnostics fileContents |> Seq.filter (fun e -> e.Severity = expectedSeverity) |> Seq.toArray @@ -133,7 +133,7 @@ type DocumentDiagnosticAnalyzerTests() = ?expectedMessage: string ) = let errors = - getDiagnostics fileContents + this.getDiagnostics fileContents |> Seq.filter (fun e -> e.Severity = DiagnosticSeverity.Error) |> Seq.toArray diff --git a/vsintegration/tests/FSharp.Editor.Tests/EditorFormattingServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/EditorFormattingServiceTests.fs index beb93403e910..9442957325fd 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/EditorFormattingServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/EditorFormattingServiceTests.fs @@ -38,7 +38,7 @@ let def = )marker4 """ - let pasteTemplate = + let _pasteTemplate = """ let foo = diff --git a/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj b/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj index 407a1aad700c..77ce57f27e0c 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj +++ b/vsintegration/tests/FSharp.Editor.Tests/FSharp.Editor.Tests.fsproj @@ -7,6 +7,8 @@ false true $(NoWarn);FS3511 + $(OtherFlags) --warnon:1182 + true diff --git a/vsintegration/tests/FSharp.Editor.Tests/Helpers/RoslynHelpers.fs b/vsintegration/tests/FSharp.Editor.Tests/Helpers/RoslynHelpers.fs index 5b00301821bd..9bedb277c9d2 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/Helpers/RoslynHelpers.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/Helpers/RoslynHelpers.fs @@ -188,7 +188,7 @@ type TestHostWorkspaceServices(hostServices: HostServices, workspace: Workspace) with _ -> Unchecked.defaultof<'T> - override _.FindLanguageServices(filter) = Seq.empty + override _.FindLanguageServices(_filter) = Seq.empty override _.GetLanguageServices(languageName) = match languageName with @@ -275,7 +275,7 @@ type RoslynTestHelpers private () = static member SetEditorOptions (solution: Solution) options = solution.Workspace.Services.GetService().With(options) - static member CreateSolution(source, ?options: FSharpProjectOptions, ?editorOptions) = + static member CreateSolution(source, ?options: FSharpProjectOptions, ?extraFSharpProjectOtherOptions: string array, ?editorOptions) = let projId = ProjectId.CreateNewId() let docInfo = RoslynTestHelpers.CreateDocumentInfo projId "C:\\test.fs" source @@ -284,9 +284,18 @@ type RoslynTestHelpers private () = let projInfo = RoslynTestHelpers.CreateProjectInfo projId projFilePath [ docInfo ] let solution = RoslynTestHelpers.CreateSolution [ projInfo ] - options - |> Option.defaultValue RoslynTestHelpers.DefaultProjectOptions - |> RoslynTestHelpers.SetProjectOptions projId solution + let options = + let options = options |> Option.defaultValue RoslynTestHelpers.DefaultProjectOptions + + match extraFSharpProjectOtherOptions with + | None + | Some [||] -> options + | Some otherOptions -> + { options with + OtherOptions = Array.concat [| options.OtherOptions; otherOptions |] + } + + options |> RoslynTestHelpers.SetProjectOptions projId solution if editorOptions.IsSome then RoslynTestHelpers.SetEditorOptions solution editorOptions.Value @@ -352,9 +361,7 @@ type RoslynTestHelpers private () = } let solution = - match customEditorOptions with - | Some o -> RoslynTestHelpers.CreateSolution(code, options, o) - | None -> RoslynTestHelpers.CreateSolution(code, options) + RoslynTestHelpers.CreateSolution(code, options, ?editorOptions = customEditorOptions) solution |> RoslynTestHelpers.GetSingleDocument diff --git a/vsintegration/tests/FSharp.Editor.Tests/QuickInfoProviderTests.fs b/vsintegration/tests/FSharp.Editor.Tests/QuickInfoProviderTests.fs index aa8eb39bb53e..abd08e2bf457 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/QuickInfoProviderTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/QuickInfoProviderTests.fs @@ -85,7 +85,7 @@ module QuickInfoProviderTests = | QuickInfo _ -> QuickInfo(desc, docs) | _ -> Desc desc - | ToolTipElement.CompositionError (error) -> Error + | ToolTipElement.CompositionError _ -> Error let executeQuickInfoTest (programText: string) testCases = let document =