Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/release-notes/.FSharp.Compiler.Service/10.0.300.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

### Added

* Added warning FS3882 when a function or delegate value is used as an interpolated string argument. ([PR #19289](https://github.com/dotnet/fsharp/pull/19289))

### Changed

* Centralized product TFM (Target Framework Moniker) into MSBuild props file `eng/TargetFrameworks.props`. Changing the target framework now only requires editing one file, and it integrates with MSBuild's `--getProperty` for scripts.
Expand Down
2 changes: 2 additions & 0 deletions docs/release-notes/.Language/preview.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
### Added

* Warn (FS3882) when a function or delegate value is used as an interpolated string argument, since it will be formatted via `ToString` rather than being applied. ([PR #19289](https://github.com/dotnet/fsharp/pull/19289))

### Fixed

### Changed
15 changes: 15 additions & 0 deletions src/Compiler/Checking/Expressions/CheckExpressions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7466,6 +7466,15 @@ and TcFormatStringExpr cenv (overallTy: OverallTy) env m tpenv (fmtString: strin
)

/// Check an interpolated string expression
and [<TailCall>] warnForFunctionValuesInFillExprs (g: TcGlobals) argTys synFillExprs =
match argTys, synFillExprs with
| argTy :: restTys, (synFillExpr: SynExpr) :: restExprs ->
if isFunTy g argTy || isDelegateTy g argTy then
warning (Error(FSComp.SR.tcFunctionValueUsedAsInterpolatedStringArg (), synFillExpr.Range))

warnForFunctionValuesInFillExprs g restTys restExprs
| _ -> ()

and TcInterpolatedStringExpr cenv (overallTy: OverallTy) env m tpenv (parts: SynInterpolatedStringPart list) =
let g = cenv.g

Expand Down Expand Up @@ -7615,6 +7624,9 @@ and TcInterpolatedStringExpr cenv (overallTy: OverallTy) env m tpenv (parts: Syn
// Type check the expressions filling the holes
let fillExprs, tpenv = TcExprsNoFlexes cenv env m tpenv argTys synFillExprs

if g.langVersion.SupportsFeature LanguageFeature.WarnWhenFunctionValueUsedAsInterpolatedStringArg then
warnForFunctionValuesInFillExprs g argTys synFillExprs

// Take all interpolated string parts and typed fill expressions
// and convert them to typed expressions that can be used as args to System.String.Concat
// return an empty list if there are some format specifiers that make lowering to not applicable
Expand Down Expand Up @@ -7684,6 +7696,9 @@ and TcInterpolatedStringExpr cenv (overallTy: OverallTy) env m tpenv (parts: Syn
// Type check the expressions filling the holes
let fillExprs, tpenv = TcExprsNoFlexes cenv env m tpenv argTys synFillExprs

if g.langVersion.SupportsFeature LanguageFeature.WarnWhenFunctionValueUsedAsInterpolatedStringArg then
warnForFunctionValuesInFillExprs g argTys synFillExprs

let fillExprsBoxed = (argTys, fillExprs) ||> List.map2 (mkCallBox g m)

let dotnetFormatStringExpr = mkString g m dotnetFormatString
Expand Down
4 changes: 3 additions & 1 deletion src/Compiler/FSComp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1803,5 +1803,7 @@ featureAllowLetOrUseBangTypeAnnotationWithoutParens,"Allow let! and use! type an
3878,tcAttributeIsNotValidForUnionCaseWithFields,"This attribute is not valid for use on union cases with fields."
3879,xmlDocNotFirstOnLine,"XML documentation comments should be the first non-whitespace text on a line."
featureReturnFromFinal,"Support for ReturnFromFinal/YieldFromFinal in computation expressions to enable tailcall optimization when available on the builder."
featureWarnWhenFunctionValueUsedAsInterpolatedStringArg,"Warn when a function value is used as an interpolated string argument"
3880,optsLangVersionOutOfSupport,"Language version '%s' is out of support. The last .NET SDK supporting it is available at https://dotnet.microsoft.com/en-us/download/dotnet/%s"
3881,optsUnrecognizedLanguageFeature,"Unrecognized language feature name: '%s'. Use a valid feature name such as 'NameOf' or 'StringInterpolation'."
3881,optsUnrecognizedLanguageFeature,"Unrecognized language feature name: '%s'. Use a valid feature name such as 'NameOf' or 'StringInterpolation'."
3882,tcFunctionValueUsedAsInterpolatedStringArg,"This expression is a function value. When used in an interpolated string it will be formatted using its 'ToString' method, which is likely not the intended behavior. Consider applying the function to its arguments."
4 changes: 4 additions & 0 deletions src/Compiler/Facilities/LanguageFeatures.fs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ type LanguageFeature =
| ErrorOnInvalidDeclsInTypeDefinitions
| AllowTypedLetUseAndBang
| ReturnFromFinal
| WarnWhenFunctionValueUsedAsInterpolatedStringArg

/// LanguageVersion management
type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array) =
Expand Down Expand Up @@ -245,6 +246,7 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array)

// F# 11.0
// Put stabilized features here for F# 11.0 previews via .NET SDK preview channels
LanguageFeature.WarnWhenFunctionValueUsedAsInterpolatedStringArg, languageVersion110

// Difference between languageVersion110 and preview - 11.0 gets turned on automatically by picking a preview .NET 11 SDK
// previewVersion is only when "preview" is specified explicitly in project files and users also need a preview SDK
Expand Down Expand Up @@ -440,6 +442,8 @@ type LanguageVersion(versionText, ?disabledFeaturesArray: LanguageFeature array)
| LanguageFeature.ErrorOnInvalidDeclsInTypeDefinitions -> FSComp.SR.featureErrorOnInvalidDeclsInTypeDefinitions ()
| LanguageFeature.AllowTypedLetUseAndBang -> FSComp.SR.featureAllowLetOrUseBangTypeAnnotationWithoutParens ()
| LanguageFeature.ReturnFromFinal -> FSComp.SR.featureReturnFromFinal ()
| LanguageFeature.WarnWhenFunctionValueUsedAsInterpolatedStringArg ->
FSComp.SR.featureWarnWhenFunctionValueUsedAsInterpolatedStringArg ()

/// Get a version string associated with the given feature.
static member GetFeatureVersionString feature =
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/Facilities/LanguageFeatures.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ type LanguageFeature =
| ErrorOnInvalidDeclsInTypeDefinitions
| AllowTypedLetUseAndBang
| ReturnFromFinal
| WarnWhenFunctionValueUsedAsInterpolatedStringArg

/// LanguageVersion management
type LanguageVersion =
Expand Down
2 changes: 1 addition & 1 deletion src/Compiler/Service/FSharpProjectSnapshot.fs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ type FSharpFileSnapshot(FileName: string, Version: string, GetSource: unit -> Ta
task {
match! f fileName |> Async.StartAsTask with
| Some source -> return SourceTextNew.ofISourceText source
| None -> return failwith $"Couldn't get source for file {f}"
| None -> return failwith $"Couldn't get source for file {fileName}"
}
)

Expand Down
8 changes: 4 additions & 4 deletions src/Compiler/Utilities/DependencyGraph.fs
Original file line number Diff line number Diff line change
Expand Up @@ -329,22 +329,22 @@ type GraphExtensions =
static member Unpack(node: 'NodeValue, unpacker) =
match unpacker node with
| Some value -> value
| None -> failwith $"Expected {unpacker} but got: {node}"
| None -> failwith $"Expected unpacker to match but got: {node}"

[<Extension>]
static member UnpackOne(dependencies: 'NodeValue seq, unpacker: 'NodeValue -> 'UnpackedDependency option) =
dependencies
|> Seq.tryExactlyOne
|> Option.bind unpacker
|> Option.defaultWith (fun () ->
failwith $"Expected exactly one dependency matching {unpacker} but got: %A{dependencies |> Seq.toArray}")
failwith $"Expected exactly one dependency matching unpacker but got: %A{dependencies |> Seq.toArray}")

[<Extension>]
static member UnpackMany(dependencies: 'NodeValue seq, unpacker) =
let results = dependencies |> Seq.choose unpacker

if dependencies |> Seq.length <> (results |> Seq.length) then
failwith $"Expected all dependencies to match {unpacker} but got: %A{dependencies |> Seq.toArray}"
failwith $"Expected all dependencies to match unpacker but got: %A{dependencies |> Seq.toArray}"

results

Expand All @@ -361,7 +361,7 @@ type GraphExtensions =
| None, None -> extras.Add dependency |> ignore

match oneResult with
| None -> failwith $"Expected exactly one dependency matching {oneUnpacker} but didn't find any"
| None -> failwith $"Expected exactly one dependency matching oneUnpacker but didn't find any"
| Some head ->
if extras.Count > 0 then
failwith $"Found extra dependencies: %A{extras.ToArray()}"
Expand Down
10 changes: 10 additions & 0 deletions src/Compiler/xlf/FSComp.txt.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compiler/xlf/FSComp.txt.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compiler/xlf/FSComp.txt.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compiler/xlf/FSComp.txt.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions src/Compiler/xlf/FSComp.txt.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading