diff --git a/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md b/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md index 2687dc328e9..42e9e5aed45 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md +++ b/docs/release-notes/.FSharp.Compiler.Service/8.0.400.md @@ -28,6 +28,7 @@ * Expose inner exception information of TypeProviders to help diagnostics in IDE ([PR #17251](https://github.com/dotnet/fsharp/pull/17251)) * Parser: recover on empty match clause ([PR #17233](https://github.com/dotnet/fsharp/pull/17233)) * Support empty-bodied computation expressions. ([Language suggestion #1232](https://github.com/fsharp/fslang-suggestions/issues/1232), [RFC FS-1144 (PR #774)](https://github.com/fsharp/fslang-design/pull/774), [PR #17352](https://github.com/dotnet/fsharp/pull/17352)) +* Support using `Slice` to enable slicing syntax `expr1[expr2..expr3]`. ([Language suggestion #1317](https://github.com/fsharp/fslang-suggestions/issues/1317), [RFC FS-1145 (PR #TODO)](TODO), [PR #17377](https://github.com/dotnet/fsharp/pull/17377)) ### Changed * Enforce `AttributeTargets.Interface` ([PR #17173](https://github.com/dotnet/fsharp/pull/17173)) diff --git a/src/Compiler/Checking/CheckExpressions.fs b/src/Compiler/Checking/CheckExpressions.fs index 6bdf94ed9ff..59488ead5d4 100644 --- a/src/Compiler/Checking/CheckExpressions.fs +++ b/src/Compiler/Checking/CheckExpressions.fs @@ -6374,11 +6374,6 @@ and TcExprILAssembly (cenv: cenv) overallTy env tpenv (ilInstrs, synTyArgs, synA // Converts 'a..b' to a call to the '(..)' operator in FSharp.Core // Converts 'a..b..c' to a call to the '(.. ..)' operator in FSharp.Core -// -// NOTE: we could eliminate these more efficiently in LowerComputedCollections.fs, since -// [| 1..4 |] -// becomes [| for i in (..) 1 4 do yield i |] -// instead of generating the array directly from the ranges and RewriteRangeExpr synExpr = match synExpr with // a..b..c (parsed as (a..b)..c ) @@ -6556,166 +6551,466 @@ and TcIndexingThen cenv env overallTy mWholeExpr mDot tpenv setInfo synLeftExprO let g = cenv.g let ad = env.AccessRights - // Find the first type in the effective hierarchy that either has a DefaultMember attribute OR - // has a member called 'Item' - let isIndex = indexArgs |> List.forall (fun indexArg -> match DecodeIndexArg cenv indexArg with IndexArgItem _ -> true | _ -> false) - let propName = - if isIndex then - FoldPrimaryHierarchyOfType (fun ty acc -> - match acc with - | None -> - match tryTcrefOfAppTy g ty with - | ValueSome tcref -> - TryFindTyconRefStringAttribute g mWholeExpr g.attrib_DefaultMemberAttribute tcref - | _ -> - let item = Some "Item" - match AllPropInfosOfTypeInScope ResultCollectionSettings.AtMostOneResult cenv.infoReader env.NameEnv item ad IgnoreOverrides mWholeExpr ty with - | [] -> None - | _ -> item - | _ -> acc) - g - cenv.amap - mWholeExpr - AllowMultiIntfInstantiations.Yes - exprTy - None - else Some "GetSlice" - - let isNominal = isAppTy g exprTy - - let isArray = isArrayTy g exprTy - let isString = typeEquiv g g.string_ty exprTy + let (|Array|String|Nominal|Unknown|) exprTy = + if isArrayTy g exprTy then Array + elif typeEquiv g g.string_ty exprTy then String + elif isAppTy g exprTy then Nominal + else Unknown + + let indexOpPath = ["Microsoft";"FSharp";"Core";"LanguagePrimitives";"IntrinsicFunctions"] + let sliceOpPath = ["Microsoft";"FSharp";"Core";"Operators";"OperatorIntrinsics"] + + /// Look up the appropriate array indexer or slicer path, method, and args. + let (|ArrayIndexerOrSlicer|_|) (indexArgs, setInfo) = + let fixedIndex3d4dEnabled = g.langVersion.SupportsFeature LanguageFeature.FixedIndexSlice3d4d + match indexArgs, setInfo with + | [IndexArgItem _; IndexArgItem _], None -> Some (indexOpPath, "GetArray2D", expandedIndexArgs) + | [IndexArgItem _; IndexArgItem _; IndexArgItem _;], None -> Some (indexOpPath, "GetArray3D", expandedIndexArgs) + | [IndexArgItem _; IndexArgItem _; IndexArgItem _; IndexArgItem _], None -> Some (indexOpPath, "GetArray4D", expandedIndexArgs) + | [IndexArgItem _], None -> Some (indexOpPath, "GetArray", expandedIndexArgs) + | [IndexArgItem _; IndexArgItem _], Some (expr3, _) -> Some (indexOpPath, "SetArray2D", (expandedIndexArgs @ [expr3])) + | [IndexArgItem _; IndexArgItem _; IndexArgItem _;], Some (expr3, _) -> Some (indexOpPath, "SetArray3D", (expandedIndexArgs @ [expr3])) + | [IndexArgItem _; IndexArgItem _; IndexArgItem _; IndexArgItem _], Some (expr3, _) -> Some (indexOpPath, "SetArray4D", (expandedIndexArgs @ [expr3])) + | [IndexArgItem _], Some (expr3, _) -> Some (indexOpPath, "SetArray", (expandedIndexArgs @ [expr3])) + | [IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice", expandedIndexArgs) + | [IndexArgItem _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice2DFixed1", expandedIndexArgs) + | [IndexArgRange _;IndexArgItem _], None -> Some (sliceOpPath, "GetArraySlice2DFixed2", expandedIndexArgs) + | [IndexArgRange _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice2D", expandedIndexArgs) + | [IndexArgRange _;IndexArgRange _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice3D", expandedIndexArgs) + | [IndexArgRange _;IndexArgRange _;IndexArgRange _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice4D", expandedIndexArgs) + | [IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice", (expandedIndexArgs @ [expr3])) + | [IndexArgRange _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice2D", (expandedIndexArgs @ [expr3])) + | [IndexArgItem _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice2DFixed1", (expandedIndexArgs @ [expr3])) + | [IndexArgRange _;IndexArgItem _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice2DFixed2", (expandedIndexArgs @ [expr3])) + | [IndexArgRange _;IndexArgRange _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice3D", (expandedIndexArgs @ [expr3])) + | [IndexArgRange _;IndexArgRange _;IndexArgRange _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4D", (expandedIndexArgs @ [expr3])) + | _ when fixedIndex3d4dEnabled -> + match indexArgs, setInfo with + | [IndexArgItem _;IndexArgRange _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice3DFixedSingle1", expandedIndexArgs) + | [IndexArgRange _;IndexArgItem _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice3DFixedSingle2", expandedIndexArgs) + | [IndexArgRange _;IndexArgRange _;IndexArgItem _], None -> Some (sliceOpPath, "GetArraySlice3DFixedSingle3", expandedIndexArgs) + | [IndexArgItem _;IndexArgItem _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice3DFixedDouble1", expandedIndexArgs) + | [IndexArgItem _;IndexArgRange _;IndexArgItem _], None -> Some (sliceOpPath, "GetArraySlice3DFixedDouble2", expandedIndexArgs) + | [IndexArgRange _;IndexArgItem _;IndexArgItem _], None -> Some (sliceOpPath, "GetArraySlice3DFixedDouble3", expandedIndexArgs) + | [IndexArgItem _;IndexArgRange _;IndexArgRange _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice4DFixedSingle1", expandedIndexArgs) + | [IndexArgRange _;IndexArgItem _;IndexArgRange _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice4DFixedSingle2", expandedIndexArgs) + | [IndexArgRange _;IndexArgRange _;IndexArgItem _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice4DFixedSingle3", expandedIndexArgs) + | [IndexArgRange _;IndexArgRange _;IndexArgRange _;IndexArgItem _], None -> Some (sliceOpPath, "GetArraySlice4DFixedSingle4", expandedIndexArgs) + | [IndexArgItem _;IndexArgItem _;IndexArgRange _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice4DFixedDouble1", expandedIndexArgs) + | [IndexArgItem _;IndexArgRange _;IndexArgItem _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice4DFixedDouble2", expandedIndexArgs) + | [IndexArgItem _;IndexArgRange _;IndexArgRange _;IndexArgItem _], None -> Some (sliceOpPath, "GetArraySlice4DFixedDouble3", expandedIndexArgs) + | [IndexArgRange _;IndexArgItem _;IndexArgItem _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice4DFixedDouble4", expandedIndexArgs) + | [IndexArgRange _;IndexArgItem _;IndexArgRange _;IndexArgItem _], None -> Some (sliceOpPath, "GetArraySlice4DFixedDouble5", expandedIndexArgs) + | [IndexArgRange _;IndexArgRange _;IndexArgItem _;IndexArgItem _], None -> Some (sliceOpPath, "GetArraySlice4DFixedDouble6", expandedIndexArgs) + | [IndexArgRange _;IndexArgItem _;IndexArgItem _;IndexArgItem _], None -> Some (sliceOpPath, "GetArraySlice4DFixedTriple1", expandedIndexArgs) + | [IndexArgItem _;IndexArgRange _;IndexArgItem _;IndexArgItem _], None -> Some (sliceOpPath, "GetArraySlice4DFixedTriple2", expandedIndexArgs) + | [IndexArgItem _;IndexArgItem _;IndexArgRange _;IndexArgItem _], None -> Some (sliceOpPath, "GetArraySlice4DFixedTriple3", expandedIndexArgs) + | [IndexArgItem _;IndexArgItem _;IndexArgItem _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice4DFixedTriple4", expandedIndexArgs) + | [IndexArgItem _;IndexArgRange _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice3DFixedSingle1", (expandedIndexArgs @ [expr3])) + | [IndexArgRange _;IndexArgItem _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice3DFixedSingle2", (expandedIndexArgs @ [expr3])) + | [IndexArgRange _;IndexArgRange _;IndexArgItem _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice3DFixedSingle3", (expandedIndexArgs @ [expr3])) + | [IndexArgItem _;IndexArgItem _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice3DFixedDouble1", (expandedIndexArgs @ [expr3])) + | [IndexArgItem _;IndexArgRange _;IndexArgItem _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice3DFixedDouble2", (expandedIndexArgs @ [expr3])) + | [IndexArgRange _;IndexArgItem _;IndexArgItem _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice3DFixedDouble3", (expandedIndexArgs @ [expr3])) + | [IndexArgItem _;IndexArgRange _;IndexArgRange _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedSingle1", expandedIndexArgs @ [expr3]) + | [IndexArgRange _;IndexArgItem _;IndexArgRange _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedSingle2", expandedIndexArgs @ [expr3]) + | [IndexArgRange _;IndexArgRange _;IndexArgItem _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedSingle3", expandedIndexArgs @ [expr3]) + | [IndexArgRange _;IndexArgRange _;IndexArgRange _;IndexArgItem _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedSingle4", expandedIndexArgs @ [expr3]) + | [IndexArgItem _;IndexArgItem _;IndexArgRange _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedDouble1", expandedIndexArgs @ [expr3]) + | [IndexArgItem _;IndexArgRange _;IndexArgItem _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedDouble2", expandedIndexArgs @ [expr3]) + | [IndexArgItem _;IndexArgRange _;IndexArgRange _;IndexArgItem _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedDouble3", expandedIndexArgs @ [expr3]) + | [IndexArgRange _;IndexArgItem _;IndexArgItem _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedDouble4", expandedIndexArgs @ [expr3]) + | [IndexArgRange _;IndexArgItem _;IndexArgRange _;IndexArgItem _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedDouble5", expandedIndexArgs @ [expr3]) + | [IndexArgRange _;IndexArgRange _;IndexArgItem _;IndexArgItem _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedDouble6", expandedIndexArgs @ [expr3]) + | [IndexArgRange _;IndexArgItem _;IndexArgItem _;IndexArgItem _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedTriple1", expandedIndexArgs @ [expr3]) + | [IndexArgItem _;IndexArgRange _;IndexArgItem _;IndexArgItem _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedTriple2", expandedIndexArgs @ [expr3]) + | [IndexArgItem _;IndexArgItem _;IndexArgRange _;IndexArgItem _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedTriple3", expandedIndexArgs @ [expr3]) + | [IndexArgItem _;IndexArgItem _;IndexArgItem _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedTriple4", expandedIndexArgs @ [expr3]) + | _ -> None + | _ -> None + + /// Look up the appropriate string indexer or slicer path, method, and args. + let (|StringIndexerOrSlicer|_|) (indexArgs, setInfo) = + match indexArgs, setInfo with + | [IndexArgRange _], None -> Some (sliceOpPath, "GetStringSlice", expandedIndexArgs) + | [IndexArgItem _], None -> Some (indexOpPath, "GetString", expandedIndexArgs) + | _ -> None + + /// Try to find a member somewhere in the given type's hierarchy. + let tryFindMember choose exprTy = + FoldPrimaryHierarchyOfType + (fun ty -> Option.orElseWith (fun () -> choose ty)) + g + cenv.amap + mWholeExpr + AllowMultiIntfInstantiations.Yes + exprTy + None + + /// Try to find a property with the given name. + let tryFindNamedProp name ty = + let name = Some name + match AllPropInfosOfTypeInScope ResultCollectionSettings.AtMostOneResult cenv.infoReader env.NameEnv name ad IgnoreOverrides mWholeExpr ty with + | [] -> None + | _ :: _ -> name + + /// Try to find methods with the given name. + let tryFindMatchingMeths name choose ty = + match AllMethInfosOfTypeInScope ResultCollectionSettings.AllResults cenv.infoReader env.NameEnv (Some name) ad IgnoreOverrides mWholeExpr ty with + | [] -> None + | matchingMembers -> choose matchingMembers + + /// Try to find an instance getter with the given name and return type. + let tryFindInstanceGetter name exprTy retTy = + TryFindIntrinsicPropInfo cenv.infoReader mWholeExpr env.AccessRights name exprTy + |> List.tryFind (fun propInfo -> + not propInfo.IsStatic + && propInfo.HasGetter + && propInfo.GetterMethod.IsNullary + && typeEquivAux EraseMeasures g retTy (propInfo.GetterMethod.GetFSharpReturnType(cenv.amap, mWholeExpr, []))) + + /// The `Item` property name. + let Item = "Item" + + /// The `GetSlice` method name. + let GetSlice = "GetSlice" + + /// The `SetSlice` method name. + let SetSlice = "SetSlice" + + /// The `Slice` method name. + let Slice = "Slice" + + /// The `Length` property name. + let Length = "Length" + + /// The `Count` property name. + let Count = "Count" + + /// Try to find a `GetSlice` method. + /// If we have a `GetSlice` method, we don't need a `Length` or `Count` getter. + let tryFindGetSlice exprTy = + exprTy + |> tryFindMember (tryFindMatchingMeths GetSlice (fun _ -> Some GetSlice)) + + /// Try to find a `Slice` method with a single 2-tuple parameter. + /// If we have a `Slice` method, we also need a `Length` or `Count` getter. + let tryFindSlice exprTy = + exprTy + |> tryFindMember (tryFindMatchingMeths Slice (List.tryPick (fun slice -> + match slice.GetParamTypes(cenv.amap, mWholeExpr, slice.FormalMethodInst) with + | [[_; _] as tys] when tys |> List.forall (typeEquivAux EraseMeasures g g.int32_ty) -> Some slice + | _ -> None))) + |> Option.bind (fun sliceOverloads -> + tryFindInstanceGetter Length exprTy g.int32_ty + |> Option.orElseWith (fun () -> tryFindInstanceGetter Count exprTy g.int32_ty) + |> Option.map (fun getLength -> sliceOverloads, getLength.GetterMethod)) + + /// Whether the syntactic arguments are + /// indicative of indexing or slicing. + /// + /// Indexing := expr1[expr2] + /// + /// Slicing := expr1[expr2..] | expr1[..expr2] | expr1[expr2..expr3] + let (|Indexing|Slicing|) indexArgs = + if indexArgs |> List.forall (fun indexArg -> match indexArg with IndexArgItem _ -> true | _ -> false) then + Indexing + else + Slicing + + /// Match if we find an indexer property or method in scope for the type. + let (|Indexable|_|) exprTy = + exprTy + |> tryFindMember (fun ty -> + // Search each nominal type in the hierarchy for a `DefaultMemberAttribute`. + // If the type is not a nominal type, search for a property named `Item`. + match tryTcrefOfAppTy g ty with + | ValueSome tcref -> TryFindTyconRefStringAttribute g mWholeExpr g.attrib_DefaultMemberAttribute tcref + | ValueNone -> tryFindNamedProp Item ty) + + /// Fall back to `Item` for delayed lookup. + let (|PossiblyIndexable|) (_exprTy: TType) = Item + + /// Match if we find a `GetSlice` method in scope for the type. + let (|GetSliceable|_|) = tryFindGetSlice + + /// Match if we find a `Slice` method in scope for the type. + let (|Sliceable|_|) = tryFindSlice + + /// Fall back to `GetSlice` for delayed lookup. + let (|PossiblyGetSliceable|) (_exprTy: TType) = GetSlice + + /// Whether an index or slice is being gotten or set. + let (|Getting|Setting|) setInfo = + match setInfo with + | None -> Getting + | Some (setArg, mOfLeftOfSet) -> Setting (setArg, mOfLeftOfSet) let idxRange = indexArgs |> List.map (fun e -> e.Range) |> List.reduce unionRanges - let MakeIndexParam setSliceArrayOption = - match DecodeIndexArgs cenv indexArgs with - | [] -> failwith "unexpected empty index list" - | [IndexArgItem _] -> SynExpr.Paren (expandedIndexArgs.Head, range0, None, idxRange) - | _ -> SynExpr.Paren (SynExpr.Tuple (false, expandedIndexArgs @ Option.toList setSliceArrayOption, [], idxRange), range0, None, idxRange) - - let attemptArrayString = - let indexOpPath = ["Microsoft";"FSharp";"Core";"LanguagePrimitives";"IntrinsicFunctions"] - let sliceOpPath = ["Microsoft";"FSharp";"Core";"Operators";"OperatorIntrinsics"] - - let info = - if isArray then - let fixedIndex3d4dEnabled = g.langVersion.SupportsFeature LanguageFeature.FixedIndexSlice3d4d - let indexArgs = List.map (DecodeIndexArg cenv) indexArgs - match indexArgs, setInfo with - | [IndexArgItem _; IndexArgItem _], None -> Some (indexOpPath, "GetArray2D", expandedIndexArgs) - | [IndexArgItem _; IndexArgItem _; IndexArgItem _;], None -> Some (indexOpPath, "GetArray3D", expandedIndexArgs) - | [IndexArgItem _; IndexArgItem _; IndexArgItem _; IndexArgItem _], None -> Some (indexOpPath, "GetArray4D", expandedIndexArgs) - | [IndexArgItem _], None -> Some (indexOpPath, "GetArray", expandedIndexArgs) - | [IndexArgItem _; IndexArgItem _], Some (expr3, _) -> Some (indexOpPath, "SetArray2D", (expandedIndexArgs @ [expr3])) - | [IndexArgItem _; IndexArgItem _; IndexArgItem _;], Some (expr3, _) -> Some (indexOpPath, "SetArray3D", (expandedIndexArgs @ [expr3])) - | [IndexArgItem _; IndexArgItem _; IndexArgItem _; IndexArgItem _], Some (expr3, _) -> Some (indexOpPath, "SetArray4D", (expandedIndexArgs @ [expr3])) - | [IndexArgItem _], Some (expr3, _) -> Some (indexOpPath, "SetArray", (expandedIndexArgs @ [expr3])) - | [IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice", expandedIndexArgs) - | [IndexArgItem _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice2DFixed1", expandedIndexArgs) - | [IndexArgRange _;IndexArgItem _], None -> Some (sliceOpPath, "GetArraySlice2DFixed2", expandedIndexArgs) - | [IndexArgRange _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice2D", expandedIndexArgs) - | [IndexArgRange _;IndexArgRange _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice3D", expandedIndexArgs) - | [IndexArgRange _;IndexArgRange _;IndexArgRange _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice4D", expandedIndexArgs) - | [IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice", (expandedIndexArgs @ [expr3])) - | [IndexArgRange _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice2D", (expandedIndexArgs @ [expr3])) - | [IndexArgItem _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice2DFixed1", (expandedIndexArgs @ [expr3])) - | [IndexArgRange _;IndexArgItem _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice2DFixed2", (expandedIndexArgs @ [expr3])) - | [IndexArgRange _;IndexArgRange _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice3D", (expandedIndexArgs @ [expr3])) - | [IndexArgRange _;IndexArgRange _;IndexArgRange _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4D", (expandedIndexArgs @ [expr3])) - | _ when fixedIndex3d4dEnabled -> - match indexArgs, setInfo with - | [IndexArgItem _;IndexArgRange _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice3DFixedSingle1", expandedIndexArgs) - | [IndexArgRange _;IndexArgItem _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice3DFixedSingle2", expandedIndexArgs) - | [IndexArgRange _;IndexArgRange _;IndexArgItem _], None -> Some (sliceOpPath, "GetArraySlice3DFixedSingle3", expandedIndexArgs) - | [IndexArgItem _;IndexArgItem _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice3DFixedDouble1", expandedIndexArgs) - | [IndexArgItem _;IndexArgRange _;IndexArgItem _], None -> Some (sliceOpPath, "GetArraySlice3DFixedDouble2", expandedIndexArgs) - | [IndexArgRange _;IndexArgItem _;IndexArgItem _], None -> Some (sliceOpPath, "GetArraySlice3DFixedDouble3", expandedIndexArgs) - | [IndexArgItem _;IndexArgRange _;IndexArgRange _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice4DFixedSingle1", expandedIndexArgs) - | [IndexArgRange _;IndexArgItem _;IndexArgRange _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice4DFixedSingle2", expandedIndexArgs) - | [IndexArgRange _;IndexArgRange _;IndexArgItem _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice4DFixedSingle3", expandedIndexArgs) - | [IndexArgRange _;IndexArgRange _;IndexArgRange _;IndexArgItem _], None -> Some (sliceOpPath, "GetArraySlice4DFixedSingle4", expandedIndexArgs) - | [IndexArgItem _;IndexArgItem _;IndexArgRange _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice4DFixedDouble1", expandedIndexArgs) - | [IndexArgItem _;IndexArgRange _;IndexArgItem _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice4DFixedDouble2", expandedIndexArgs) - | [IndexArgItem _;IndexArgRange _;IndexArgRange _;IndexArgItem _], None -> Some (sliceOpPath, "GetArraySlice4DFixedDouble3", expandedIndexArgs) - | [IndexArgRange _;IndexArgItem _;IndexArgItem _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice4DFixedDouble4", expandedIndexArgs) - | [IndexArgRange _;IndexArgItem _;IndexArgRange _;IndexArgItem _], None -> Some (sliceOpPath, "GetArraySlice4DFixedDouble5", expandedIndexArgs) - | [IndexArgRange _;IndexArgRange _;IndexArgItem _;IndexArgItem _], None -> Some (sliceOpPath, "GetArraySlice4DFixedDouble6", expandedIndexArgs) - | [IndexArgRange _;IndexArgItem _;IndexArgItem _;IndexArgItem _], None -> Some (sliceOpPath, "GetArraySlice4DFixedTriple1", expandedIndexArgs) - | [IndexArgItem _;IndexArgRange _;IndexArgItem _;IndexArgItem _], None -> Some (sliceOpPath, "GetArraySlice4DFixedTriple2", expandedIndexArgs) - | [IndexArgItem _;IndexArgItem _;IndexArgRange _;IndexArgItem _], None -> Some (sliceOpPath, "GetArraySlice4DFixedTriple3", expandedIndexArgs) - | [IndexArgItem _;IndexArgItem _;IndexArgItem _;IndexArgRange _], None -> Some (sliceOpPath, "GetArraySlice4DFixedTriple4", expandedIndexArgs) - | [IndexArgItem _;IndexArgRange _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice3DFixedSingle1", (expandedIndexArgs @ [expr3])) - | [IndexArgRange _;IndexArgItem _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice3DFixedSingle2", (expandedIndexArgs @ [expr3])) - | [IndexArgRange _;IndexArgRange _;IndexArgItem _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice3DFixedSingle3", (expandedIndexArgs @ [expr3])) - | [IndexArgItem _;IndexArgItem _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice3DFixedDouble1", (expandedIndexArgs @ [expr3])) - | [IndexArgItem _;IndexArgRange _;IndexArgItem _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice3DFixedDouble2", (expandedIndexArgs @ [expr3])) - | [IndexArgRange _;IndexArgItem _;IndexArgItem _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice3DFixedDouble3", (expandedIndexArgs @ [expr3])) - | [IndexArgItem _;IndexArgRange _;IndexArgRange _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedSingle1", expandedIndexArgs @ [expr3]) - | [IndexArgRange _;IndexArgItem _;IndexArgRange _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedSingle2", expandedIndexArgs @ [expr3]) - | [IndexArgRange _;IndexArgRange _;IndexArgItem _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedSingle3", expandedIndexArgs @ [expr3]) - | [IndexArgRange _;IndexArgRange _;IndexArgRange _;IndexArgItem _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedSingle4", expandedIndexArgs @ [expr3]) - | [IndexArgItem _;IndexArgItem _;IndexArgRange _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedDouble1", expandedIndexArgs @ [expr3]) - | [IndexArgItem _;IndexArgRange _;IndexArgItem _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedDouble2", expandedIndexArgs @ [expr3]) - | [IndexArgItem _;IndexArgRange _;IndexArgRange _;IndexArgItem _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedDouble3", expandedIndexArgs @ [expr3]) - | [IndexArgRange _;IndexArgItem _;IndexArgItem _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedDouble4", expandedIndexArgs @ [expr3]) - | [IndexArgRange _;IndexArgItem _;IndexArgRange _;IndexArgItem _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedDouble5", expandedIndexArgs @ [expr3]) - | [IndexArgRange _;IndexArgRange _;IndexArgItem _;IndexArgItem _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedDouble6", expandedIndexArgs @ [expr3]) - | [IndexArgRange _;IndexArgItem _;IndexArgItem _;IndexArgItem _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedTriple1", expandedIndexArgs @ [expr3]) - | [IndexArgItem _;IndexArgRange _;IndexArgItem _;IndexArgItem _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedTriple2", expandedIndexArgs @ [expr3]) - | [IndexArgItem _;IndexArgItem _;IndexArgRange _;IndexArgItem _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedTriple3", expandedIndexArgs @ [expr3]) - | [IndexArgItem _;IndexArgItem _;IndexArgItem _;IndexArgRange _], Some (expr3, _) -> Some (sliceOpPath, "SetArraySlice4DFixedTriple4", expandedIndexArgs @ [expr3]) - | _ -> None - | _ -> None + let parenthesize synExpr = SynExpr.Paren (synExpr, range0, None, idxRange) + + let tupleIfMultiple decodedIndexArgs expandedIndexArgs = + match decodedIndexArgs, expandedIndexArgs with + | [IndexArgItem _], [arg] -> arg + | _, args -> SynExpr.Tuple (false, args, [], idxRange) + + /// expr1[expr2] + let mkDelayedIndexedGet indexer indexArgs = + [ DelayedDotLookup([ident(indexer, mWholeExpr)], mWholeExpr) + DelayedApp(ExprAtomicFlag.Atomic, true, synLeftExprOpt, parenthesize (tupleIfMultiple indexArgs expandedIndexArgs), mWholeExpr) ] + + /// expr1[expr2] <- expr3 + let mkDelayedIndexedSet indexer indexArgs setArg mOfLeftOfSet = + [ DelayedDotLookup([ident(indexer, mOfLeftOfSet)], mOfLeftOfSet) + DelayedApp(ExprAtomicFlag.Atomic, true, synLeftExprOpt, parenthesize (tupleIfMultiple indexArgs expandedIndexArgs), mOfLeftOfSet) + MakeDelayedSet(setArg, mWholeExpr) ] + + /// expr1[expr2..] + /// expr1[..expr2] + /// expr1[expr2..expr3] + let mkDelayedGetSlice indexer indexArgs = + [ DelayedDotLookup([ident(indexer, mWholeExpr)], mWholeExpr) + DelayedApp(ExprAtomicFlag.Atomic, true, synLeftExprOpt, parenthesize (tupleIfMultiple indexArgs expandedIndexArgs), mWholeExpr) ] + + /// expr1[expr2..] <- expr3 + /// expr1[..expr2] <- expr3 + /// expr1[expr2..expr3] <- expr3 + let mkDelayedSetSlice indexArgs setArg mOfLeftOfSet = + [ DelayedDotLookup([ident(SetSlice, mOfLeftOfSet)], mOfLeftOfSet) + DelayedApp(ExprAtomicFlag.Atomic, true, synLeftExprOpt, parenthesize (tupleIfMultiple indexArgs (expandedIndexArgs @ [setArg])), mOfLeftOfSet) ] + + /// Match if we can generate a call to a `Slice` method. + let (|Sliceable|_|) ((indexArgs, setInfo), exprTy) = + match (indexArgs, setInfo), exprTy with + | (Slicing & indexArgs, Getting), Sliceable (slice, getLength) -> + let mkAdd m expr1 expr2 = mkCallAdditionOperator g m g.int32_ty expr1 expr2 + let mkSub m expr1 expr2 = mkCallSubtractionOperator g m g.int32_ty expr1 expr2 + + /// Match if the given expression is or is equivalent to System.Int32, + /// ignoring units of measure if present. + let (|Int32|_|) synExpr = + let expr, ty, tpenv = TcExprOfUnknownType cenv env tpenv synExpr + if typeEquivAux EraseMeasures g ty g.int32_ty then Some (Int32 (expr, tpenv)) + else None + + match indexArgs with + // expr[start..finish] + | [IndexArgRange (Some (Int32 (start, _), false), Some (Int32 (finish, tpenv), false), m1, m2)] -> + // Compiles to: + // + // let expr = expr in + // let len = expr.Length in + // let start = min (max start 0) len in + // expr.Slice (start, max (min (finish - start + 1) (len - start)) 0) - elif isString then - match DecodeIndexArgs cenv indexArgs, setInfo with - | [IndexArgRange _], None -> Some (sliceOpPath, "GetStringSlice", expandedIndexArgs) - | [IndexArgItem _], None -> Some (indexOpPath, "GetString", expandedIndexArgs) - | _ -> None + let mLhs = expr.Range + let tcVal = LightweightTcValForUsingInBuildMethodCall g - else None + let exprVal, exprExpr = mkCompGenLocal mLhs "expr" exprTy + let lenVal, lenExpr = mkCompGenLocal mLhs "len" g.int32_ty + let startVal, startExpr = mkCompGenLocal mLhs "start" g.int32_ty - match info with - | None -> None - | Some (path, functionName, indexArgs) -> - let operPath = mkSynLidGet (mDot.MakeSynthetic()) path functionName - let f, fty, tpenv = TcExprOfUnknownType cenv env tpenv operPath - let domainTy, resultTy = UnifyFunctionType (Some mWholeExpr) cenv env.DisplayEnv mWholeExpr fty - UnifyTypes cenv env mWholeExpr domainTy exprTy - let f', resultTy = buildApp cenv (MakeApplicableExprNoFlex cenv f) resultTy expr mWholeExpr - let delayed = List.foldBack (fun idx acc -> DelayedApp(ExprAtomicFlag.Atomic, true, None, idx, mWholeExpr) :: acc) indexArgs delayed // atomic, otherwise no ar.[1] <- xyz - Some (PropagateThenTcDelayed cenv overallTy env tpenv mWholeExpr f' resultTy ExprAtomicFlag.Atomic delayed ) - - match attemptArrayString with - | Some res -> res - | None when isNominal || Option.isSome propName -> - let nm = - match propName with - | None -> "Item" - | Some nm -> nm - let delayed = - match setInfo with - // expr1.[expr2] - | None -> - [ DelayedDotLookup([ ident(nm, mWholeExpr)], mWholeExpr) - DelayedApp(ExprAtomicFlag.Atomic, true, synLeftExprOpt, MakeIndexParam None, mWholeExpr) - yield! delayed ] - - // expr1.[expr2] <- expr3 --> expr1.Item(expr2) <- expr3 - | Some (expr3, mOfLeftOfSet) -> - if isIndex then - [ DelayedDotLookup([ident(nm, mOfLeftOfSet)], mOfLeftOfSet) - DelayedApp(ExprAtomicFlag.Atomic, true, synLeftExprOpt, MakeIndexParam None, mOfLeftOfSet) - MakeDelayedSet(expr3, mWholeExpr) - yield! delayed ] - else - [ DelayedDotLookup([ident("SetSlice", mOfLeftOfSet)], mOfLeftOfSet) - DelayedApp(ExprAtomicFlag.Atomic, true, synLeftExprOpt, MakeIndexParam (Some expr3), mWholeExpr) - yield! delayed ] + let len, _ = BuildMethodCall tcVal g cenv.amap PossiblyMutates mWholeExpr true getLength ValUseFlag.NormalValUse [] [exprExpr] [] None + let start = mkCallMinOperator g m1 g.int32_ty (mkCallMaxOperator g m1 g.int32_ty start (mkZero g m1)) lenExpr + let sliceLen = mkCallMaxOperator g m2 g.int32_ty (mkCallMinOperator g m2 g.int32_ty (mkAdd m2 (mkSub m2 finish startExpr) (mkOne g m2)) (mkSub m2 lenExpr startExpr)) (mkZero g m2) + let slice, ty = BuildMethodCall tcVal g cenv.amap PossiblyMutates mWholeExpr false slice ValUseFlag.NormalValUse slice.FormalMethodInst [exprExpr] [startExpr; sliceLen] None + + let expr = + mkCompGenLet mLhs exprVal expr + (mkCompGenLet mLhs lenVal len + (mkCompGenLet m1 startVal start + slice)) + + Some (tpenv, expr, ty) + + // expr[^start..finish] + | [IndexArgRange (Some (Int32 (start, _), true), Some (Int32 (finish, tpenv), false), m1, m2)] -> + // Compiles to: + // + // let expr = expr in + // let len = expr.Length in + // let start = max (len - start) 0 in + // expr.Slice (start, max (min (finish - start + 1) (len - start)) 0) + + let mLhs = expr.Range + let tcVal = LightweightTcValForUsingInBuildMethodCall g + + let exprVal, exprExpr = mkCompGenLocal mLhs "expr" exprTy + let lenVal, lenExpr = mkCompGenLocal mLhs "len" g.int32_ty + let startVal, startExpr = mkCompGenLocal mLhs "start" g.int32_ty + + let len, _ = BuildMethodCall tcVal g cenv.amap PossiblyMutates mWholeExpr true getLength ValUseFlag.NormalValUse [] [exprExpr] [] None + let start = mkCallMaxOperator g m1 g.int32_ty (mkSub m1 lenExpr start) (mkZero g m1) + let sliceLen = mkCallMaxOperator g m2 g.int32_ty (mkCallMinOperator g m2 g.int32_ty (mkAdd m2 (mkSub m2 finish startExpr) (mkOne g m2)) (mkSub m2 lenExpr startExpr)) (mkZero g m2) + let slice, ty = BuildMethodCall tcVal g cenv.amap PossiblyMutates mWholeExpr false slice ValUseFlag.NormalValUse slice.FormalMethodInst [exprExpr] [startExpr; sliceLen] None + + let expr = + mkCompGenLet mLhs exprVal expr + (mkCompGenLet mLhs lenVal len + (mkCompGenLet m1 startVal start + slice)) + + Some (tpenv, expr, ty) + + // expr[start..^finish] + | [IndexArgRange (Some (Int32 (start, _), false), Some (Int32 (finish, tpenv), true), m1, m2)] -> + // Compiles to: + // + // let expr = expr in + // let len = expr.Length in + // let start = min (max start 0) len in + // expr.Slice (start, max (min (len - finish - start + 1) len) 0) + let mLhs = expr.Range + let tcVal = LightweightTcValForUsingInBuildMethodCall g + + let exprVal, exprExpr = mkCompGenLocal mLhs "expr" exprTy + let lenVal, lenExpr = mkCompGenLocal mLhs "len" g.int32_ty + let startVal, startExpr = mkCompGenLocal mLhs "start" g.int32_ty + + let len, _ = BuildMethodCall tcVal g cenv.amap PossiblyMutates mWholeExpr true getLength ValUseFlag.NormalValUse [] [exprExpr] [] None + let start = mkCallMinOperator g m1 g.int32_ty (mkCallMaxOperator g m1 g.int32_ty start (mkZero g m1)) lenExpr + let sliceLen = mkCallMaxOperator g m2 g.int32_ty (mkCallMinOperator g m2 g.int32_ty (mkAdd m2 (mkSub m2 (mkSub m2 lenExpr finish) startExpr) (mkOne g m2)) lenExpr) (mkZero g m2) + let slice, ty = BuildMethodCall tcVal g cenv.amap PossiblyMutates mWholeExpr false slice ValUseFlag.NormalValUse slice.FormalMethodInst [exprExpr] [startExpr; sliceLen] None + + let expr = + mkCompGenLet mLhs exprVal expr + (mkCompGenLet mLhs lenVal len + (mkCompGenLet m1 startVal start + slice)) + + Some (tpenv, expr, ty) + + // expr[^start..^finish] + | [IndexArgRange (Some (Int32 (start, _), true), Some (Int32 (finish, tpenv), true), m1, m2)] -> + // Compiles to: + // + // let expr = expr in + // let len = expr.Length in + // let start = max (len - start) 0 in + // expr.Slice (start, max (min (len - finish - start + 1) len) 0) + + let mLhs = expr.Range + let tcVal = LightweightTcValForUsingInBuildMethodCall g + + let exprVal, exprExpr = mkCompGenLocal mLhs "expr" exprTy + let lenVal, lenExpr = mkCompGenLocal mLhs "len" g.int32_ty + let startVal, startExpr = mkCompGenLocal mLhs "start" g.int32_ty + + let len, _ = BuildMethodCall tcVal g cenv.amap PossiblyMutates mWholeExpr true getLength ValUseFlag.NormalValUse [] [exprExpr] [] None + let start = mkCallMaxOperator g m1 g.int32_ty (mkSub m1 lenExpr start) (mkZero g m1) + let sliceLen = mkCallMaxOperator g m2 g.int32_ty (mkCallMinOperator g m2 g.int32_ty (mkAdd m2 (mkSub m2 (mkSub m2 lenExpr finish) startExpr) (mkOne g m2)) lenExpr) (mkZero g m2) + let slice, ty = BuildMethodCall tcVal g cenv.amap PossiblyMutates mWholeExpr false slice ValUseFlag.NormalValUse slice.FormalMethodInst [exprExpr] [startExpr; sliceLen] None + + let expr = + mkCompGenLet mLhs exprVal expr + (mkCompGenLet mLhs lenVal len + (mkCompGenLet m1 startVal start + slice)) + + Some (tpenv, expr, ty) + + // expr[start..] + | [IndexArgRange (Some (Int32 (start, tpenv), false), None, m1, m2)] -> + // Compiles to: + // + // let expr = expr in + // let len = expr.Length in + // let start = min (max start 0) len in + // expr.Slice (start, len - start) + + let mLhs = expr.Range + let tcVal = LightweightTcValForUsingInBuildMethodCall g + + let exprVal, exprExpr = mkCompGenLocal mLhs "expr" exprTy + let lenVal, lenExpr = mkCompGenLocal mLhs "len" g.int32_ty + let startVal, startExpr = mkCompGenLocal mLhs "start" g.int32_ty + + let len, _ = BuildMethodCall tcVal g cenv.amap PossiblyMutates mWholeExpr true getLength ValUseFlag.NormalValUse [] [exprExpr] [] None + let start = mkCallMinOperator g m1 g.int32_ty (mkCallMaxOperator g m1 g.int32_ty start (mkZero g m1)) lenExpr + let sliceLen = mkSub m2 lenExpr startExpr + let slice, ty = BuildMethodCall tcVal g cenv.amap PossiblyMutates mWholeExpr false slice ValUseFlag.NormalValUse slice.FormalMethodInst [exprExpr] [startExpr; sliceLen] None + + let expr = + mkCompGenLet mLhs exprVal expr + (mkCompGenLet mLhs lenVal len + (mkCompGenLet m1 startVal start + slice)) + + Some (tpenv, expr, ty) + + // expr[..finish] + | [IndexArgRange (None, Some (Int32 (finish, tpenv), false), m1, m2)] -> + // Compiles to: + // + // let expr = expr in + // expr.Slice (0, max (min (finish + 1) expr.Length) 0) + + let mLhs = expr.Range + let tcVal = LightweightTcValForUsingInBuildMethodCall g + + let exprVal, exprExpr = mkCompGenLocal mLhs "expr" exprTy + + let len, _ = BuildMethodCall tcVal g cenv.amap PossiblyMutates mWholeExpr true getLength ValUseFlag.NormalValUse [] [exprExpr] [] None + let start = mkZero g m1 + let sliceLen = mkCallMaxOperator g m2 g.int32_ty (mkCallMinOperator g m2 g.int32_ty (mkAdd m2 finish (mkOne g m2)) len) (mkZero g m2) + let slice, ty = BuildMethodCall tcVal g cenv.amap PossiblyMutates mWholeExpr false slice ValUseFlag.NormalValUse slice.FormalMethodInst [exprExpr] [start; sliceLen] None + + let expr = mkCompGenLet mLhs exprVal expr slice + + Some (tpenv, expr, ty) + + | _ -> None + + | _ -> None + + /// Finish typechecking array or string indexing. + let tcArrayOrStringIndexing (path, functionName, indexArgs) = + let operPath = mkSynLidGet (mDot.MakeSynthetic()) path functionName + let f, fty, tpenv = TcExprOfUnknownType cenv env tpenv operPath + let domainTy, resultTy = UnifyFunctionType (Some mWholeExpr) cenv env.DisplayEnv mWholeExpr fty + UnifyTypes cenv env mWholeExpr domainTy exprTy + let f', resultTy = buildApp cenv (MakeApplicableExprNoFlex cenv f) resultTy expr mWholeExpr + let delayed = List.foldBack (fun idx acc -> DelayedApp(ExprAtomicFlag.Atomic, true, None, idx, mWholeExpr) :: acc) indexArgs delayed // atomic, otherwise no ar.[1] <- xyz + PropagateThenTcDelayed cenv overallTy env tpenv mWholeExpr f' resultTy ExprAtomicFlag.Atomic delayed + + let propagateThenTcDelayed tpenv expr exprTy delayed = PropagateThenTcDelayed cenv overallTy env tpenv mDot (MakeApplicableExprNoFlex cenv expr) exprTy ExprAtomicFlag.Atomic delayed + let decodedIndexArgs = DecodeIndexArgs cenv indexArgs + + match (decodedIndexArgs, setInfo), exprTy with + // Look for FSharp.Core array and string indexing/slicing helpers. + | (_, Array) & (ArrayIndexerOrSlicer (path, meth, args), _) + | (_, String) & (StringIndexerOrSlicer (path, meth, args), _) -> tcArrayOrStringIndexing (path, meth, args) + + // Look for an indexer property, or else assume `Item`. + | (Indexing, Getting), Indexable indexer + | (Indexing, Getting), (Array | Nominal) & PossiblyIndexable indexer -> + propagateThenTcDelayed tpenv expr exprTy (mkDelayedIndexedGet indexer decodedIndexArgs @ delayed) + + // Look for `GetSlice`. + | (Slicing, Getting), Nominal & GetSliceable slicer -> + propagateThenTcDelayed tpenv expr exprTy (mkDelayedGetSlice slicer decodedIndexArgs @ delayed) + + // In the absence of `GetSlice`, look for `Slice`. + | ((Slicing, Getting), Nominal) & Sliceable (tpenv, expr, exprTy) -> + propagateThenTcDelayed tpenv expr exprTy delayed + + // In the immediate absence of either, assume `GetSlice`. + | (Slicing, Getting), PossiblyGetSliceable slicer -> + propagateThenTcDelayed tpenv expr exprTy (mkDelayedGetSlice slicer decodedIndexArgs @ delayed) + + // Look for an indexer property, or else assume `Item`. + | (Indexing, Setting (setArg, mOfLeftOfSet)), Indexable indexer + | (Indexing, Setting (setArg, mOfLeftOfSet)), (Array | Nominal) & PossiblyIndexable indexer -> + propagateThenTcDelayed tpenv expr exprTy (mkDelayedIndexedSet indexer decodedIndexArgs setArg mOfLeftOfSet @ delayed) + + // Assume `SetSlice`. + | (Slicing, Setting (setArg, mOfLeftOfSet)), (Array | Nominal) -> + propagateThenTcDelayed tpenv expr exprTy (mkDelayedSetSlice decodedIndexArgs setArg mOfLeftOfSet @ delayed) + | _ -> // deprecated constrained lookup error(Error(FSComp.SR.tcObjectOfIndeterminateTypeUsedRequireTypeConstraint(), mWholeExpr)) diff --git a/src/Compiler/TypedTree/TcGlobals.fs b/src/Compiler/TypedTree/TcGlobals.fs index 69a99dfe119..5a738f5558a 100644 --- a/src/Compiler/TypedTree/TcGlobals.fs +++ b/src/Compiler/TypedTree/TcGlobals.fs @@ -675,6 +675,8 @@ type TcGlobals( let v_or_info = makeIntrinsicValRef(fslib_MFIntrinsicOperators_nleref, "or" , None , Some "Or" , [], mk_rel_sig v_bool_ty) let v_or2_info = makeIntrinsicValRef(fslib_MFIntrinsicOperators_nleref, CompileOpName "||" , None , None , [], mk_rel_sig v_bool_ty) let v_compare_operator_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "compare" , None , Some "Compare", [vara], mk_compare_sig varaTy) + let v_max_operator_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "max" , None , Some "Max" , [vara], ([[varaTy];[varaTy]], varaTy)) + let v_min_operator_info = makeIntrinsicValRef(fslib_MFOperators_nleref, "min" , None , Some "Min" , [vara], ([[varaTy];[varaTy]], varaTy)) let v_equals_operator_info = makeIntrinsicValRef(fslib_MFOperators_nleref, CompileOpName "=" , None , None , [vara], mk_rel_sig varaTy) let v_equals_nullable_operator_info = makeIntrinsicValRef(fslib_MFNullableOperators_nleref, CompileOpName "=?" , None , None , [vara], ([[varaTy];[mkNullableTy varaTy]], v_bool_ty)) let v_nullable_equals_operator_info = makeIntrinsicValRef(fslib_MFNullableOperators_nleref, CompileOpName "?=" , None , None , [vara], ([[mkNullableTy varaTy];[varaTy]], v_bool_ty)) @@ -1673,6 +1675,8 @@ type TcGlobals( member val invalid_op_vref = ValRefForIntrinsic v_invalid_op_info member val failwithf_vref = ValRefForIntrinsic v_failwithf_info + member _.max_operator_info = v_max_operator_info + member _.min_operator_info = v_min_operator_info member _.equals_operator_info = v_equals_operator_info member _.not_equals_operator = v_not_equals_operator_info member _.less_than_operator = v_less_than_operator_info diff --git a/src/Compiler/TypedTree/TypedTreeOps.fs b/src/Compiler/TypedTree/TypedTreeOps.fs index 120baae8461..3d60ded3a6b 100644 --- a/src/Compiler/TypedTree/TypedTreeOps.fs +++ b/src/Compiler/TypedTree/TypedTreeOps.fs @@ -7747,6 +7747,10 @@ let mkCallGenericEqualityWithComparerOuter (g: TcGlobals) m ty comp e1 e2 = mkAp let mkCallGenericHashWithComparerOuter (g: TcGlobals) m ty comp e1 = mkApps g (typedExprForIntrinsic g m g.generic_hash_withc_outer_info, [[ty]], [comp;e1], m) +let mkCallMaxOperator (g: TcGlobals) m ty e1 e2 = mkApps g (typedExprForIntrinsic g m g.max_operator_info, [[ty]], [ e1;e2 ], m) + +let mkCallMinOperator (g: TcGlobals) m ty e1 e2 = mkApps g (typedExprForIntrinsic g m g.min_operator_info, [[ty]], [ e1;e2 ], m) + let mkCallEqualsOperator (g: TcGlobals) m ty e1 e2 = mkApps g (typedExprForIntrinsic g m g.equals_operator_info, [[ty]], [ e1;e2 ], m) let mkCallNotEqualsOperator (g: TcGlobals) m ty e1 e2 = mkApps g (typedExprForIntrinsic g m g.not_equals_operator, [[ty]], [ e1;e2 ], m) diff --git a/src/Compiler/TypedTree/TypedTreeOps.fsi b/src/Compiler/TypedTree/TypedTreeOps.fsi index cec67a4961d..b6bf52fa5ff 100755 --- a/src/Compiler/TypedTree/TypedTreeOps.fsi +++ b/src/Compiler/TypedTree/TypedTreeOps.fsi @@ -2084,6 +2084,10 @@ val mkCallGenericEqualityWithComparerOuter: TcGlobals -> range -> TType -> Expr val mkCallGenericHashWithComparerOuter: TcGlobals -> range -> TType -> Expr -> Expr -> Expr +val mkCallMaxOperator: g: TcGlobals -> m: range -> ty: TType -> e1: Expr -> e2: Expr -> Expr + +val mkCallMinOperator: g: TcGlobals -> m: range -> ty: TType -> e1: Expr -> e2: Expr -> Expr + val mkCallEqualsOperator: TcGlobals -> range -> TType -> Expr -> Expr -> Expr val mkCallNotEqualsOperator: TcGlobals -> range -> TType -> Expr -> Expr -> Expr diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MemberDefinitions/MethodsAndProperties/MethodsAndProperties.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MemberDefinitions/MethodsAndProperties/MethodsAndProperties.fs index a71f63cbfb9..a2bfd5fbfdb 100644 --- a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MemberDefinitions/MethodsAndProperties/MethodsAndProperties.fs +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/MemberDefinitions/MethodsAndProperties/MethodsAndProperties.fs @@ -667,4 +667,384 @@ type GenericIndexer<'indexerArgs,'indexerOutput,'indexerInput>() = |> withLangVersionPreview |> typecheck |> shouldFail - |> withSingleDiagnostic (Warning 3581, Line 9, Col 17, Line 9, Col 21, "An indexed property's getter and setter must have the same type. Property 'Item' has getter of type ''indexerOutput' but setter of type ''indexerInput'.") \ No newline at end of file + |> withSingleDiagnostic (Warning 3581, Line 9, Col 17, Line 9, Col 21, "An indexed property's getter and setter must have the same type. Property 'Item' has getter of type ''indexerOutput' but setter of type ''indexerInput'.") + +// Strictly speaking, some of these could be made to work for netfx, +// but they would require more custom setup (since we couldn't use ReadOnlySpan.Slice). +#if NETCOREAPP2_1_OR_GREATER + module Slicing = + let [] SupportedVersion = "preview" + + /// Slicing tests for types in the System namespace that + /// expose a Slice method (but not a GetSlice method). + module SystemTypes = + [] + let ``readOnlySpan[start..finish]``() = + Fsx """ + open System + + let f () = + let span = "abc123".AsSpan () + let actual = span[2..3] + let expected = span.Slice (2, 2) // "c1" + + if not (expected.SequenceEqual actual) then + failwith $"Expected '{expected.ToString ()}' but got '{actual.ToString ()}'." + + f () + """ + |> withLangVersion SupportedVersion + |> runFsi + |> shouldSucceed + + [] + let ``readOnlySpan[start..]``() = + Fsx """ + open System + + let f () = + let span = "abc123".AsSpan () + let actual = span[2..] + let expected = span.Slice (2, span.Length - 2) // "c123" + + if not (expected.SequenceEqual actual) then + failwith $"Expected '{expected.ToString ()}' but got '{actual.ToString ()}'." + + f () + """ + |> withLangVersion SupportedVersion + |> runFsi + |> shouldSucceed + + [] + let ``readOnlySpan[..finish]``() = + Fsx """ + open System + + let f () = + let span = "abc123".AsSpan () + let actual = span[..3] + let expected = span.Slice (0, 4) // "abc1" + + if not (expected.SequenceEqual actual) then + failwith $"Expected '{expected.ToString ()}' but got '{actual.ToString ()}'." + + f () + """ + |> withLangVersion SupportedVersion + |> runFsi + |> shouldSucceed + + [] + let ``readOnlySpan[start () .. finish ()]``() = + Fsx """ + open System + + let f () = + let effects = ResizeArray () + let start () = effects.Add "start"; 2 + let finish () = effects.Add "finish"; 3 + let span = "abc123".AsSpan () + let actual = span[start () .. finish ()] + let expected = span.Slice (2, 2) // "c1" + + if not (expected.SequenceEqual actual) then + failwith $"Expected '{expected.ToString ()}' but got '{actual.ToString ()}'." + + match List.ofSeq effects with + | ["start"; "finish"] -> () + | actual -> failwith $"Expected '[\"start\"; \"finish\"]' but got '%A{actual}'." + + f () + """ + |> withLangVersion SupportedVersion + |> runFsi + |> shouldSucceed + + [] + let ``readOnlySpan[start () ..]``() = + Fsx """ + open System + + let f () = + let effects = ResizeArray () + let start () = effects.Add "start"; 2 + let span = "abc123".AsSpan () + let actual = span[start () ..] + let expected = span.Slice (2, span.Length - 2) // "c123" + + if not (expected.SequenceEqual actual) then + failwith $"Expected '{expected.ToString ()}' but got '{actual.ToString ()}'." + + match List.ofSeq effects with + | ["start"] -> () + | actual -> failwith $"Expected '[\"start\"]' but got '%A{actual}'." + + f () + """ + |> withLangVersion SupportedVersion + |> runFsi + |> shouldSucceed + + [] + let ``readOnlySpan[.. finish ()]``() = + Fsx """ + open System + + let f () = + let effects = ResizeArray () + let finish () = effects.Add "finish"; 3 + let span = "abc123".AsSpan () + let actual = span[.. finish ()] + let expected = span.Slice (0, 4) // "abc1" + + if not (expected.SequenceEqual actual) then + failwith $"Expected '{expected.ToString ()}' but got '{actual.ToString ()}'." + + match List.ofSeq effects with + | ["finish"] -> () + | actual -> failwith $"Expected '[\"finish\"]' but got '%A{actual}'." + + f () + """ + |> withLangVersion SupportedVersion + |> runFsi + |> shouldSucceed + + [] + let ``(readOnlySpan ())[start..finish]``() = + Fsx """ + open System + + let effects = ResizeArray () + + let span () = + effects.Add "span" + "abc123".AsSpan () + + let f () = + let actual = (span ())[2..3] + let expected = "c1".AsSpan () + + if not (expected.SequenceEqual actual) then + failwith $"Expected '{expected.ToString ()}' but got '{actual.ToString ()}'." + + match List.ofSeq effects with + | ["span"] -> () + | actual -> failwith $"Expected '[\"span\"]' but got '%A{actual}'." + + f () + """ + |> withLangVersion SupportedVersion + |> runFsi + |> shouldSucceed + + [] + let ``(readOnlySpan ())[^start..^finish]``() = + Fsx """ + open System + + let effects = ResizeArray () + + let span () = + effects.Add "span" + "abc123".AsSpan () + + let f () = + let actual = (span ())[^4..^3] + let expected = "c1".AsSpan () + + if not (expected.SequenceEqual actual) then + failwith $"Expected '{expected.ToString ()}' but got '{actual.ToString ()}'." + + match List.ofSeq effects with + | ["span"] -> () + | actual -> failwith $"Expected '[\"span\"]' but got '%A{actual}'." + + f () + """ + |> withLangVersion SupportedVersion + |> runFsi + |> shouldSucceed + + /// Slicing tests for types that expose both Slice and GetSlice methods. + module CustomTypes = + [] + let ``sliceAndGetSlice[start..finish]``() = + Fsx """ + open System + + let effects = ResizeArray () + + type T = + | T + + member _.Length = 99 + + member this.GetSlice (start, finish) = + let start = defaultArg start 0 + let finish = defaultArg finish this.Length + effects.Add "GetSlice" + [start..finish] + + member _.Slice (start, length) = + effects.Add "Slice" + [start..length - start] + + let f () = + let actual = T[2..3] + let expected = [2..3] + + if expected <> actual then + failwith $"Expected '%A{expected}' but got '%A{actual}'." + + match List.ofSeq effects with + | ["GetSlice"] -> () + | actual -> failwith $"Expected '\"GetSlice\"' but got '%A{actual}'." + + f () + """ + |> withLangVersion SupportedVersion + |> runFsi + |> shouldSucceed + + [] + let ``justSlice[start..finish]``() = + Fsx """ + open System + + let effects = ResizeArray () + + type T (xs : int array) = + do effects.Add "T" + + member _.Length = xs.Length + + member _.Slice (start, length) = + effects.Add "Slice" + xs.AsSpan().Slice(start, length).ToArray () + + let f () = + let xs = [|1..10|] + let actual = (T xs)[2..3] + let expected = xs[2..3] + + if expected <> actual then + failwith $"Expected '%A{expected}' but got '%A{actual}'." + + match List.ofSeq effects with + | ["T"; "Slice"] -> () + | actual -> failwith $"Expected '[\"T\"; \"Slice\"]' but got '%A{actual}'." + + f () + """ + |> withLangVersion SupportedVersion + |> runFsi + |> shouldSucceed + + [] + let ``justSlice_UnitsOfMeasure[start..finish]``() = + Fsx """ + open System + + type [] m + + let effects = ResizeArray () + + type T (xs : int array) = + do effects.Add "T" + + member _.Length = xs.Length + + member _.Slice (start : int, length : int) = + effects.Add "Slice" + xs.AsSpan().Slice(int start, int length).ToArray () + + let f () = + let xs = [|1..10|] + let actual = (T xs)[2..3] + let expected = xs[2..3] + + if expected <> actual then + failwith $"Expected '%A{expected}' but got '%A{actual}'." + + match List.ofSeq effects with + | ["T"; "Slice"] -> () + | actual -> failwith $"Expected '[\"T\"; \"Slice\"]' but got '%A{actual}'." + + f () + """ + |> withLangVersion SupportedVersion + |> runFsi + |> shouldSucceed + + [] + let ``justGetSlice[start..finish]``() = + Fsx """ + open System + + let effects = ResizeArray () + + type T = + | T + + member _.Length = 99 + + member this.GetSlice (start, finish) = + let start = defaultArg start 0 + let finish = defaultArg finish this.Length + effects.Add "GetSlice" + [start..finish] + + let f () = + let actual = T[2..3] + let expected = [2..3] + + if expected <> actual then + failwith $"Expected '%A{expected}' but got '%A{actual}'." + + match List.ofSeq effects with + | ["GetSlice"] -> () + | actual -> failwith $"Expected '\"GetSlice\"' but got '%A{actual}'." + + f () + """ + |> withLangVersion SupportedVersion + |> runFsi + |> shouldSucceed + + [] + let ``extensionSlice[start..finish]``() = + Fsx """ + open System + + module M = + type T = + { Xs : int array } + + member this.Length = this.Xs.Length + + open M + + module N = + type T with + member this.Slice (start, length) = + { Xs = this.Xs.AsSpan().Slice(start, length).ToArray() } + + open N + + let f () = + let xs = [|1..10|] + let t = { Xs = xs } + let actual = t[2..3] + let expected = xs[2..3] + + if expected <> actual.Xs then + failwith $"Expected '%A{expected}' but got '%A{actual}'." + + f () + """ + |> withLangVersion SupportedVersion + |> runFsi + |> shouldSucceed +#endif