diff --git a/src/fsharp/FSComp.txt b/src/fsharp/FSComp.txt index 0505c47b289..36d8d4e1116 100644 --- a/src/fsharp/FSComp.txt +++ b/src/fsharp/FSComp.txt @@ -1224,6 +1224,7 @@ invalidFullNameForProvidedType,"invalid full name for provided type" 3085,tcCustomOperationMayNotBeUsedInConjunctionWithNonSimpleLetBindings,"A custom operation may not be used in conjunction with a non-value or recursive 'let' binding in another part of this computation expression" 3086,tcCustomOperationMayNotBeUsedHere,"A custom operation may not be used in conjunction with 'use', 'try/with', 'try/finally', 'if/then/else' or 'match' operators within this computation expression" 3087,tcCustomOperationMayNotBeOverloaded,"The custom operation '%s' refers to a method which is overloaded. The implementations of custom operations may not be overloaded." +featureOverloadsForCustomOperations,"overloads for custom operations" 3090,tcIfThenElseMayNotBeUsedWithinQueries,"An if/then/else expression may not be used within queries. Consider using either an if/then expression, or use a sequence expression instead." 3091,ilxgenUnexpectedArgumentToMethodHandleOfDuringCodegen,"Invalid argument to 'methodhandleof' during codegen" 3092,etProvidedTypeReferenceMissingArgument,"A reference to a provided type was missing a value for the static parameter '%s'. You may need to recompile one or more referenced assemblies." diff --git a/src/fsharp/LanguageFeatures.fs b/src/fsharp/LanguageFeatures.fs index de0ae2ed8f6..0a1d8c355a8 100644 --- a/src/fsharp/LanguageFeatures.fs +++ b/src/fsharp/LanguageFeatures.fs @@ -34,6 +34,7 @@ type LanguageFeature = | WitnessPassing | InterfacesWithMultipleGenericInstantiation | StringInterpolation + | OverloadsForCustomOperations /// LanguageVersion management type LanguageVersion (specifiedVersionAsString) = @@ -73,6 +74,7 @@ type LanguageVersion (specifiedVersionAsString) = LanguageFeature.InterfacesWithMultipleGenericInstantiation, previewVersion LanguageFeature.NameOf, previewVersion LanguageFeature.StringInterpolation, previewVersion + LanguageFeature.OverloadsForCustomOperations, previewVersion ] let specified = @@ -144,6 +146,7 @@ type LanguageVersion (specifiedVersionAsString) = | LanguageFeature.WitnessPassing -> FSComp.SR.featureWitnessPassing() | LanguageFeature.InterfacesWithMultipleGenericInstantiation -> FSComp.SR.featureInterfacesWithMultipleGenericInstantiation() | LanguageFeature.StringInterpolation -> FSComp.SR.featureStringInterpolation() + | LanguageFeature.OverloadsForCustomOperations -> FSComp.SR.featureOverloadsForCustomOperations() /// Get a version string associated with the given feature. member _.GetFeatureVersionString feature = diff --git a/src/fsharp/LanguageFeatures.fsi b/src/fsharp/LanguageFeatures.fsi index 9dbfccea942..38847502e0c 100644 --- a/src/fsharp/LanguageFeatures.fsi +++ b/src/fsharp/LanguageFeatures.fsi @@ -22,6 +22,7 @@ type LanguageFeature = | WitnessPassing | InterfacesWithMultipleGenericInstantiation | StringInterpolation + | OverloadsForCustomOperations /// LanguageVersion management type LanguageVersion = diff --git a/src/fsharp/TypeChecker.fs b/src/fsharp/TypeChecker.fs index adba0794ac2..563b001f4e1 100755 --- a/src/fsharp/TypeChecker.fs +++ b/src/fsharp/TypeChecker.fs @@ -7905,19 +7905,36 @@ and TcComputationExpression cenv env overallTy mWhole (interpExpr: Expr) builder Some (nm, maintainsVarSpaceUsingBind, maintainsVarSpace, allowInto, isLikeZip, isLikeJoin, isLikeGroupJoin, joinConditionWord, methInfo)) let customOperationMethodsIndexedByKeyword = - customOperationMethods - |> Seq.groupBy (fun (nm, _, _, _, _, _, _, _, _) -> nm) - |> Seq.map (fun (nm, g) -> (nm, Seq.toList g)) + if cenv.g.langVersion.SupportsFeature LanguageFeature.OverloadsForCustomOperations then + customOperationMethods + |> Seq.groupBy (fun (nm, _, _, _, _, _, _, _, _) -> nm) + |> Seq.map (fun (nm, group) -> + (nm, + group + |> Seq.distinctBy (fun (_, _, _, _, _, _, _, _, methInfo) -> methInfo.LogicalName) + |> Seq.toList)) + else + customOperationMethods + |> Seq.groupBy (fun (nm, _, _, _, _, _, _, _, _) -> nm) + |> Seq.map (fun (nm, g) -> (nm, Seq.toList g)) |> dict // Check for duplicates by method name (keywords and method names must be 1:1) let customOperationMethodsIndexedByMethodName = - customOperationMethods - |> Seq.groupBy (fun (_, _, _, _, _, _, _, _, methInfo) -> methInfo.LogicalName) - |> Seq.map (fun (nm, g) -> (nm, Seq.toList g)) + if cenv.g.langVersion.SupportsFeature LanguageFeature.OverloadsForCustomOperations then + customOperationMethods + |> Seq.groupBy (fun (_, _, _, _, _, _, _, _, methInfo) -> methInfo.LogicalName) + |> Seq.map (fun (nm, group) -> + (nm, + group + |> Seq.distinctBy (fun (nm, _, _, _, _, _, _, _, _) -> nm) + |> Seq.toList)) + else + customOperationMethods + |> Seq.groupBy (fun (_, _, _, _, _, _, _, _, methInfo) -> methInfo.LogicalName) + |> Seq.map (fun (nm, g) -> (nm, Seq.toList g)) |> dict - /// Decide if the identifier represents a use of a custom query operator let tryGetDataForCustomOperation (nm: Ident) = match customOperationMethodsIndexedByKeyword.TryGetValue nm.idText with @@ -8899,12 +8916,12 @@ and TcComputationExpression cenv env overallTy mWhole (interpExpr: Expr) builder let maintainsVarSpace = customOperationMaintainsVarSpace nm let maintainsVarSpaceUsingBind = customOperationMaintainsVarSpaceUsingBind nm - let expectedArgCount = expectedArgCountForCustomOperator nm + let expectedArgCount = expectedArgCountForCustomOperator nm let dataCompAfterOp = match opExpr with - | StripApps(SingleIdent nm, args) -> - if args.Length = expectedArgCount then + | StripApps(SingleIdent nm, args) -> + if args.Length = expectedArgCount || cenv.g.langVersion.SupportsFeature LanguageFeature.OverloadsForCustomOperations then // Check for the [] attribute on each argument position let args = args |> List.mapi (fun i arg -> if isCustomOperationProjectionParameter (i+1) nm then @@ -8913,7 +8930,7 @@ and TcComputationExpression cenv env overallTy mWhole (interpExpr: Expr) builder mkSynCall methInfo.DisplayName mClause (dataCompPrior :: args) else errorR(Error(FSComp.SR.tcCustomOperationHasIncorrectArgCount(nm.idText, expectedArgCount, args.Length), nm.idRange)) - mkSynCall methInfo.DisplayName mClause ([ dataCompPrior ] @ List.init expectedArgCount (fun i -> arbExpr("_arg" + string i, mClause))) + mkSynCall methInfo.DisplayName mClause ([ dataCompPrior ] @ List.init expectedArgCount (fun i -> arbExpr("_arg" + string i, mClause))) | _ -> failwith "unreachable" match optionalCont with diff --git a/tests/fsharp/Compiler/Conformance/DataExpressions/ComputationExpressions.fs b/tests/fsharp/Compiler/Conformance/DataExpressions/ComputationExpressions.fs index 240583f704d..5b2c6159b09 100644 --- a/tests/fsharp/Compiler/Conformance/DataExpressions/ComputationExpressions.fs +++ b/tests/fsharp/Compiler/Conformance/DataExpressions/ComputationExpressions.fs @@ -710,3 +710,129 @@ let ceResult = check "grwerjkrwejgk42" ceResult.Value 2 """ + let overloadLib includeInternalExtensions includeExternalExtensions = + """ +open System + +type Content = ArraySegment list + +type ContentBuilder() = + member this.Run(c: Content) = + let crlf = "\r\n"B + [|for part in List.rev c do + yield! part.Array.[part.Offset..(part.Count+part.Offset-1)] + yield! crlf |] + + member this.Yield(_) = [] + + [] + member this.Body(c: Content, segment: ArraySegment) = + segment::c + """ + (if includeInternalExtensions then """ + +type ContentBuilder with + // unattributed internal type extension with same arity + member this.Body(c: Content, bytes: byte[]) = + ArraySegment(bytes, 0, bytes.Length)::c + + // internal type extension with different arity + [] + member this.Body(c: Content, bytes: byte[], offset, count) = + ArraySegment(bytes, offset, count)::c + """ else """ + + // unattributed type member with same arity + member this.Body(c: Content, bytes: byte[]) = + ArraySegment(bytes, 0, bytes.Length)::c + + // type member with different arity + [] + member this.Body(c: Content, bytes: byte[], offset, count) = + ArraySegment(bytes, offset, count)::c + """) + (if includeExternalExtensions then """ + +module Extensions = + type ContentBuilder with + // unattributed external type extension with same arity + member this.Body(c: Content, content: System.IO.Stream) = + let mem = new System.IO.MemoryStream() + content.CopyTo(mem) + let bytes = mem.ToArray() + ArraySegment(bytes, 0, bytes.Length)::c + + // external type extensions as ParamArray + [] + member this.Body(c: Content, [] contents: string[]) = + List.rev [for c in contents -> let b = Text.Encoding.ASCII.GetBytes c in ArraySegment<_>(b,0,b.Length)] @ c +open Extensions + """ else """ + + // unattributed type member with same arity + member this.Body(c: Content, content: System.IO.Stream) = + let mem = new System.IO.MemoryStream() + content.CopyTo(mem) + let bytes = mem.ToArray() + ArraySegment(bytes, 0, bytes.Length)::c + + // type members + [] + member this.Body(c: Content, [] contents: string[]) = + List.rev [for c in contents -> let b = Text.Encoding.ASCII.GetBytes c in ArraySegment<_>(b,0,b.Length)] @ c + """) + """ + +let check msg actual expected = if actual <> expected then failwithf "FAILED %s, expected %A, got %A" msg expected actual + """ + + let OverloadLibTest inclInternalExt inclExternalExt source = + CompilerAssert.CompileExeAndRunWithOptions [| "/langversion:preview" |] (overloadLib inclInternalExt inclExternalExt + source) + + [] + let ``OverloadLib accepts overloaded methods`` () = + OverloadLibTest false false """ +let mem = new System.IO.MemoryStream("Stream"B) +let content = ContentBuilder() +let ceResult = + content { + body "Name" + body (ArraySegment<_>("Email"B, 0, 5)) + body "Password"B 2 4 + body "BYTES"B + body mem + body "Description" "of" "content" + } +check "TmFtZVxyXG5FbWF1" ceResult "Name\r\nEmail\r\nsswo\r\nBYTES\r\nStream\r\nDescription\r\nof\r\ncontent\r\n"B + """ + + [] + let ``OverloadLib accepts overloaded internal extension methods`` () = + OverloadLibTest true false """ +let mem = new System.IO.MemoryStream("Stream"B) +let content = ContentBuilder() +let ceResult = + content { + body "Name" + body (ArraySegment<_>("Email"B, 0, 5)) + body "Password"B 2 4 + body "BYTES"B + body mem + body "Description" "of" "content" + } +check "TmFtZVxyXG5FbWF2" ceResult "Name\r\nEmail\r\nsswo\r\nBYTES\r\nStream\r\nDescription\r\nof\r\ncontent\r\n"B + """ + + [] + let ``OverloadLib accepts overloaded internal and external extensions`` () = + OverloadLibTest true true """ +let mem = new System.IO.MemoryStream("Stream"B) +let content = ContentBuilder() +let ceResult = + content { + body "Name" + body (ArraySegment<_>("Email"B, 0, 5)) + body "Password"B 2 4 + body "BYTES"B + body mem + body "Description" "of" "content" + } +check "TmFtZVxyXG5FbWF3" ceResult "Name\r\nEmail\r\nsswo\r\nBYTES\r\nStream\r\nDescription\r\nof\r\ncontent\r\n"B + """ diff --git a/tests/fsharp/typecheck/sigs/neg60.bsl b/tests/fsharp/typecheck/sigs/neg60.bsl index 9e05fc389f2..f403c73f293 100644 --- a/tests/fsharp/typecheck/sigs/neg60.bsl +++ b/tests/fsharp/typecheck/sigs/neg60.bsl @@ -79,3 +79,83 @@ neg60.fs(80,19,80,20): typecheck error FS0043: Expecting a type supporting the o neg60.fs(81,22,81,34): typecheck error FS0002: This function takes too many arguments, or is used in a context where a function is not expected neg60.fs(87,10,87,13): typecheck error FS0043: The type 'System.Nullable' does not support the operator '?=?'. Consider opening the module 'Microsoft.FSharp.Linq.NullableOperators'. + +neg60.fs(128,9,128,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(128,9,128,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(128,9,128,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(128,9,128,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(128,9,128,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(128,9,128,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(128,9,128,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(128,9,128,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(128,9,128,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(128,9,128,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(128,9,128,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(128,9,128,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(128,9,128,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(128,9,128,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(128,9,128,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(128,9,128,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(128,9,128,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(128,9,128,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(128,9,128,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(129,9,129,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(129,9,129,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(129,9,129,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(129,9,129,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(129,9,129,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(129,9,129,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(130,9,130,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(130,9,130,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(130,9,130,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(130,9,130,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(130,9,130,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(130,9,130,13): typecheck error FS3099: 'body' is used with an incorrect number of arguments. This is a custom operation in this query or computation expression. Expected 1 argument(s), but given 3. + +neg60.fs(131,9,131,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(131,9,131,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(131,9,131,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(131,9,131,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(131,9,131,13): typecheck error FS3087: The custom operation 'body' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. + +neg60.fs(131,9,131,13): typecheck error FS3099: 'body' is used with an incorrect number of arguments. This is a custom operation in this query or computation expression. Expected 1 argument(s), but given 3. + +neg60.fs(177,9,177,13): typecheck error FS3099: 'text' is used with an incorrect number of arguments. This is a custom operation in this query or computation expression. Expected 1 argument(s), but given 0. + +neg60.fs(186,9,186,24): typecheck error FS3099: 'with_validators' is used with an incorrect number of arguments. This is a custom operation in this query or computation expression. Expected 1 argument(s), but given 2. + +neg60.fs(195,9,195,24): typecheck error FS3099: 'with_validators' is used with an incorrect number of arguments. This is a custom operation in this query or computation expression. Expected 1 argument(s), but given 3. diff --git a/tests/fsharp/typecheck/sigs/neg60.fs b/tests/fsharp/typecheck/sigs/neg60.fs index 15475f55884..70026402204 100644 --- a/tests/fsharp/typecheck/sigs/neg60.fs +++ b/tests/fsharp/typecheck/sigs/neg60.fs @@ -85,3 +85,115 @@ module NullableOperators3 = let a = new System.Nullable(0) let b = new System.Nullable(1) if a ?=? b then printfn "Same" else printfn "Differerent" + +module QuerySyntaxWithValidOverloading = + + open System + + type Content = ArraySegment list + + type ContentBuilder() = + member this.Run(c: Content) = + let crlf = "\r\n"B + [|for part in List.rev c do + yield! part.Array.[part.Offset..(part.Count+part.Offset-1)] + yield! crlf |] + + member this.Yield(_) = [] + + [] + member this.Body(c: Content, segment: ArraySegment) = + segment::c + + [] + member this.Body(c: Content, bytes: byte[], offset, count) = + ArraySegment(bytes, offset, count)::c + + [] + member this.Body(c: Content, [] contents: string[]) = + let rec loop acc (contents: string list) = + match contents with + | [] -> acc + | content::rest -> + let bytes = Text.Encoding.ASCII.GetBytes(content) + loop (this.Body(c, bytes, 0, bytes.Length)) rest + loop c (List.ofArray contents) + + let content = ContentBuilder() + + //--------------------------------------------------------------- + + let values = + content { + body "Name" + body (ArraySegment<_>("Email"B, 0, 5)) + body "Password"B 2 4 + body "Description" "of" "content" + } + +module QuerySyntaxWithOptionalParamAndParamsArray = + + open System + + type InputKind = + | Text of placeholder:string option + | Password of placeholder: string option + + type InputOptions = + { Label: string option + Kind : InputKind + Validators : (string -> bool) array } + + type InputBuilder() = + + member t.Yield(_) = + { Label = None + Kind = Text None + Validators = [||] } + + [] + member this.Text(io,?placeholder) = + { io with Kind = Text placeholder } + + [] + member this.Password(io,?placeholder) = + { io with Kind = Password placeholder } + + [] + member this.Label(io,label) = + { io with Label = Some label } + + [] + member this.Validators(io, [] validators) = + { io with Validators = validators } + + let input = InputBuilder() + + //--------------------------------------------------------------- + + let name = + input { + label "Name" + text + with_validators + (String.IsNullOrWhiteSpace >> not) + } + + let email = + input { + label "Email" + text "Your email" + with_validators + (String.IsNullOrWhiteSpace >> not) + (fun s -> s.Contains "@") + } + + let password = + input { + label "Password" + password "Must contains at least 6 characters, one number and one uppercase" + with_validators + (String.exists Char.IsUpper) + (String.exists Char.IsDigit) + (fun s -> s.Length >= 6) + } diff --git a/tests/fsharp/typecheck/sigs/neg61.bsl b/tests/fsharp/typecheck/sigs/neg61.bsl index 2811c3198e5..5b1ecfb7c66 100644 --- a/tests/fsharp/typecheck/sigs/neg61.bsl +++ b/tests/fsharp/typecheck/sigs/neg61.bsl @@ -99,4 +99,4 @@ neg61.fs(191,21,191,33): typecheck error FS3153: Arguments to query operators ma neg61.fs(197,21,197,33): typecheck error FS3153: Arguments to query operators may require parentheses, e.g. 'where (x > y)' or 'groupBy (x.Length / 10)' -neg61.fs(202,20,202,31): typecheck error FS3153: Arguments to query operators may require parentheses, e.g. 'where (x > y)' or 'groupBy (x.Length / 10)' +neg61.fs(202,20,202,31): typecheck error FS3153: Arguments to query operators may require parentheses, e.g. 'where (x > y)' or 'groupBy (x.Length / 10)' \ No newline at end of file diff --git a/tests/fsharp/typecheck/sigs/neg87.bsl b/tests/fsharp/typecheck/sigs/neg87.bsl index 5359647ec21..298effe9103 100644 --- a/tests/fsharp/typecheck/sigs/neg87.bsl +++ b/tests/fsharp/typecheck/sigs/neg87.bsl @@ -37,4 +37,4 @@ neg87.fsx(14,5,14,10): typecheck error FS3087: The custom operation 'test2' refe neg87.fsx(14,5,14,10): typecheck error FS3087: The custom operation 'test2' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. -neg87.fsx(14,5,14,10): typecheck error FS3087: The custom operation 'test2' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. +neg87.fsx(14,5,14,10): typecheck error FS3087: The custom operation 'test2' refers to a method which is overloaded. The implementations of custom operations may not be overloaded. \ No newline at end of file