diff --git a/src/fsharp/AttributeChecking.fs b/src/fsharp/AttributeChecking.fs index 1f6f9eac191..6bd4fd0e0cd 100644 --- a/src/fsharp/AttributeChecking.fs +++ b/src/fsharp/AttributeChecking.fs @@ -351,6 +351,11 @@ let CheckFSharpAttributesForHidden g attribs = let CheckFSharpAttributesForObsolete g attribs = not (isNil attribs) && (HasFSharpAttribute g g.attrib_SystemObsolete attribs) + +/// Indicate if a list of F# attributes contains 'RequireArgumentNameAttribute'. Used to error on call site if arguments aren't explicitly named. +let CheckFSharpAttributesForRequireNamedParametersAttribute g attribs = + not (isNil attribs) && (HasFSharpAttribute g g.attrib_RequireArgumentNameAttribute attribs) + /// Indicate if a list of F# attributes contains 'ObsoleteAttribute'. Used to suppress the item in intellisense. /// Also check the attributes for CompilerMessageAttribute, which has an IsHidden argument that allows /// items to be suppressed from intellisense. @@ -390,7 +395,7 @@ let CheckILFieldAttributes g (finfo:ILFieldInfo) m = #endif /// Check the attributes associated with a method, returning warnings and errors as data. -let CheckMethInfoAttributes g m tyargsOpt minfo = +let CheckMethInfoAttributes g m tyargsOpt minfo hasUnammedCallerArgs = let search = BindMethInfoAttributes m minfo (fun ilAttribs -> Some(CheckILAttributes g false ilAttribs m)) @@ -399,6 +404,8 @@ let CheckMethInfoAttributes g m tyargsOpt minfo = CheckFSharpAttributes g fsAttribs m ++ (fun () -> if Option.isNone tyargsOpt && HasFSharpAttribute g g.attrib_RequiresExplicitTypeArgumentsAttribute fsAttribs then ErrorD(Error(FSComp.SR.tcFunctionRequiresExplicitTypeArguments(minfo.LogicalName), m)) + elif Option.isSome hasUnammedCallerArgs && hasUnammedCallerArgs.Value && HasFSharpAttribute g g.attrib_RequireArgumentNameAttribute fsAttribs then + ErrorD(Error(FSComp.SR.tcMethodRequireNamedArgumentAttribute(minfo.LogicalName), m)) else CompleteD) Some res) diff --git a/src/fsharp/AttributeChecking.fsi b/src/fsharp/AttributeChecking.fsi index 1c8bb25e22a..df0299f81f3 100644 --- a/src/fsharp/AttributeChecking.fsi +++ b/src/fsharp/AttributeChecking.fsi @@ -59,7 +59,7 @@ val CheckPropInfoAttributes: pinfo:PropInfo -> m:range -> OperationResult val CheckILFieldAttributes: g:TcGlobals -> finfo:ILFieldInfo -> m:range -> unit -val CheckMethInfoAttributes: g:TcGlobals -> m:range -> tyargsOpt:'a option -> minfo:MethInfo -> OperationResult +val CheckMethInfoAttributes: g:TcGlobals -> m:range -> tyargsOpt:'a option -> minfo:MethInfo -> hasUnammedCallerArgs:bool option -> OperationResult val MethInfoIsUnseen: g:TcGlobals -> m:range -> ty:TType -> minfo:MethInfo -> bool diff --git a/src/fsharp/CheckExpressions.fs b/src/fsharp/CheckExpressions.fs index 31f34b8df12..99c01b55ea5 100644 --- a/src/fsharp/CheckExpressions.fs +++ b/src/fsharp/CheckExpressions.fs @@ -8322,7 +8322,7 @@ and TcEventValueThen cenv overallTy env tpenv mItem mExprAndItem objDetails (ein let delegateType = einfo.GetDelegateType(cenv.amap, mItem) let (SigOfFunctionForDelegate(invokeMethInfo, compiledViewOfDelArgTys, _, _)) = GetSigOfFunctionForDelegate cenv.infoReader delegateType mItem ad let objArgs = Option.toList (Option.map fst objDetails) - MethInfoChecks cenv.g cenv.amap true None objArgs env.eAccessRights mItem invokeMethInfo + MethInfoChecks cenv.g cenv.amap true None objArgs env.eAccessRights mItem invokeMethInfo None // This checks for and drops the 'object' sender let argsTy = ArgsTypOfEventInfo cenv.infoReader mItem ad einfo @@ -8788,7 +8788,13 @@ and TcMethodApplication finalCalledMeth.AssociatedPropertyInfo |> Option.iter (fun pinfo -> CheckPropInfoAttributes pinfo mItem |> CommitOperationResult) let isInstance = not (isNil objArgs) - MethInfoChecks cenv.g cenv.amap isInstance tyargsOpt objArgs ad mItem finalCalledMethInfo + + let hasUnammedCallerArgs = + match unnamedCurriedCallerArgs with + | [] | [[]] -> false + | _ -> true + + MethInfoChecks cenv.g cenv.amap isInstance tyargsOpt objArgs ad mItem finalCalledMethInfo (Some hasUnammedCallerArgs) // Adhoc constraints on use of .NET methods begin @@ -8925,7 +8931,7 @@ and TcSetterArgExpr cenv env denv objExpr ad (AssignedItemSetter(id, setter, Cal let argExprPrebinder, action, defnItem = match setter with | AssignedPropSetter (pinfo, pminfo, pminst) -> - MethInfoChecks cenv.g cenv.amap true None [objExpr] ad m pminfo + MethInfoChecks cenv.g cenv.amap true None [objExpr] ad m pminfo None let calledArgTy = List.head (List.head (pminfo.GetParamTypes(cenv.amap, m, pminst))) let argExprPrebinder, argExpr = MethodCalls.AdjustCallerArgExprForCoercions cenv.g cenv.amap cenv.infoReader ad false calledArgTy ReflectedArgInfo.None callerArgTy m argExpr let mut = (if isStructTy cenv.g (tyOfExpr cenv.g objExpr) then DefinitelyMutates else PossiblyMutates) @@ -9040,7 +9046,7 @@ and TcNewDelegateThen cenv overallTy env tpenv mDelTy mExprAndArg delegateTy arg UnifyTypes cenv env mExprAndArg overallTy delegateTy let (SigOfFunctionForDelegate(invokeMethInfo, delArgTys, _, fty)) = GetSigOfFunctionForDelegate cenv.infoReader delegateTy mDelTy ad // We pass isInstance = true here because we're checking the rights to access the "Invoke" method - MethInfoChecks cenv.g cenv.amap true None [] env.eAccessRights mExprAndArg invokeMethInfo + MethInfoChecks cenv.g cenv.amap true None [] env.eAccessRights mExprAndArg invokeMethInfo None let args = GetMethodArgs arg match args with | [farg], [] -> diff --git a/src/fsharp/ConstraintSolver.fs b/src/fsharp/ConstraintSolver.fs index 9f42a53607a..56d865af37a 100644 --- a/src/fsharp/ConstraintSolver.fs +++ b/src/fsharp/ConstraintSolver.fs @@ -1592,7 +1592,7 @@ and SolveMemberConstraint (csenv: ConstraintSolverEnv) ignoreUnresolvedOverload else ErrorD(ConstraintSolverError(FSComp.SR.csMethodFoundButIsStatic((NicePrint.minimalStringOfType denv minfo.ApparentEnclosingType), (DecompileOpName nm), nm), m, m2 )) else - do! CheckMethInfoAttributes g m None minfo + do! CheckMethInfoAttributes g m None minfo None return TTraitSolved (minfo, calledMeth.CalledTyArgs) | _ -> diff --git a/src/fsharp/FSComp.txt b/src/fsharp/FSComp.txt index 5229999efc5..601d943a304 100644 --- a/src/fsharp/FSComp.txt +++ b/src/fsharp/FSComp.txt @@ -1556,4 +1556,5 @@ forFormatInvalidForInterpolated4,"Interpolated strings used as type IFormattable 3390,xmlDocMissingParameter,"This XML comment is incomplete: no documentation for parameter '%s'" 3391,tcLiteralAttributeCannotUseActivePattern,"A [] declaration cannot use an active pattern for its identifier" 3392,containerDeprecated,"The 'AssemblyKeyNameAttribute' has been deprecated. Use 'AssemblyKeyFileAttribute' instead." -3393,containerSigningUnsupportedOnThisPlatform,"Key container signing is not supported on this platform." \ No newline at end of file +3393,containerSigningUnsupportedOnThisPlatform,"Key container signing is not supported on this platform." +3394,tcMethodRequireNamedArgumentAttribute,"The method '%s' has the 'RequireNamedArgumentAttribute' attribute specified, use the named arguments syntax (e.g. 'MethodName(x = value)')." diff --git a/src/fsharp/FSharp.Core/prim-types.fs b/src/fsharp/FSharp.Core/prim-types.fs index 26a5400717f..d4e2f1e2f4c 100644 --- a/src/fsharp/FSharp.Core/prim-types.fs +++ b/src/fsharp/FSharp.Core/prim-types.fs @@ -346,7 +346,12 @@ namespace Microsoft.FSharp.Core [] type RequireQualifiedAccessAttribute() = inherit System.Attribute() - + + [] + [] + type RequireNamedArgumentAttribute() = + inherit System.Attribute() + [] [] type AutoOpenAttribute(path:string) = diff --git a/src/fsharp/FSharp.Core/prim-types.fsi b/src/fsharp/FSharp.Core/prim-types.fsi index 064b621e56a..9fd4192864c 100644 --- a/src/fsharp/FSharp.Core/prim-types.fsi +++ b/src/fsharp/FSharp.Core/prim-types.fsi @@ -886,6 +886,19 @@ namespace Microsoft.FSharp.Core /// RequireQualifiedAccessAttribute new : unit -> RequireQualifiedAccessAttribute + /// This attribute is used to mandate that call site to a method bearing the attribute + /// use named arguments argument syntax. + /// + /// Attributes + [] + [] + type RequireNamedArgumentAttribute = + inherit Attribute + + /// Creates an instance of the attribute + /// RequireNamedArgumentAttribute + new : unit -> RequireNamedArgumentAttribute + /// Indicates a construct is automatically opened when brought into scope through /// an assembly reference or then opening of the containing namespace or module. /// diff --git a/src/fsharp/InfoReader.fs b/src/fsharp/InfoReader.fs index 844b3a0c003..e2c86efd14c 100644 --- a/src/fsharp/InfoReader.fs +++ b/src/fsharp/InfoReader.fs @@ -875,7 +875,7 @@ let GetSigOfFunctionForDelegate (infoReader: InfoReader) delty m ad = | [] -> [g.unit_ty] | _ -> compiledViewOfDelArgTys let delRetTy = invokeMethInfo.GetFSharpReturnTy(amap, m, minst) - CheckMethInfoAttributes g m None invokeMethInfo |> CommitOperationResult + CheckMethInfoAttributes g m None invokeMethInfo None |> CommitOperationResult let fty = mkIteratedFunTy fsharpViewOfDelArgTys delRetTy SigOfFunctionForDelegate(invokeMethInfo, compiledViewOfDelArgTys, delRetTy, fty) diff --git a/src/fsharp/MethodCalls.fs b/src/fsharp/MethodCalls.fs index 94fbd7ff60f..fcf8a8642aa 100644 --- a/src/fsharp/MethodCalls.fs +++ b/src/fsharp/MethodCalls.fs @@ -1766,7 +1766,7 @@ let ILFieldInstanceChecks g amap ad m (finfo : ILFieldInfo) = CheckILFieldInfoAccessible g amap m ad finfo CheckILFieldAttributes g finfo m -let MethInfoChecks g amap isInstance tyargsOpt objArgs ad m (minfo: MethInfo) = +let MethInfoChecks g amap isInstance tyargsOpt objArgs ad m (minfo: MethInfo) (hasUnammedCallerArgs: bool option) = if minfo.IsInstance <> isInstance then if isInstance then error (Error (FSComp.SR.csMethodIsNotAnInstanceMethod(minfo.LogicalName), m)) @@ -1798,7 +1798,7 @@ let MethInfoChecks g amap isInstance tyargsOpt objArgs ad m (minfo: MethInfo) = (minfo.LogicalName.StartsWithOrdinal("get_Item") || minfo.LogicalName.StartsWithOrdinal("get_Rest")) then warning (Error (FSComp.SR.tcTupleMemberNotNormallyUsed(), m)) - CheckMethInfoAttributes g m tyargsOpt minfo |> CommitOperationResult + CheckMethInfoAttributes g m tyargsOpt minfo hasUnammedCallerArgs |> CommitOperationResult exception FieldNotMutable of DisplayEnv * RecdFieldRef * range diff --git a/src/fsharp/MethodCalls.fsi b/src/fsharp/MethodCalls.fsi index ac96b7b30d8..f6286e52b57 100644 --- a/src/fsharp/MethodCalls.fsi +++ b/src/fsharp/MethodCalls.fsi @@ -289,7 +289,7 @@ val ILFieldStaticChecks: g:TcGlobals -> amap:ImportMap -> infoReader:InfoReader val ILFieldInstanceChecks: g:TcGlobals -> amap:ImportMap -> ad:AccessorDomain -> m:range -> finfo:ILFieldInfo -> unit -val MethInfoChecks: g:TcGlobals -> amap:ImportMap -> isInstance:bool -> tyargsOpt:'a option -> objArgs:Expr list -> ad:AccessorDomain -> m:range -> minfo:MethInfo -> unit +val MethInfoChecks: g:TcGlobals -> amap:ImportMap -> isInstance:bool -> tyargsOpt:'a option -> objArgs:Expr list -> ad:AccessorDomain -> m:range -> minfo:MethInfo -> hasUnammedCallerArgs:bool option -> unit exception FieldNotMutable of TypedTreeOps.DisplayEnv * RecdFieldRef * range diff --git a/src/fsharp/TcGlobals.fs b/src/fsharp/TcGlobals.fs index 9446966eff5..c6f7011740d 100755 --- a/src/fsharp/TcGlobals.fs +++ b/src/fsharp/TcGlobals.fs @@ -1199,6 +1199,7 @@ type public TcGlobals(compilingFslib: bool, ilg:ILGlobals, fslibCcu: CcuThunk, d member val attrib_AutoSerializableAttribute = mk_MFCore_attrib "AutoSerializableAttribute" member val attrib_RequireQualifiedAccessAttribute = mk_MFCore_attrib "RequireQualifiedAccessAttribute" + member val attrib_RequireArgumentNameAttribute = mk_MFCore_attrib "RequireArgumentNameAttribute" member val attrib_EntryPointAttribute = mk_MFCore_attrib "EntryPointAttribute" member val attrib_DefaultAugmentationAttribute = mk_MFCore_attrib "DefaultAugmentationAttribute" member val attrib_CompilerMessageAttribute = mk_MFCore_attrib "CompilerMessageAttribute" diff --git a/tests/FSharp.Core.UnitTests/SurfaceArea.fs b/tests/FSharp.Core.UnitTests/SurfaceArea.fs index 11fb8cec14e..e6003ffe448 100644 --- a/tests/FSharp.Core.UnitTests/SurfaceArea.fs +++ b/tests/FSharp.Core.UnitTests/SurfaceArea.fs @@ -2274,6 +2274,7 @@ Microsoft.FSharp.Core.ReflectedDefinitionAttribute: Boolean IncludeValue Microsoft.FSharp.Core.ReflectedDefinitionAttribute: Boolean get_IncludeValue() Microsoft.FSharp.Core.ReflectedDefinitionAttribute: Void .ctor() Microsoft.FSharp.Core.ReflectedDefinitionAttribute: Void .ctor(Boolean) +Microsoft.FSharp.Core.RequireNamedArgumentAttribute: Void .ctor() Microsoft.FSharp.Core.RequireQualifiedAccessAttribute: Void .ctor() Microsoft.FSharp.Core.RequiresExplicitTypeArgumentsAttribute: Void .ctor() Microsoft.FSharp.Core.ResultModule: Microsoft.FSharp.Core.FSharpResult`2[T,TResult] MapError[TError,TResult,T](Microsoft.FSharp.Core.FSharpFunc`2[TError,TResult], Microsoft.FSharp.Core.FSharpResult`2[T,TError]) diff --git a/tests/fsharp/tests.fs b/tests/fsharp/tests.fs index 253703752a7..7487836b371 100644 --- a/tests/fsharp/tests.fs +++ b/tests/fsharp/tests.fs @@ -3063,3 +3063,8 @@ module OverloadResolution = let [] ``neg_generic_known_argument_types`` () = singleNegTest (testConfig "typecheck/overloads") "neg_generic_known_argument_types" let [] ``neg_tupled_arguments`` () = singleNegTest (testConfig "typecheck/overloads") "neg_tupled_arguments" #endif + +module RequireNamedArgument = + [] + let ``type check neg_requirenamedargument same file`` () = + singleNegTest (testConfig "typecheck/requirenamedargument") "test1" diff --git a/tests/fsharp/typecheck/requirenamedargument/test1.bsl b/tests/fsharp/typecheck/requirenamedargument/test1.bsl new file mode 100644 index 00000000000..95a4ec13798 --- /dev/null +++ b/tests/fsharp/typecheck/requirenamedargument/test1.bsl @@ -0,0 +1,16 @@ + +test1.fsx(41,1,41,4): typecheck error FS3394: The method 'B' has the 'RequireNamedArgumentAttribute' attribute specified, use the named arguments syntax (e.g. 'MethodName(x = value)'). + +test1.fsx(43,1,43,4): typecheck error FS3394: The method 'C' has the 'RequireNamedArgumentAttribute' attribute specified, use the named arguments syntax (e.g. 'MethodName(x = value)'). + +test1.fsx(50,1,50,4): typecheck error FS3394: The method 'I' has the 'RequireNamedArgumentAttribute' attribute specified, use the named arguments syntax (e.g. 'MethodName(x = value)'). + +test1.fsx(54,1,54,5): typecheck error FS3394: The method 'I' has the 'RequireNamedArgumentAttribute' attribute specified, use the named arguments syntax (e.g. 'MethodName(x = value)'). + +test1.fsx(57,1,57,23): typecheck error FS3394: The method 'DefaultMethod' has the 'RequireNamedArgumentAttribute' attribute specified, use the named arguments syntax (e.g. 'MethodName(x = value)'). + +test1.fsx(58,1,58,16): typecheck error FS3394: The method 'Method' has the 'RequireNamedArgumentAttribute' attribute specified, use the named arguments syntax (e.g. 'MethodName(x = value)'). + +test1.fsx(60,1,60,24): typecheck error FS3394: The method 'DefaultMethod' has the 'RequireNamedArgumentAttribute' attribute specified, use the named arguments syntax (e.g. 'MethodName(x = value)'). + +test1.fsx(61,1,61,17): typecheck error FS3394: The method 'Method' has the 'RequireNamedArgumentAttribute' attribute specified, use the named arguments syntax (e.g. 'MethodName(x = value)'). diff --git a/tests/fsharp/typecheck/requirenamedargument/test1.fsx b/tests/fsharp/typecheck/requirenamedargument/test1.fsx new file mode 100644 index 00000000000..653466279fe --- /dev/null +++ b/tests/fsharp/typecheck/requirenamedargument/test1.fsx @@ -0,0 +1,61 @@ +open System.Runtime.InteropServices + + +type I = + interface + [] + abstract member I: i:int -> unit + end + +[] +type Abstract() = + [] + abstract Method: i:int -> unit + [] + abstract DefaultMethod: i:int -> unit + default this.DefaultMethod(i) = printfn $"{i}" + +type Concrete() = + inherit Abstract() + interface I with + member x.I(i:int) = printfn $"{i}" + override x.Method(_) = printfn "method!" + override x.DefaultMethod(a) = printfn "default method!"; base.DefaultMethod(a) + +type A() = + + [] + member x.B(c:int, d:string) = () + + [] + static member C(d:byte) = () + + [] + static member C() = () + +type B() = + inherit A() + member x.Opt([]i: int, j: int) = () +let a = A() + +a.B(1, "") + +A.C(1uy) + +A.C() + +let b = B() +b.Opt(j=2) +let i = Concrete() :> I +i.I(1) + +let ii = { new I with member x.I(b:int) = printfn $"{b}"} + +ii.I(2) + +let concrete = Concrete() +concrete.DefaultMethod(1) +concrete.Method(1) +let baseClass = concrete :> Abstract +baseClass.DefaultMethod(1) +baseClass.Method(1) \ No newline at end of file