diff --git a/docs/release-notes/.FSharp.Compiler.Service/9.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/9.0.100.md index b98195c81d7..ea87f571750 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/9.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/9.0.100.md @@ -38,6 +38,7 @@ * Enable consuming generic arguments defined as `allows ref struct` in C# ([Issue #17597](https://github.com/dotnet/fsharp/issues/17597) * Trivia for SynTypeConstraint.WhereTyparNotSupportsNull. ([Issue #17721](https://github.com/dotnet/fsharp/issues/17721), [PR #17745](https://github.com/dotnet/fsharp/pull/17745)) * Trivia for SynType.WithNull. ([Issue #17720](https://github.com/dotnet/fsharp/issues/17720), [PR #17745](https://github.com/dotnet/fsharp/pull/17745)) +* Support `CallerArgumentExpression` ([Language Suggestion #966](https://github.com/fsharp/fslang-suggestions/issues/966), [PR #17519](https://github.com/dotnet/fsharp/pull/17519)) ### Changed diff --git a/docs/release-notes/.Language/preview.md b/docs/release-notes/.Language/preview.md index b18d08e30c3..94bc4eb8442 100644 --- a/docs/release-notes/.Language/preview.md +++ b/docs/release-notes/.Language/preview.md @@ -1,6 +1,7 @@ ### Added * Better generic unmanaged structs handling. ([Language suggestion #692](https://github.com/fsharp/fslang-suggestions/issues/692), [PR #12154](https://github.com/dotnet/fsharp/pull/12154)) +* Support `CallerArgumentExpression` ([Language Suggestion #966](https://github.com/fsharp/fslang-suggestions/issues/966), [PR #17519](https://github.com/dotnet/fsharp/pull/17519)) ### Fixed diff --git a/src/Compiler/Checking/MethodCalls.fs b/src/Compiler/Checking/MethodCalls.fs index 72363943549..13abea72e31 100644 --- a/src/Compiler/Checking/MethodCalls.fs +++ b/src/Compiler/Checking/MethodCalls.fs @@ -1418,7 +1418,7 @@ let emptyPreBinder (e: Expr) = e /// Get the expression that must be inserted on the caller side for a CallerSide optional arg, /// i.e. one where there is no corresponding caller arg. -let rec GetDefaultExpressionForCallerSideOptionalArg tcFieldInit g (calledArg: CalledArg) currCalledArgTy currDfltVal eCallerMemberName mMethExpr = +let rec GetDefaultExpressionForCallerSideOptionalArg tcFieldInit g (calledArg: CalledArg) currCalledArgTy currDfltVal eCallerMemberName mMethExpr unnamedArgs = match currDfltVal with | MissingValue -> // Add an I_nop if this is an initonly field to make sure we never recognize it as an lvalue. See mkExprAddrOfExpr. @@ -1435,7 +1435,7 @@ let rec GetDefaultExpressionForCallerSideOptionalArg tcFieldInit g (calledArg: C let ctorArgs = [Expr.Const (tcFieldInit mMethExpr fieldInit, mMethExpr, inst)] emptyPreBinder, Expr.Op (TOp.ILCall (false, false, true, true, NormalValUse, false, false, ctor, [inst], [], [currCalledArgTy]), [], ctorArgs, mMethExpr) | ByrefTy g inst -> - GetDefaultExpressionForCallerSideOptionalArg tcFieldInit g calledArg inst (PassByRef(inst, currDfltVal)) eCallerMemberName mMethExpr + GetDefaultExpressionForCallerSideOptionalArg tcFieldInit g calledArg inst (PassByRef(inst, currDfltVal)) eCallerMemberName mMethExpr unnamedArgs | _ -> match calledArg.CallerInfo, eCallerMemberName with | CallerLineNumber, _ when typeEquiv g currCalledArgTy g.int_ty -> @@ -1445,6 +1445,20 @@ let rec GetDefaultExpressionForCallerSideOptionalArg tcFieldInit g (calledArg: C emptyPreBinder, Expr.Const (Const.String fileName, mMethExpr, currCalledArgTy) | CallerMemberName, Some callerName when (typeEquiv g currCalledArgTy g.string_ty) -> emptyPreBinder, Expr.Const (Const.String callerName, mMethExpr, currCalledArgTy) + + | CallerArgumentExpression param, _ when g.langVersion.SupportsFeature LanguageFeature.SupportCallerArgumentExpression && typeEquiv g currCalledArgTy g.string_ty -> + let str = + unnamedArgs + |> List.tryPick (fun { CalledArg=called; CallerArg=caller } -> + match called.NameOpt with + | Some x when x.idText = param -> + let code = FileContent.getCodeText caller.Range + if System.String.IsNullOrEmpty code then None + else Some (Const.String code) + | _ -> None) + |> Option.defaultWith (fun _ -> tcFieldInit mMethExpr fieldInit) + emptyPreBinder, Expr.Const (str, mMethExpr, currCalledArgTy) + | _ -> emptyPreBinder, Expr.Const (tcFieldInit mMethExpr fieldInit, mMethExpr, currCalledArgTy) @@ -1468,13 +1482,13 @@ let rec GetDefaultExpressionForCallerSideOptionalArg tcFieldInit g (calledArg: C | PassByRef (ty, dfltVal2) -> let v, _ = mkCompGenLocal mMethExpr "defaultByrefArg" ty - let wrapper2, rhs = GetDefaultExpressionForCallerSideOptionalArg tcFieldInit g calledArg currCalledArgTy dfltVal2 eCallerMemberName mMethExpr + let wrapper2, rhs = GetDefaultExpressionForCallerSideOptionalArg tcFieldInit g calledArg currCalledArgTy dfltVal2 eCallerMemberName mMethExpr unnamedArgs (wrapper2 >> mkCompGenLet mMethExpr v rhs), mkValAddr mMethExpr false (mkLocalValRef v) /// Get the expression that must be inserted on the caller side for a CalleeSide optional arg where /// no caller argument has been provided. Normally this is 'None', however CallerMemberName and friends /// can be used with 'CalleeSide' optional arguments -let GetDefaultExpressionForCalleeSideOptionalArg g (calledArg: CalledArg) eCallerMemberName (mMethExpr: range) = +let GetDefaultExpressionForCalleeSideOptionalArg g (calledArg: CalledArg) eCallerMemberName (mMethExpr: range) unnamedArgs = let calledArgTy = calledArg.CalledArgumentType let calledNonOptTy = if isOptionTy g calledArgTy then @@ -1493,12 +1507,28 @@ let GetDefaultExpressionForCalleeSideOptionalArg g (calledArg: CalledArg) eCalle | CallerMemberName, Some(callerName) when typeEquiv g calledNonOptTy g.string_ty -> let memberNameExpr = Expr.Const (Const.String callerName, mMethExpr, calledNonOptTy) mkSome g calledNonOptTy memberNameExpr mMethExpr + + | CallerArgumentExpression param, _ when g.langVersion.SupportsFeature LanguageFeature.SupportCallerArgumentExpression && typeEquiv g calledNonOptTy g.string_ty -> + let exprOpt = + unnamedArgs + |> List.tryPick (fun { CalledArg=called; CallerArg=caller } -> + match called.NameOpt with + | Some x when x.idText = param -> + let code = FileContent.getCodeText caller.Range + if System.String.IsNullOrEmpty code then None + else Some (Expr.Const(Const.String code, mMethExpr, calledNonOptTy)) + | _ -> None) + + match exprOpt with + | Some expr -> mkSome g calledNonOptTy expr mMethExpr + | None -> mkNone g calledNonOptTy mMethExpr + | _ -> mkNone g calledNonOptTy mMethExpr /// Get the expression that must be inserted on the caller side for an optional arg where /// no caller argument has been provided. -let GetDefaultExpressionForOptionalArg tcFieldInit g (calledArg: CalledArg) eCallerMemberName mItem (mMethExpr: range) = +let GetDefaultExpressionForOptionalArg tcFieldInit g (calledArg: CalledArg) eCallerMemberName mItem (mMethExpr: range) unnamedArgs = let calledArgTy = calledArg.CalledArgumentType let preBinder, expr = match calledArg.OptArgInfo with @@ -1506,10 +1536,10 @@ let GetDefaultExpressionForOptionalArg tcFieldInit g (calledArg: CalledArg) eCal error(InternalError("Unexpected NotOptional", mItem)) | CallerSide dfltVal -> - GetDefaultExpressionForCallerSideOptionalArg tcFieldInit g calledArg calledArgTy dfltVal eCallerMemberName mMethExpr + GetDefaultExpressionForCallerSideOptionalArg tcFieldInit g calledArg calledArgTy dfltVal eCallerMemberName mMethExpr unnamedArgs | CalleeSide -> - emptyPreBinder, GetDefaultExpressionForCalleeSideOptionalArg g calledArg eCallerMemberName mMethExpr + emptyPreBinder, GetDefaultExpressionForCalleeSideOptionalArg g calledArg eCallerMemberName mMethExpr unnamedArgs // Combine the variable allocators (if any) let callerArg = CallerArg(calledArgTy, mMethExpr, false, expr) @@ -1563,7 +1593,7 @@ let AdjustCallerArgForOptional tcVal tcFieldInit eCallerMemberName (infoReader: mkOptionToNullable g m (destOptionTy g callerArgTy) callerArgExpr else // CSharpMethod(?x=b) when 'b' has optional type and 'x' has non-nullable type --> CSharpMethod(x=Option.defaultValue DEFAULT v) - let _wrapper, defaultExpr = GetDefaultExpressionForCallerSideOptionalArg tcFieldInit g calledArg calledArgTy dfltVal eCallerMemberName m + let _wrapper, defaultExpr = GetDefaultExpressionForCallerSideOptionalArg tcFieldInit g calledArg calledArgTy dfltVal eCallerMemberName m [assignedArg] let ty = destOptionTy g callerArgTy mkOptionDefaultValue g m ty defaultExpr callerArgExpr else @@ -1623,7 +1653,7 @@ let AdjustCallerArgsForOptionals tcVal tcFieldInit eCallerMemberName (infoReader // i.e. there is no corresponding caller arg. let optArgs, optArgPreBinder = (emptyPreBinder, calledMeth.UnnamedCalledOptArgs) ||> List.mapFold (fun preBinder calledArg -> - let preBinder2, arg = GetDefaultExpressionForOptionalArg tcFieldInit g calledArg eCallerMemberName mItem mMethExpr + let preBinder2, arg = GetDefaultExpressionForOptionalArg tcFieldInit g calledArg eCallerMemberName mItem mMethExpr unnamedArgs arg, (preBinder >> preBinder2)) let adjustedNormalUnnamedArgs = List.map (AdjustCallerArgForOptional tcVal tcFieldInit eCallerMemberName infoReader ad) unnamedArgs diff --git a/src/Compiler/Checking/PostInferenceChecks.fs b/src/Compiler/Checking/PostInferenceChecks.fs index 06ef1b9766a..2c84cf799bc 100644 --- a/src/Compiler/Checking/PostInferenceChecks.fs +++ b/src/Compiler/Checking/PostInferenceChecks.fs @@ -2389,15 +2389,30 @@ let CheckEntityDefn cenv env (tycon: Entity) = if numCurriedArgSets > 1 && others |> List.exists (fun minfo2 -> not (IsAbstractDefaultPair2 minfo minfo2)) then errorR(Error(FSComp.SR.chkDuplicateMethodCurried(nm, NicePrint.minimalStringOfType cenv.denv ty), m)) + let paramDatas = minfo.GetParamDatas(cenv.amap, m, minfo.FormalMethodInst) + if numCurriedArgSets > 1 && - (minfo.GetParamDatas(cenv.amap, m, minfo.FormalMethodInst) + (paramDatas |> List.existsSquared (fun (ParamData(isParamArrayArg, _isInArg, isOutArg, optArgInfo, callerInfo, _, reflArgInfo, ty)) -> isParamArrayArg || isOutArg || reflArgInfo.AutoQuote || optArgInfo.IsOptional || callerInfo <> NoCallerInfo || isByrefLikeTy g m ty)) then errorR(Error(FSComp.SR.chkCurriedMethodsCantHaveOutParams(), m)) if numCurriedArgSets = 1 then - minfo.GetParamDatas(cenv.amap, m, minfo.FormalMethodInst) - |> List.iterSquared (fun (ParamData(_, isInArg, _, optArgInfo, callerInfo, _, _, ty)) -> + let paramNames = + paramDatas + |> List.concat + |> List.choose (fun (ParamData(_, _, _, _, _, nameOpt, _, _)) -> nameOpt) + + let checkCallerArgumentExpression name (nameOpt: Ident option) = + match nameOpt with + | Some ident when name = ident.idText -> + warning(Error(FSComp.SR.tcCallerArgumentExpressionSelfReferential(name), m)) + | _ when paramNames |> List.forall (fun i -> name <> i.idText) -> + warning(Error(FSComp.SR.tcCallerArgumentExpressionHasInvalidParameterName(name), m)) + | _ -> () + + paramDatas + |> List.iterSquared (fun (ParamData(_, isInArg, _, optArgInfo, callerInfo, nameOpt, _, ty)) -> ignore isInArg match (optArgInfo, callerInfo) with | _, NoCallerInfo -> () @@ -2419,7 +2434,20 @@ let CheckEntityDefn cenv env (tycon: Entity) = errorR(Error(FSComp.SR.tcCallerInfoWrongType(callerInfo |> string, "string", NicePrint.minimalStringOfType cenv.denv ty), m)) | CalleeSide, CallerMemberName -> if not ((isOptionTy g ty) && (typeEquiv g g.string_ty (destOptionTy g ty))) then - errorR(Error(FSComp.SR.tcCallerInfoWrongType(callerInfo |> string, "string", NicePrint.minimalStringOfType cenv.denv (destOptionTy g ty)), m))) + errorR(Error(FSComp.SR.tcCallerInfoWrongType(callerInfo |> string, "string", NicePrint.minimalStringOfType cenv.denv (destOptionTy g ty)), m)) + + | CallerSide _, CallerArgumentExpression name -> + if not (typeEquiv g g.string_ty ty) then + errorR(Error(FSComp.SR.tcCallerInfoWrongType(callerInfo |> string, "string", NicePrint.minimalStringOfType cenv.denv ty), m)) + + checkCallerArgumentExpression name nameOpt + + | CalleeSide, CallerArgumentExpression name -> + if not ((isOptionTy g ty) && (typeEquiv g g.string_ty (destOptionTy g ty))) then + errorR(Error(FSComp.SR.tcCallerInfoWrongType(callerInfo |> string, "string", NicePrint.minimalStringOfType cenv.denv (destOptionTy g ty)), m)) + + checkCallerArgumentExpression name nameOpt + ) for pinfo in immediateProps do let nm = pinfo.PropertyName diff --git a/src/Compiler/Checking/infos.fs b/src/Compiler/Checking/infos.fs index 23afa7bece5..d5096bf212d 100644 --- a/src/Compiler/Checking/infos.fs +++ b/src/Compiler/Checking/infos.fs @@ -240,6 +240,7 @@ type CallerInfo = | CallerLineNumber | CallerMemberName | CallerFilePath + | CallerArgumentExpression of paramName: string override x.ToString() = sprintf "%+A" x @@ -317,20 +318,23 @@ let CrackParamAttribsInfo g (ty: TType, argInfo: ArgReprInfo) = let isCallerLineNumberArg = HasFSharpAttribute g g.attrib_CallerLineNumberAttribute argInfo.Attribs let isCallerFilePathArg = HasFSharpAttribute g g.attrib_CallerFilePathAttribute argInfo.Attribs let isCallerMemberNameArg = HasFSharpAttribute g g.attrib_CallerMemberNameAttribute argInfo.Attribs + let callerArgumentExpressionArg = TryFindFSharpAttributeOpt g g.attrib_CallerArgumentExpressionAttribute argInfo.Attribs let callerInfo = - match isCallerLineNumberArg, isCallerFilePathArg, isCallerMemberNameArg with - | false, false, false -> NoCallerInfo - | true, false, false -> CallerLineNumber - | false, true, false -> CallerFilePath - | false, false, true -> CallerMemberName - | false, true, true -> + match isCallerLineNumberArg, isCallerFilePathArg, isCallerMemberNameArg, callerArgumentExpressionArg with + | false, false, false, None -> NoCallerInfo + | true, false, false, None -> CallerLineNumber + | false, true, false, None -> CallerFilePath + | false, false, true, None -> CallerMemberName + | false, false, false, Some(Attrib(_, _, (AttribStringArg x :: _), _, _, _, _)) -> + CallerArgumentExpression(x) + | false, true, true, _ -> match TryFindFSharpAttribute g g.attrib_CallerMemberNameAttribute argInfo.Attribs with | Some(Attrib(_, _, _, _, _, _, callerMemberNameAttributeRange)) -> warning(Error(FSComp.SR.CallerMemberNameIsOverridden(argInfo.Name.Value.idText), callerMemberNameAttributeRange)) CallerFilePath | _ -> failwith "Impossible" - | _, _, _ -> + | _, _, _, _ -> // if multiple caller info attributes are specified, pick the "wrong" one here // so that we get an error later match tryDestOptionTy g ty with @@ -1278,14 +1282,22 @@ type MethInfo = let isCallerLineNumberArg = TryFindILAttribute g.attrib_CallerLineNumberAttribute attrs let isCallerFilePathArg = TryFindILAttribute g.attrib_CallerFilePathAttribute attrs let isCallerMemberNameArg = TryFindILAttribute g.attrib_CallerMemberNameAttribute attrs + let isCallerArgumentExpressionArg = TryFindILAttributeOpt g.attrib_CallerArgumentExpressionAttribute attrs let callerInfo = - match isCallerLineNumberArg, isCallerFilePathArg, isCallerMemberNameArg with - | false, false, false -> NoCallerInfo - | true, false, false -> CallerLineNumber - | false, true, false -> CallerFilePath - | false, false, true -> CallerMemberName - | _, _, _ -> + match isCallerLineNumberArg, isCallerFilePathArg, isCallerMemberNameArg, isCallerArgumentExpressionArg with + | false, false, false, false -> NoCallerInfo + | true, false, false, false -> CallerLineNumber + | false, true, false, false -> CallerFilePath + | false, false, true, false -> CallerMemberName + | false, false, false, true -> + match g.attrib_CallerArgumentExpressionAttribute with + | Some (AttribInfo(tref,_)) -> + match TryDecodeILAttribute tref attrs with + | Some ([ILAttribElem.String (Some name) ], _) -> CallerArgumentExpression(name) + | _ -> NoCallerInfo + | None -> NoCallerInfo + | _, _, _, _ -> // if multiple caller info attributes are specified, pick the "wrong" one here // so that we get an error later if p.Type.TypeRef.FullName = "System.Int32" then CallerFilePath diff --git a/src/Compiler/Checking/infos.fsi b/src/Compiler/Checking/infos.fsi index 9ab99a8346b..3f98f320043 100644 --- a/src/Compiler/Checking/infos.fsi +++ b/src/Compiler/Checking/infos.fsi @@ -101,6 +101,7 @@ type CallerInfo = | CallerLineNumber | CallerMemberName | CallerFilePath + | CallerArgumentExpression of paramName: string [] type ReflectedArgInfo = diff --git a/src/Compiler/Driver/ScriptClosure.fs b/src/Compiler/Driver/ScriptClosure.fs index 0e22231abb8..de1104ea944 100644 --- a/src/Compiler/Driver/ScriptClosure.fs +++ b/src/Compiler/Driver/ScriptClosure.fs @@ -159,7 +159,9 @@ module ScriptPreprocessClosure = reduceMemoryUsage ) = + FileContent.readFileContents [ fileName ] let projectDir = !! Path.GetDirectoryName(fileName) + let isInteractive = (codeContext = CodeContext.CompilationAndEvaluation) let isInvalidationSupported = (codeContext = CodeContext.Editing) diff --git a/src/Compiler/Driver/fsc.fs b/src/Compiler/Driver/fsc.fs index ac4ee179538..6510a9f1130 100644 --- a/src/Compiler/Driver/fsc.fs +++ b/src/Compiler/Driver/fsc.fs @@ -622,6 +622,8 @@ let main1 // Register framework tcImports to be disposed in future disposables.Register frameworkTcImports + FileContent.readFileContents sourceFiles + // Parse sourceFiles ReportTime tcConfig "Parse inputs" use unwindParsePhase = UseBuildPhase BuildPhase.Parse diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index b5a50afc7c0..27619bf2550 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1783,4 +1783,7 @@ featureEmptyBodiedComputationExpressions,"Support for computation expressions wi featureAllowAccessModifiersToAutoPropertiesGettersAndSetters,"Allow access modifiers to auto properties getters and setters" 3871,tcAccessModifiersNotAllowedInSRTPConstraint,"Access modifiers cannot be applied to an SRTP constraint." featureAllowObjectExpressionWithoutOverrides,"Allow object expressions without overrides" -3872,tcPartialActivePattern,"Multi-case partial active patterns are not supported. Consider using a single-case partial active pattern or a full active pattern." \ No newline at end of file +3872,tcPartialActivePattern,"Multi-case partial active patterns are not supported. Consider using a single-case partial active pattern or a full active pattern." +featureSupportCallerArgumentExpression,"Support `CallerArgumentExpression`" +3873,tcCallerArgumentExpressionSelfReferential,"This CallerArgumentExpression with argument '%s' will have no effect because it's self-referential." +3873,tcCallerArgumentExpressionHasInvalidParameterName,"This CallerArgumentExpression with argument '%s' will have no effect because it's applied with an invalid parameter name." diff --git a/src/Compiler/Facilities/LanguageFeatures.fs b/src/Compiler/Facilities/LanguageFeatures.fs index 5c311237594..0717d494cfd 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fs +++ b/src/Compiler/Facilities/LanguageFeatures.fs @@ -94,6 +94,7 @@ type LanguageFeature = | ParsedHashDirectiveArgumentNonQuotes | EmptyBodiedComputationExpressions | AllowObjectExpressionWithoutOverrides + | SupportCallerArgumentExpression /// LanguageVersion management type LanguageVersion(versionText) = @@ -219,6 +220,7 @@ type LanguageVersion(versionText) = LanguageFeature.FromEndSlicing, previewVersion // Unfinished features --- needs work LanguageFeature.AllowAccessModifiersToAutoPropertiesGettersAndSetters, previewVersion LanguageFeature.AllowObjectExpressionWithoutOverrides, previewVersion + LanguageFeature.SupportCallerArgumentExpression, previewVersion ] static let defaultLanguageVersion = LanguageVersion("default") @@ -375,6 +377,7 @@ type LanguageVersion(versionText) = | LanguageFeature.ParsedHashDirectiveArgumentNonQuotes -> FSComp.SR.featureParsedHashDirectiveArgumentNonString () | LanguageFeature.EmptyBodiedComputationExpressions -> FSComp.SR.featureEmptyBodiedComputationExpressions () | LanguageFeature.AllowObjectExpressionWithoutOverrides -> FSComp.SR.featureAllowObjectExpressionWithoutOverrides () + | LanguageFeature.SupportCallerArgumentExpression -> FSComp.SR.featureSupportCallerArgumentExpression () /// Get a version string associated with the given feature. static member GetFeatureVersionString feature = diff --git a/src/Compiler/Facilities/LanguageFeatures.fsi b/src/Compiler/Facilities/LanguageFeatures.fsi index 7408300b943..1060bb106bc 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fsi +++ b/src/Compiler/Facilities/LanguageFeatures.fsi @@ -85,6 +85,7 @@ type LanguageFeature = | ParsedHashDirectiveArgumentNonQuotes | EmptyBodiedComputationExpressions | AllowObjectExpressionWithoutOverrides + | SupportCallerArgumentExpression /// LanguageVersion management type LanguageVersion = diff --git a/src/Compiler/Facilities/prim-lexing.fs b/src/Compiler/Facilities/prim-lexing.fs index d1b965f100f..55b72c7490f 100644 --- a/src/Compiler/Facilities/prim-lexing.fs +++ b/src/Compiler/Facilities/prim-lexing.fs @@ -210,15 +210,17 @@ open System.Collections.Generic [] type internal Position = val FileIndex: int + val OriginalFileIndex: int val Line: int val OriginalLine: int val AbsoluteOffset: int val StartOfLineAbsoluteOffset: int member x.Column = x.AbsoluteOffset - x.StartOfLineAbsoluteOffset - new(fileIndex: int, line: int, originalLine: int, startOfLineAbsoluteOffset: int, absoluteOffset: int) = + new(fileIndex: int, originalFileIndex: int, line: int, originalLine: int, startOfLineAbsoluteOffset: int, absoluteOffset: int) = { FileIndex = fileIndex + OriginalFileIndex = originalFileIndex Line = line OriginalLine = originalLine AbsoluteOffset = absoluteOffset @@ -226,25 +228,25 @@ type internal Position = } member x.NextLine = - Position(x.FileIndex, x.Line + 1, x.OriginalLine + 1, x.AbsoluteOffset, x.AbsoluteOffset) + Position(x.FileIndex, x.OriginalFileIndex, x.Line + 1, x.OriginalLine + 1, x.AbsoluteOffset, x.AbsoluteOffset) member x.EndOfToken n = - Position(x.FileIndex, x.Line, x.OriginalLine, x.StartOfLineAbsoluteOffset, x.AbsoluteOffset + n) + Position(x.FileIndex, x.OriginalFileIndex, x.Line, x.OriginalLine, x.StartOfLineAbsoluteOffset, x.AbsoluteOffset + n) member x.ShiftColumnBy by = - Position(x.FileIndex, x.Line, x.OriginalLine, x.StartOfLineAbsoluteOffset, x.AbsoluteOffset + by) + Position(x.FileIndex, x.OriginalFileIndex, x.Line, x.OriginalLine, x.StartOfLineAbsoluteOffset, x.AbsoluteOffset + by) member x.ColumnMinusOne = - Position(x.FileIndex, x.Line, x.OriginalLine, x.StartOfLineAbsoluteOffset, x.StartOfLineAbsoluteOffset - 1) + Position(x.FileIndex, x.OriginalFileIndex, x.Line, x.OriginalLine, x.StartOfLineAbsoluteOffset, x.StartOfLineAbsoluteOffset - 1) member x.ApplyLineDirective(fileIdx, line) = - Position(fileIdx, line, x.OriginalLine, x.AbsoluteOffset, x.AbsoluteOffset) + Position(fileIdx, x.OriginalFileIndex, line, x.OriginalLine + 1, x.AbsoluteOffset, x.AbsoluteOffset) override p.ToString() = $"({p.Line},{p.Column})" static member Empty = Position() - static member FirstLine fileIdx = Position(fileIdx, 1, 0, 0, 0) + static member FirstLine fileIdx = Position(fileIdx, fileIdx, 1, 1, 0, 0) type internal LexBufferFiller<'Char> = LexBuffer<'Char> -> unit diff --git a/src/Compiler/Facilities/prim-lexing.fsi b/src/Compiler/Facilities/prim-lexing.fsi index ff13f96c9e1..b58c3e26ef7 100644 --- a/src/Compiler/Facilities/prim-lexing.fsi +++ b/src/Compiler/Facilities/prim-lexing.fsi @@ -72,6 +72,8 @@ type internal Position = /// The file index for the file associated with the input stream, use fileOfFileIndex to decode val FileIndex: int + val OriginalFileIndex: int + /// The line number in the input stream, assuming fresh positions have been updated /// for the new line by modifying the EndPos property of the LexBuffer. val Line: int diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs index 10991bb59f7..e19da4601d3 100644 --- a/src/Compiler/Interactive/fsi.fs +++ b/src/Compiler/Interactive/fsi.fs @@ -831,6 +831,15 @@ type internal FsiStdinSyphon(errorWriter: TextWriter) = let lines = text.Split '\n' if 0 < i && i <= lines.Length then lines[i - 1] else "" + /// Gets the indicated line in the syphon text + member _.GetLineNoPrune fileName i = + if fileName <> stdinMockFileName then + "" + else + let text = syphonText.ToString() + let lines = text.Split '\n' + if 0 < i && i <= lines.Length then lines[i - 1] else "" + /// Display the given error. member syphon.PrintDiagnostic(tcConfig: TcConfig, diagnostic: PhasedDiagnostic) = ignoreAllErrors (fun () -> @@ -4659,6 +4668,15 @@ type FsiEvaluationSession if List.isEmpty fsiOptions.SourceFiles then fsiConsolePrompt.PrintAhead() + do + FileContent.getLineDynamic <- + { new FileContent.IFileContentGetLine with + member this.GetLine (fileName: string) (line: int) : string = + fsiStdinSyphon.GetLineNoPrune fileName line + + member this.GetLineNewLineMark _ : string = "\n" + } + let fsiConsoleInput = FsiConsoleInput(fsi, fsiOptions, inReader, outWriter) /// The single, global interactive checker that can be safely used in conjunction with other operations diff --git a/src/Compiler/Service/IncrementalBuild.fs b/src/Compiler/Service/IncrementalBuild.fs index b7560b222c2..7f70b3c0653 100644 --- a/src/Compiler/Service/IncrementalBuild.fs +++ b/src/Compiler/Service/IncrementalBuild.fs @@ -1496,6 +1496,8 @@ type IncrementalBuilder(initialState: IncrementalBuilderInitialState, state: Inc tcConfigB, sourceFilesNew + FileContent.readFileContents sourceFiles + // If this is a builder for a script, re-apply the settings inferred from the // script and its load closure to the configuration. // diff --git a/src/Compiler/Service/TransparentCompiler.fs b/src/Compiler/Service/TransparentCompiler.fs index e3acd1d4c6c..8e509c47b58 100644 --- a/src/Compiler/Service/TransparentCompiler.fs +++ b/src/Compiler/Service/TransparentCompiler.fs @@ -822,6 +822,8 @@ type internal TransparentCompiler tcConfigB.parallelReferenceResolution <- parallelReferenceResolution tcConfigB.captureIdentifiersWhenParsing <- captureIdentifiersWhenParsing + FileContent.readFileContents sourceFilesNew + return tcConfigB, sourceFilesNew, loadClosureOpt } diff --git a/src/Compiler/Service/service.fs b/src/Compiler/Service/service.fs index 2c915870f84..c033308c4ae 100644 --- a/src/Compiler/Service/service.fs +++ b/src/Compiler/Service/service.fs @@ -645,6 +645,9 @@ type FSharpChecker // Apply command-line arguments and collect more source files if they are in the arguments let sourceFilesNew = ApplyCommandLineArgs(tcConfigB, sourceFiles, argv) + + FileContent.readFileContents sourceFilesNew + FSharpParsingOptions.FromTcConfigBuilder(tcConfigB, Array.ofList sourceFilesNew, isInteractive), errorScope.Diagnostics member ic.GetParsingOptionsFromCommandLineArgs(argv, ?isInteractive: bool, ?isEditing) = diff --git a/src/Compiler/SyntaxTree/ParseHelpers.fs b/src/Compiler/SyntaxTree/ParseHelpers.fs index 22c27eeb9b0..9d4546d9193 100644 --- a/src/Compiler/SyntaxTree/ParseHelpers.fs +++ b/src/Compiler/SyntaxTree/ParseHelpers.fs @@ -40,14 +40,27 @@ let warningStringOfPos (p: pos) = warningStringOfCoords p.Line p.Column /// Get an F# compiler position from a lexer position let posOfLexPosition (p: Position) = mkPos p.Line p.Column +let posOfLexOriginalPosition (p: Position) = mkPos p.OriginalLine p.Column /// Get an F# compiler range from a lexer range let mkSynRange (p1: Position) (p2: Position) = - if p1.FileIndex = p2.FileIndex then - mkFileIndexRange p1.FileIndex (posOfLexPosition p1) (posOfLexPosition p2) + let p2' = + if p1.FileIndex = p2.FileIndex then p2 + else + // This means we had a #line directive in the middle of this syntax element. + (p1.ShiftColumnBy 1) + + // TODO need tests + if p1.OriginalFileIndex <> p1.FileIndex || p1.OriginalLine <> p1.Line then + mkFileIndexRangeWithOriginRange + p1.FileIndex + (posOfLexPosition p1) + (posOfLexPosition p2') + p1.OriginalFileIndex + (posOfLexOriginalPosition p1) + (posOfLexOriginalPosition p2) else - // This means we had a #line directive in the middle of this syntax element. - mkFileIndexRange p1.FileIndex (posOfLexPosition p1) (posOfLexPosition (p1.ShiftColumnBy 1)) + mkFileIndexRange p1.FileIndex (posOfLexPosition p1) (posOfLexPosition p2') type LexBuffer<'Char> with diff --git a/src/Compiler/TypedTree/TcGlobals.fs b/src/Compiler/TypedTree/TcGlobals.fs index 2c065437f2b..d8b5fc52832 100644 --- a/src/Compiler/TypedTree/TcGlobals.fs +++ b/src/Compiler/TypedTree/TcGlobals.fs @@ -1078,7 +1078,6 @@ type TcGlobals( // Adding an unnecessary "let" instead of inlining into a multi-line pipelined compute-once "member val" that is too complex for @dsyme let v_attribs_Unsupported = [ tryFindSysAttrib "System.Runtime.CompilerServices.ModuleInitializerAttribute" - tryFindSysAttrib "System.Runtime.CompilerServices.CallerArgumentExpressionAttribute" tryFindSysAttrib "System.Runtime.InteropServices.UnmanagedCallersOnlyAttribute" tryFindSysAttrib "System.Runtime.CompilerServices.CompilerFeatureRequiredAttribute" tryFindSysAttrib "System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute" @@ -1488,6 +1487,7 @@ type TcGlobals( member val attrib_ExtensionAttribute = findSysAttrib "System.Runtime.CompilerServices.ExtensionAttribute" member val attrib_CallerLineNumberAttribute = findSysAttrib "System.Runtime.CompilerServices.CallerLineNumberAttribute" member val attrib_CallerFilePathAttribute = findSysAttrib "System.Runtime.CompilerServices.CallerFilePathAttribute" + member val attrib_CallerArgumentExpressionAttribute = tryFindSysAttrib "System.Runtime.CompilerServices.CallerArgumentExpressionAttribute" member val attrib_CallerMemberNameAttribute = findSysAttrib "System.Runtime.CompilerServices.CallerMemberNameAttribute" member val attrib_SkipLocalsInitAttribute = findSysAttrib "System.Runtime.CompilerServices.SkipLocalsInitAttribute" member val attribs_Unsupported = v_attribs_Unsupported diff --git a/src/Compiler/TypedTree/TcGlobals.fsi b/src/Compiler/TypedTree/TcGlobals.fsi index 950d5217500..e1001ebdfc1 100644 --- a/src/Compiler/TypedTree/TcGlobals.fsi +++ b/src/Compiler/TypedTree/TcGlobals.fsi @@ -326,6 +326,8 @@ type internal TcGlobals = member attrib_CallerLineNumberAttribute: BuiltinAttribInfo + member attrib_CallerArgumentExpressionAttribute: BuiltinAttribInfo option + member attrib_CallerMemberNameAttribute: BuiltinAttribInfo member attrib_ClassAttribute: BuiltinAttribInfo diff --git a/src/Compiler/Utilities/range.fs b/src/Compiler/Utilities/range.fs index 6d0002a97ba..f09de2fb8c2 100755 --- a/src/Compiler/Utilities/range.fs +++ b/src/Compiler/Utilities/range.fs @@ -260,8 +260,8 @@ module FileIndex = [] [ {DebugCode}")>] -type Range(code1: int64, code2: int64) = - static member Zero = range (0L, 0L) +type _RangeBackground(code1: int64, code2: int64) = + static member Zero = _rangeBackground (0L, 0L) new(fIdx, bl, bc, el, ec) = let code1 = @@ -273,9 +273,9 @@ type Range(code1: int64, code2: int64) = ((int64 bl <<< startLineShift) &&& startLineMask) ||| ((int64 (el - bl) <<< heightShift) &&& heightMask) - range (code1, code2) + _rangeBackground (code1, code2) - new(fIdx, b: pos, e: pos) = range (fIdx, b.Line, b.Column, e.Line, e.Column) + new(fIdx, b: pos, e: pos) = _rangeBackground (fIdx, b.Line, b.Column, e.Line, e.Column) member _.StartLine = int32 (uint64 (code2 &&& startLineMask) >>> startLineShift) @@ -307,18 +307,18 @@ type Range(code1: int64, code2: int64) = member _.FileIndex = int32 (code1 &&& fileIndexMask) - member m.StartRange = range (m.FileIndex, m.Start, m.Start) + member m.StartRange = _rangeBackground (m.FileIndex, m.Start, m.Start) - member m.EndRange = range (m.FileIndex, m.End, m.End) + member m.EndRange = _rangeBackground (m.FileIndex, m.End, m.End) member m.FileName = fileOfFileIndex m.FileIndex member m.ShortFileName = Path.GetFileName(fileOfFileIndex m.FileIndex) member _.MakeSynthetic() = - range (code1, code2 ||| isSyntheticMask) + _rangeBackground (code1, code2 ||| isSyntheticMask) - member m.IsAdjacentTo(otherRange: Range) = + member m.IsAdjacentTo(otherRange: _RangeBackground) = m.FileIndex = otherRange.FileIndex && m.End.Encoding = otherRange.Start.Encoding member _.NoteSourceConstruct(kind) = @@ -335,7 +335,7 @@ type Range(code1: int64, code2: int64) = | NotedSourceConstruct.Combine -> 8 | NotedSourceConstruct.DelayOrQuoteOrRun -> 9 - range (code1, (code2 &&& ~~~debugPointKindMask) ||| (int64 code <<< debugPointKindShift)) + _rangeBackground (code1, (code2 &&& ~~~debugPointKindMask) ||| (int64 code <<< debugPointKindShift)) member _.Code1 = code1 @@ -369,14 +369,14 @@ type Range(code1: int64, code2: int64) = with e -> e.ToString() - member _.Equals(m2: range) = + member _.Equals(m2: _rangeBackground) = let code2 = code2 &&& ~~~(debugPointKindMask ||| isSyntheticMask) let rcode2 = m2.Code2 &&& ~~~(debugPointKindMask ||| isSyntheticMask) code1 = m2.Code1 && code2 = rcode2 override m.Equals(obj) = match obj with - | :? range as m2 -> m.Equals(m2) + | :? _rangeBackground as m2 -> m.Equals(m2) | _ -> false override _.GetHashCode() = @@ -386,6 +386,120 @@ type Range(code1: int64, code2: int64) = override r.ToString() = sprintf "(%d,%d--%d,%d)" r.StartLine r.StartColumn r.EndLine r.EndColumn + member m.IsZero = m.Equals _rangeBackground.Zero + +and _rangeBackground = _RangeBackground + +[] +[ ({StartLine},{StartColumn}-{EndLine},{EndColumn}) {ShortFileName} -> {DebugCode}")>] +type Range(range1: _rangeBackground, range2: _rangeBackground) = + static member Zero = range (_rangeBackground.Zero, _rangeBackground.Zero) + + new(fIdx, bl, bc, el, ec) = range (_rangeBackground (fIdx, bl, bc, el, ec), _RangeBackground.Zero) + + new(fIdx, bl, bc, el, ec, fIdx2, bl2, bc2, el2, ec2) = + range (_rangeBackground (fIdx, bl, bc, el, ec), _rangeBackground (fIdx2, bl2, bc2, el2, ec2)) + + new(fIdx, b: pos, e: pos) = range (_rangeBackground (fIdx, b.Line, b.Column, e.Line, e.Column), _rangeBackground.Zero) + + new(fIdx, b: pos, e: pos, fIdx2, b2: pos, e2: pos) = + range ( + _rangeBackground (fIdx, b.Line, b.Column, e.Line, e.Column), + _rangeBackground (fIdx2, b2.Line, b2.Column, e2.Line, e2.Column) + ) + + member _.StartLine = range1.StartLine + + member _.StartColumn = range1.StartColumn + + member _.EndLine = range1.EndLine + + member _.EndColumn = range1.EndColumn + + member _.IsSynthetic = range1.IsSynthetic + + member _.NotedSourceConstruct = range1.NotedSourceConstruct + + member _.Start = range1.Start + + member _.End = range1.End + + member _.FileIndex = range1.FileIndex + + member _.StartRange = Range(range1.StartRange, range2.StartRange) + + member _.EndRange = Range(range1.EndRange, range2.EndRange) + + member _.FileName = range1.FileName + + member _.ShortFileName = range1.ShortFileName + + member _.MakeSynthetic() = + range (range1.MakeSynthetic(), range2.MakeSynthetic()) + + member _.IsAdjacentTo(otherRange: Range) = range1.IsAdjacentTo otherRange.Range1 + + member _.NoteSourceConstruct(kind) = + range (range1.NoteSourceConstruct kind, range2.NoteSourceConstruct kind) + + member _.Code1 = range1.Code1 + + member _.Code2 = range1.Code2 + + member _.Range1 = range1 + + member _.Range2 = range2 + + member _.DebugCode = range1.DebugCode + + member _.Equals(m2: range) = + range1.Equals m2.Range1 && range2.Equals m2.Range2 + + override m.Equals(obj) = + match obj with + | :? range as m2 -> m.Equals(m2) + | _ -> false + + override _.GetHashCode() = + range1.GetHashCode() + range2.GetHashCode() + + override _.ToString() = + let fromText = + if range2.IsZero then + String.Empty + else + $"(from: {range2.ToString()})" + + $"{range1} {fromText}" + + member _.HasOriginalRange = not range2.IsZero + + member _.OriginalStartLine = range2.StartLine + + member _.OriginalStartColumn = range2.StartColumn + + member _.OriginalEndLine = range2.EndLine + + member _.OriginalEndColumn = range2.EndColumn + + member _.OriginalIsSynthetic = range2.IsSynthetic + + member _.OriginalNotedSourceConstruct = range2.NotedSourceConstruct + + member _.OriginalStart = range2.Start + + member _.OriginalEnd = range2.End + + member _.OriginalFileIndex = range2.FileIndex + + member _.OriginalStartRange = Range(range2.StartRange, range2.StartRange) + + member _.OriginalEndRange = Range(range2.EndRange, range2.EndRange) + + member _.OriginalFileName = range2.FileName + + member _.OriginalShortFileName = range2.ShortFileName + and range = Range #if CHECK_LINE0_TYPES // turn on to check that we correctly transform zero-based line counts to one-based line counts @@ -444,6 +558,9 @@ module Range = let mkFileIndexRange fileIndex startPos endPos = range (fileIndex, startPos, endPos) + let mkFileIndexRangeWithOriginRange fileIndex startPos endPos fileIndex2 startPos2 endPos2 = + range (fileIndex, startPos, endPos, fileIndex2, startPos2, endPos2) + let posOrder = Order.orderOn (fun (p: pos) -> p.Line, p.Column) (Pair.order (Int32.order, Int32.order)) @@ -484,7 +601,21 @@ module Range = m2 let m = - range (m1.FileIndex, start.StartLine, start.StartColumn, finish.EndLine, finish.EndColumn) + if m1.OriginalFileIndex = m2.OriginalFileIndex then + range ( + m1.FileIndex, + start.StartLine, + start.StartColumn, + finish.EndLine, + finish.EndColumn, + m1.OriginalFileIndex, + start.OriginalStartLine, + start.OriginalStartColumn, + finish.OriginalEndLine, + finish.OriginalEndColumn + ) + else + range (m1.FileIndex, start.StartLine, start.StartColumn, finish.EndLine, finish.EndColumn) if m1.IsSynthetic || m2.IsSynthetic then m.MakeSynthetic() @@ -566,3 +697,61 @@ module Range = | None -> mkRange file (mkPos 1 0) (mkPos 1 80) with _ -> mkRange file (mkPos 1 0) (mkPos 1 80) + +module internal FileContent = + let private fileContentDict = ConcurrentDictionary() + let private newlineMarkDict = ConcurrentDictionary() + + let readFileContents (fileNames: string list) = + for fileName in fileNames do + if FileSystem.FileExistsShim fileName then + use fileStream = FileSystem.OpenFileForReadShim(fileName) + let text = fileStream.ReadAllText() + + let newlineMark = + if text.IndexOf('\n') > -1 && text.IndexOf('\r') = -1 then + "\n" + else + "\r\n" + + newlineMarkDict[fileName] <- newlineMark + fileContentDict[fileName] <- text.Split([| newlineMark |], StringSplitOptions.None) + + type IFileContentGetLine = + abstract GetLine: fileName: string -> line: int -> string + abstract GetLineNewLineMark: fileName: string -> string + + let mutable getLineDynamic = + { new IFileContentGetLine with + member this.GetLine (fileName: string) (line: int) : string = + match fileContentDict.TryGetValue fileName with + | true, lines when lines.Length > line -> lines[line - 1] + | _ -> String.Empty + + member this.GetLineNewLineMark(fileName: string) : string = + match newlineMarkDict.TryGetValue fileName with + | true, res -> res + | _ -> String.Empty + } + + let getCodeText (m: range) = + let endCol = m.EndColumn - 1 + let startCol = m.StartColumn - 1 + + let s = + let filename, startLine, endLine = + if m.HasOriginalRange then + m.OriginalFileName, m.OriginalStartLine, m.OriginalEndLine + else + m.FileName, m.StartLine, m.EndLine + + [| for i in startLine..endLine -> getLineDynamic.GetLine filename i |] + |> String.concat (getLineDynamic.GetLineNewLineMark filename) + + if String.IsNullOrEmpty s then + s + else + try + s.Substring(startCol + 1, s.LastIndexOf("\n", StringComparison.Ordinal) + 1 - startCol + endCol) + with ex -> + raise(System.AggregateException($"ex: {ex}; (s: {s}, startCol: {startCol}, endCol: {endCol})", ex)) diff --git a/src/Compiler/Utilities/range.fsi b/src/Compiler/Utilities/range.fsi index 40a52efa879..a3935af8b0f 100755 --- a/src/Compiler/Utilities/range.fsi +++ b/src/Compiler/Utilities/range.fsi @@ -107,6 +107,44 @@ type Range = /// service operations like dot-completion. member IsSynthetic: bool + member HasOriginalRange: bool + + /// The start line of the range + member OriginalStartLine: int + + /// The start column of the range + member OriginalStartColumn: int + + /// The line number for the end position of the range + member OriginalEndLine: int + + /// The column number for the end position of the range + member OriginalEndColumn: int + + /// The start position of the range + member OriginalStart: pos + + /// The end position of the range + member OriginalEnd: pos + + /// The empty range that is located at the start position of the range + member OriginalStartRange: range + + /// The empty range that is located at the end position of the range + member OriginalEndRange: range + + /// The file index for the range + member internal OriginalFileIndex: int + + /// The file name for the file of the range + member OriginalFileName: string + + /// Synthetic marks ranges which are produced by intermediate compilation phases. This + /// bit signifies that the range covers something that should not be visible to language + /// service operations like dot-completion. + member OriginalIsSynthetic: bool + + /// Convert a range to be synthetic member internal MakeSynthetic: unit -> range @@ -191,6 +229,8 @@ module Range = /// This view of range marks uses file indexes explicitly val mkFileIndexRange: FileIndex -> pos -> pos -> range + val mkFileIndexRangeWithOriginRange: FileIndex -> pos -> pos -> FileIndex -> pos -> pos -> range + /// This view hides the use of file indexes and just uses filenames val mkRange: string -> pos -> pos -> range @@ -269,4 +309,20 @@ module Line = /// Convert a line number from one-based line counting (used internally in the F# compiler and in F# error messages) to zero-based line counting (used by Visual Studio) val toZ: int -> Line0 +/// Store code file content. Use to implement `CallerArgumentExpression` +module internal FileContent = + + /// Read all file contents + val readFileContents: fileNames: string list -> unit + + type IFileContentGetLine = + abstract GetLine: fileName: string -> line: int -> string + abstract GetLineNewLineMark: fileName: string -> string + + /// Get a line string from already read files. + /// + /// Used by `getCodeText` to support `CallerArgumentExpression` in F# Interactive + val mutable getLineDynamic: IFileContentGetLine + /// Get code text of the specific `range` + val getCodeText: range -> string diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index 2b1a9483def..5c40a37ed66 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -597,6 +597,11 @@ reprezentace struktury aktivních vzorů + + Support `CallerArgumentExpression` + Support `CallerArgumentExpression` + + Union case test properties Vlastnosti testu případu sjednocení @@ -1312,6 +1317,16 @@ Atributy nejde použít pro rozšíření typů. + + This CallerArgumentExpression with argument '{0}' will have no effect because it's applied with an invalid parameter name. + This CallerArgumentExpression with argument '{0}' will have no effect because it's applied with an invalid parameter name. + + + + This CallerArgumentExpression with argument '{0}' will have no effect because it's self-referential. + This CallerArgumentExpression with argument '{0}' will have no effect because it's self-referential. + + This copy-and-update record expression changes all fields of record type '{0}'. Consider using the record construction syntax instead. Tento výraz záznamu kopírování a aktualizace mění všechna pole typu záznamu '{0}'. Zvažte použití syntaxe konstrukce záznamu. diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index a1b2532e4db..89e88a19a64 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -597,6 +597,11 @@ Strukturdarstellung für aktive Muster + + Support `CallerArgumentExpression` + Support `CallerArgumentExpression` + + Union case test properties Eigenschaften von Union-Falltests @@ -1312,6 +1317,16 @@ Attribute können nicht auf Typerweiterungen angewendet werden. + + This CallerArgumentExpression with argument '{0}' will have no effect because it's applied with an invalid parameter name. + This CallerArgumentExpression with argument '{0}' will have no effect because it's applied with an invalid parameter name. + + + + This CallerArgumentExpression with argument '{0}' will have no effect because it's self-referential. + This CallerArgumentExpression with argument '{0}' will have no effect because it's self-referential. + + This copy-and-update record expression changes all fields of record type '{0}'. Consider using the record construction syntax instead. Dieser Ausdruck zum Kopieren und Aktualisieren von Datensätzen ändert alle Felder des Datensatztyps "{0}". Erwägen Sie stattdessen die Verwendung der Datensatzerstellungssyntax. diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index dd9cbb7fec6..57adf9a2353 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -597,6 +597,11 @@ representación de struct para modelos activos + + Support `CallerArgumentExpression` + Support `CallerArgumentExpression` + + Union case test properties Propiedades de prueba de caso de unión @@ -1312,6 +1317,16 @@ Los atributos no se pueden aplicar a las extensiones de tipo. + + This CallerArgumentExpression with argument '{0}' will have no effect because it's applied with an invalid parameter name. + This CallerArgumentExpression with argument '{0}' will have no effect because it's applied with an invalid parameter name. + + + + This CallerArgumentExpression with argument '{0}' will have no effect because it's self-referential. + This CallerArgumentExpression with argument '{0}' will have no effect because it's self-referential. + + This copy-and-update record expression changes all fields of record type '{0}'. Consider using the record construction syntax instead. Esta expresión de copia y actualización de registros cambia todos los campos de tipo de registro "{0}". Es preferible utilizar la sintaxis de construcción de registros. diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index 2cc92e5f4d5..0076ba54bdc 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -597,6 +597,11 @@ représentation de structure pour les modèles actifs + + Support `CallerArgumentExpression` + Support `CallerArgumentExpression` + + Union case test properties Propriétés du test de cas d’union @@ -1312,6 +1317,16 @@ Impossible d'appliquer des attributs aux extensions de type. + + This CallerArgumentExpression with argument '{0}' will have no effect because it's applied with an invalid parameter name. + This CallerArgumentExpression with argument '{0}' will have no effect because it's applied with an invalid parameter name. + + + + This CallerArgumentExpression with argument '{0}' will have no effect because it's self-referential. + This CallerArgumentExpression with argument '{0}' will have no effect because it's self-referential. + + This copy-and-update record expression changes all fields of record type '{0}'. Consider using the record construction syntax instead. Cette expression d'enregistrement de copie et de mise à jour modifie tous les champs du type d'enregistrement '{0}'. Envisagez d'utiliser la syntaxe de construction d'enregistrement à la place. diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index 5b5793c4841..c91f80a2e13 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -597,6 +597,11 @@ rappresentazione struct per criteri attivi + + Support `CallerArgumentExpression` + Support `CallerArgumentExpression` + + Union case test properties Proprietà test case di unione @@ -1312,6 +1317,16 @@ Gli attributi non possono essere applicati a estensioni di tipo. + + This CallerArgumentExpression with argument '{0}' will have no effect because it's applied with an invalid parameter name. + This CallerArgumentExpression with argument '{0}' will have no effect because it's applied with an invalid parameter name. + + + + This CallerArgumentExpression with argument '{0}' will have no effect because it's self-referential. + This CallerArgumentExpression with argument '{0}' will have no effect because it's self-referential. + + This copy-and-update record expression changes all fields of record type '{0}'. Consider using the record construction syntax instead. Questa espressione di record di copia e aggiornamento modifica tutti i campi del tipo di record '{0}'. Provare a usare la sintassi di costruzione dei record. diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index d30efc74724..7ed18ee0c62 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -597,6 +597,11 @@ アクティブなパターンの構造体表現 + + Support `CallerArgumentExpression` + Support `CallerArgumentExpression` + + Union case test properties ユニオン ケースのテスト プロパティ @@ -1312,6 +1317,16 @@ 属性を型拡張に適用することはできません。 + + This CallerArgumentExpression with argument '{0}' will have no effect because it's applied with an invalid parameter name. + This CallerArgumentExpression with argument '{0}' will have no effect because it's applied with an invalid parameter name. + + + + This CallerArgumentExpression with argument '{0}' will have no effect because it's self-referential. + This CallerArgumentExpression with argument '{0}' will have no effect because it's self-referential. + + This copy-and-update record expression changes all fields of record type '{0}'. Consider using the record construction syntax instead. この copy-and-update レコード式は、レコードの種類が '{0}' であるすべてのフィールドを変更します。代わりにレコード構築構文を使用することを検討してください。 diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index c9359c56807..774c5133d85 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -597,6 +597,11 @@ 활성 패턴에 대한 구조체 표현 + + Support `CallerArgumentExpression` + Support `CallerArgumentExpression` + + Union case test properties 공용 구조체 사례 테스트 속성 @@ -1312,6 +1317,16 @@ 형식 확장에 특성을 적용할 수 없습니다. + + This CallerArgumentExpression with argument '{0}' will have no effect because it's applied with an invalid parameter name. + This CallerArgumentExpression with argument '{0}' will have no effect because it's applied with an invalid parameter name. + + + + This CallerArgumentExpression with argument '{0}' will have no effect because it's self-referential. + This CallerArgumentExpression with argument '{0}' will have no effect because it's self-referential. + + This copy-and-update record expression changes all fields of record type '{0}'. Consider using the record construction syntax instead. 이 레코드 복사 및 업데이트 식은 '{0}' 레코드 형식의 모든 필드를 변경합니다. 레코드 생성 구문을 대신 사용하는 것이 좋습니다. diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index 933379b9f7b..d742c071e8a 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -597,6 +597,11 @@ reprezentacja struktury aktywnych wzorców + + Support `CallerArgumentExpression` + Support `CallerArgumentExpression` + + Union case test properties Właściwości testowe przypadku unii @@ -1312,6 +1317,16 @@ Atrybutów nie można stosować do rozszerzeń typu. + + This CallerArgumentExpression with argument '{0}' will have no effect because it's applied with an invalid parameter name. + This CallerArgumentExpression with argument '{0}' will have no effect because it's applied with an invalid parameter name. + + + + This CallerArgumentExpression with argument '{0}' will have no effect because it's self-referential. + This CallerArgumentExpression with argument '{0}' will have no effect because it's self-referential. + + This copy-and-update record expression changes all fields of record type '{0}'. Consider using the record construction syntax instead. To wyrażenie rekordu kopiowania i aktualizacji zmienia wszystkie pola typu rekordu „{0}”. Zamiast tego rozważ użycie składni konstrukcji rekordu. diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index e6480e13025..15880adfdbc 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -597,6 +597,11 @@ representação estrutural para padrões ativos + + Support `CallerArgumentExpression` + Support `CallerArgumentExpression` + + Union case test properties Propriedades de teste de caso de união @@ -1312,6 +1317,16 @@ Os atributos não podem ser aplicados às extensões de tipo. + + This CallerArgumentExpression with argument '{0}' will have no effect because it's applied with an invalid parameter name. + This CallerArgumentExpression with argument '{0}' will have no effect because it's applied with an invalid parameter name. + + + + This CallerArgumentExpression with argument '{0}' will have no effect because it's self-referential. + This CallerArgumentExpression with argument '{0}' will have no effect because it's self-referential. + + This copy-and-update record expression changes all fields of record type '{0}'. Consider using the record construction syntax instead. Essa expressão de registro copiar e atualizar altera todos os campos do tipo de registro '{0}'. Considere usar a sintaxe de construção de registro em vez disso. diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index 61c03885917..5066b1c5495 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -597,6 +597,11 @@ представление структуры для активных шаблонов + + Support `CallerArgumentExpression` + Support `CallerArgumentExpression` + + Union case test properties Свойства теста союзного случая @@ -1312,6 +1317,16 @@ Атрибуты не могут быть применены к расширениям типа. + + This CallerArgumentExpression with argument '{0}' will have no effect because it's applied with an invalid parameter name. + This CallerArgumentExpression with argument '{0}' will have no effect because it's applied with an invalid parameter name. + + + + This CallerArgumentExpression with argument '{0}' will have no effect because it's self-referential. + This CallerArgumentExpression with argument '{0}' will have no effect because it's self-referential. + + This copy-and-update record expression changes all fields of record type '{0}'. Consider using the record construction syntax instead. Это выражение записи копирования и обновления изменяет все поля типа записи "{0}". Вместо этого можно использовать синтаксис конструкции записи. diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index 09c113ee3f1..f03691f8a6e 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -597,6 +597,11 @@ etkin desenler için yapı gösterimi + + Support `CallerArgumentExpression` + Support `CallerArgumentExpression` + + Union case test properties Birleşim durumu test özellikleri @@ -1312,6 +1317,16 @@ Öznitelikler tür uzantılarına uygulanamaz. + + This CallerArgumentExpression with argument '{0}' will have no effect because it's applied with an invalid parameter name. + This CallerArgumentExpression with argument '{0}' will have no effect because it's applied with an invalid parameter name. + + + + This CallerArgumentExpression with argument '{0}' will have no effect because it's self-referential. + This CallerArgumentExpression with argument '{0}' will have no effect because it's self-referential. + + This copy-and-update record expression changes all fields of record type '{0}'. Consider using the record construction syntax instead. Bu kopyalama ve güncelleştirme kayıt ifadesi, '{0}' kayıt türündeki tüm alanları değiştirir. Bunun yerine kayıt oluşturma söz dizimini kullanmayı deneyin. diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index d9573b5bed2..86ba9a97716 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -597,6 +597,11 @@ 活动模式的结构表示形式 + + Support `CallerArgumentExpression` + Support `CallerArgumentExpression` + + Union case test properties 联合用例测试属性 @@ -1312,6 +1317,16 @@ 属性不可应用于类型扩展。 + + This CallerArgumentExpression with argument '{0}' will have no effect because it's applied with an invalid parameter name. + This CallerArgumentExpression with argument '{0}' will have no effect because it's applied with an invalid parameter name. + + + + This CallerArgumentExpression with argument '{0}' will have no effect because it's self-referential. + This CallerArgumentExpression with argument '{0}' will have no effect because it's self-referential. + + This copy-and-update record expression changes all fields of record type '{0}'. Consider using the record construction syntax instead. 此复制和更新记录表达式更改记录类型“{0}”的所有字段。请考虑改用记录构造语法。 diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index 1a6c3de6ccf..540d8c88413 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -597,6 +597,11 @@ 現用模式的結構表示法 + + Support `CallerArgumentExpression` + Support `CallerArgumentExpression` + + Union case test properties 聯集案例測試屬性 @@ -1312,6 +1317,16 @@ 屬性無法套用到類型延伸模組。 + + This CallerArgumentExpression with argument '{0}' will have no effect because it's applied with an invalid parameter name. + This CallerArgumentExpression with argument '{0}' will have no effect because it's applied with an invalid parameter name. + + + + This CallerArgumentExpression with argument '{0}' will have no effect because it's self-referential. + This CallerArgumentExpression with argument '{0}' will have no effect because it's self-referential. + + This copy-and-update record expression changes all fields of record type '{0}'. Consider using the record construction syntax instead. 此複製和更新記錄運算式將變更記錄類型為 '{0}' 的所有欄位。請考慮改用記錄建構語法。 diff --git a/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/CallerArgumentExpression.fs b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/CallerArgumentExpression.fs new file mode 100644 index 00000000000..5f342427532 --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/Conformance/BasicGrammarElements/CustomAttributes/CallerArgumentExpression.fs @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Conformance.BasicGrammarElements + +open Xunit +open FSharp.Test.Compiler + +module CustomAttributes_CallerArgumentExpression = + + [] + let ``Can consume CallerArgumentExpression in BCL methods`` () = + FSharp """module Program +try System.ArgumentNullException.ThrowIfNullOrWhiteSpace(Seq.init 50 (fun _ -> " ") + (* comment *) + |> String.concat " ") +with :? System.ArgumentException as ex -> + assert (ex.Message.Contains("(Parameter 'Seq.init 50 (fun _ -> \" \")\n (* comment *) \n |> String.concat \" \"")) +""" + |> withLangVersionPreview + |> compileAndRun + |> shouldSucceed + |> ignore + + [] + let ``Can define in F#`` () = + FSharp """#if !NETCOREAPP3_0_OR_GREATER +namespace System.Runtime.CompilerServices +open System +[] +type CallerArgumentExpressionAttribute(parameterName) = + inherit Attribute() + member val ParameterName: string = parameterName + +namespace global +#endif + +module Program = + open System.Runtime.CompilerServices + type A() = + static member aa ( + a, + []b: string, + []c: int, + []d: string, + []e: string) = + a,b,c,d,e + + let stringABC = "abc" + assert (A.aa(stringABC) = ("abc", ".ctor", 22, "C:\Program.fs", "stringABC")) + """ + |> withLangVersionPreview + |> compileAndRun + |> shouldSucceed + |> ignore + + [] + let ``Can define in F# - with #line`` () = + FSharp """#if !NETCOREAPP3_0_OR_GREATER +namespace System.Runtime.CompilerServices +open System +[] +type CallerArgumentExpressionAttribute(parameterName) = + inherit Attribute() + member val ParameterName: string = parameterName + +namespace global +#endif + +module Program = +# 1 "C:\\Program.fs" + open System.Runtime.CompilerServices + type A() = + static member aa ( + a, + []b: string, + []c: int, + []d: string, + []e: string) = + a,b,c,d,e + + let stringABC = "abc" + assert (A.aa(stringABC) = ("abc", ".ctor", 12, "C:\Program.fs", "stringABC")) + """ + |> withLangVersionPreview + |> compileAndRun + |> shouldSucceed + |> ignore + diff --git a/tests/FSharp.Compiler.ComponentTests/ErrorMessages/UnsupportedAttributes.fs b/tests/FSharp.Compiler.ComponentTests/ErrorMessages/UnsupportedAttributes.fs index 3eb78de55b4..8f11db7aa58 100644 --- a/tests/FSharp.Compiler.ComponentTests/ErrorMessages/UnsupportedAttributes.fs +++ b/tests/FSharp.Compiler.ComponentTests/ErrorMessages/UnsupportedAttributes.fs @@ -23,13 +23,6 @@ type C() = |> typecheck |> shouldFail |> withResults [ - { Error = Warning 202 - Range = { StartLine = 3 - StartColumn = 13 - EndLine = 3 - EndColumn = 41 } - Message = - "This attribute is currently unsupported by the F# compiler. Applying it will not achieve its intended effect." } { Error = Warning 202 Range = { StartLine = 4 StartColumn = 7 @@ -37,13 +30,13 @@ type C() = EndColumn = 24 } Message = "This attribute is currently unsupported by the F# compiler. Applying it will not achieve its intended effect." } - { Error = Warning 202 + { Error = Error 1247 Range = { StartLine = 6 - StartColumn = 22 + StartColumn = 14 EndLine = 6 - EndColumn = 82 } + EndColumn = 15 } Message = - "This attribute is currently unsupported by the F# compiler. Applying it will not achieve its intended effect." } + "'CallerArgumentExpression \"w\"' can only be applied to optional arguments" } { Error = Warning 202 Range = { StartLine = 7 StartColumn = 7 diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 03ff28e096a..1d0a6835aa0 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -39,6 +39,7 @@ + @@ -344,6 +345,7 @@ +