Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
7192705
Avoid leaking a MeterListener per Cache in DEBUG builds (#19995)
NatElkins Jul 8, 2026
3ebcc74
Bump FCSMinorVersion to 13 (keep main above 10.0.4xx servicing 43.12.…
T-Gro Jul 9, 2026
87ad51b
Update dependencies from https://github.com/dotnet/msbuild build 2026…
dotnet-maestro[bot] Jul 9, 2026
d11945a
Update dependencies from https://github.com/dotnet/roslyn build 20260…
dotnet-maestro[bot] Jul 9, 2026
60d315a
Add ResetCompilerGeneratedNameState to compiler-generated name genera…
NatElkins Jul 9, 2026
5928e91
Update dependencies from https://github.com/dotnet/msbuild build 2026…
dotnet-maestro[bot] Jul 10, 2026
4eefd05
[main] Update dependencies from dotnet/msbuild (#20055)
dotnet-maestro[bot] Jul 21, 2026
2cd254e
[main] Update dependencies from dotnet/roslyn (#20052)
dotnet-maestro[bot] Jul 21, 2026
9fc230a
Localized file check-in by OneLocBuild Task: Build definition ID 499:…
dotnet-bot Jul 21, 2026
e729d97
[main] Update dependencies from dotnet/arcade (#20054)
dotnet-maestro[bot] Jul 22, 2026
69fca7f
Tests/source context: support multiple carets (#20077)
auduchinok Jul 22, 2026
2f07589
Move VS language-service logic tests to FSharp.Compiler.Service.Tests…
T-Gro Jul 23, 2026
24d731a
Add Roslyn-format EnC CustomDebugInformation codec and portable PDB m…
NatElkins Jul 24, 2026
2e838bb
Add stable synthesized-name replay infrastructure for hot reload (#20…
NatElkins Jul 24, 2026
4a2749e
Fix #19457: lift CE constructs from plain let RHS in computation expr…
T-Gro Jul 24, 2026
1d8dc39
Fix attribute resolution in recursive module/namespace scopes (#19744)
T-Gro Jul 24, 2026
1dc395a
Correct StructLayout size emission for data-less struct unions (#19759)
Copilot Jul 24, 2026
8c0e444
Report FS3888 for generic attribute type abbreviations instead of FS0…
T-Gro Jul 26, 2026
fd6ed49
Move to .NET 11 (SDK, Arcade, product TargetFramework) (#20080)
T-Gro Jul 28, 2026
0c94e4e
Support NotNullIfNotNullAttribute (#19977)
kerams Jul 28, 2026
5dfbf7f
Move SDL/TSA validation to 1ES templates after Arcade 11 upgrade (#20…
abonie Jul 29, 2026
17cb503
Compiled ToStrings under -reflectionfree for DUs and Records (#19976)
charlesroddie Jul 30, 2026
c00299f
Run ilverify via the tool manifest instead of a hard-coded cache path…
abonie Jul 30, 2026
f4b785f
Record spreads (#18927)
brianrourkeboll Aug 1, 2026
d3403ca
Implement interpolated strings via String.Concat (#19971)
charlesroddie Aug 1, 2026
f5c88eb
[main] Source code updates from dotnet/dotnet (#20058)
dotnet-maestro[bot] Aug 3, 2026
647548b
Secure release-note checks for fork pull requests (#20081)
NatElkins Aug 3, 2026
536800c
Update test project to net11 (#20104)
abonie Aug 3, 2026
a6b69ce
Update dependencies from https://github.com/dotnet/arcade build 20260…
dotnet-maestro[bot] Aug 4, 2026
3cc7755
Move to Roslyn's unified ExternalAccess library (#20099)
JoeRobich Aug 4, 2026
ea778bb
LexFilter: drop non-strict mode (#20106)
auduchinok Aug 4, 2026
36ce348
Enable Central Package Management with transitive pinning (#20084)
T-Gro Aug 4, 2026
d89529c
Implement direct delegates (#19993)
kerams Aug 4, 2026
7e2af62
Merge remote-tracking branch 'origin/main' into darc-release/10.0.4xx…
invalid-email-address Aug 4, 2026
92a3700
Fix check_release_notes 403 by restoring pull-requests: write and mak…
T-Gro Aug 4, 2026
8ed0ce1
Restore NuGetRepack UsingTask workaround for Arcade 10 in Microsoft.F…
invalid-email-address Aug 4, 2026
4d65a64
Merge remote-tracking branch 'origin/main' into darc-release/10.0.4xx…
invalid-email-address Aug 4, 2026
3bc2155
Update dependencies from https://github.com/dotnet/arcade build 20260…
dotnet-maestro[bot] Aug 5, 2026
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
Prev Previous commit
Next Next commit
Implement interpolated strings via String.Concat (#19971)
  • Loading branch information
charlesroddie authored Aug 1, 2026
commit d3403caee0e62ae3a64964f19fc93f20a50d6aae
4 changes: 4 additions & 0 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@

* Improvements in error and warning messages: new error FS3885 when `let!`/`use!` is the final expression in a computation expression; new warning FS3886 when a list literal contains a single tuple element (likely missing `;` separator); improved wording for FS0003, FS0025, FS0039, FS0072, FS0247, FS0597, FS0670, FS3082, and SRTP operator-not-in-scope hints. ([PR #19398](https://github.com/dotnet/fsharp/pull/19398))
* Exception field serialization (`GetObjectData` and field-restoring constructor) is now gated behind `langversion:11` (`LanguageFeature.ExceptionFieldSerializationSupport`). With langversion ≤10, exception codegen is unchanged from pre-#19342 behavior. ([PR #19746](https://github.com/dotnet/fsharp/pull/19746))
* Lower string-typed interpolated strings to `System.String.Concat` rather than the reflection-based `printf` engine, making them trim- and NativeAOT-compatible. This generalizes and ungates the previous all-string `String.Concat` optimization, so it now applies to every string-typed interpolation. ([Language suggestion #1108](https://github.com/fsharp/fslang-suggestions/issues/1108), [PR #19971](https://github.com/dotnet/fsharp/pull/19971))
* Interpolated string holes (e.g. `$"{x}"`) are now formatted with invariant culture (via the `string` operator) instead of the current thread culture. ([PR #19971](https://github.com/dotnet/fsharp/pull/19971))

### Breaking Changes

* `FSharp.Compiler.Syntax.SynInterpolatedStringPart.FillExpr` now carries a `SynInterpolationFormatting` value (separating .NET alignment/format from printf specifiers) instead of an `Ident option`. ([PR #19971](https://github.com/dotnet/fsharp/pull/19971))
* Optimizer: don't inline named functions in debug builds ([PR #19548](https://github.com/dotnet/fsharp/pull/19548)
6 changes: 4 additions & 2 deletions src/Compiler/Checking/CheckFormatStrings.fs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ let mkFlexibleDecimalFormatTypar (g: TcGlobals) m =
let mkFlexibleFloatFormatTypar (g: TcGlobals) m =
mkFlexibleFormatTypar g m [ g.float_ty; g.float32_ty; g.decimal_ty ] g.float_ty

let stringFormatTy (g: TcGlobals) =
if g.checkNullness && g.langFeatureNullness then g.string_ty_ambivalent else g.string_ty

type FormatInfoRegister =
{ mutable leftJustify : bool
mutable numPrefixIfPos : char option
Expand Down Expand Up @@ -448,8 +451,7 @@ let parseFormatStringInternal
checkOtherFlags ch
collectSpecifierLocation fragLine fragCol 1
let i = skipPossibleInterpolationHole (i+1)
let stringTy = if g.checkNullness && g.langFeatureNullness then g.string_ty_ambivalent else g.string_ty
parseLoop ((posi, stringTy) :: acc) (i, fragLine, fragCol+1) fragments
parseLoop ((posi, stringFormatTy g) :: acc) (i, fragLine, fragCol+1) fragments

| 'O' ->
checkOtherFlags ch
Expand Down
9 changes: 9 additions & 0 deletions src/Compiler/Checking/CheckFormatStrings.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ open FSharp.Compiler.TcGlobals
open FSharp.Compiler.Text
open FSharp.Compiler.TypedTree

/// A flexible type variable constrained to the integer types accepted by the '%d'/'%i'/'%u' specifiers.
val mkFlexibleIntFormatTypar: g: TcGlobals -> m: range -> TType

/// A flexible type variable constrained to 'decimal', as accepted by the '%M' specifier.
val mkFlexibleDecimalFormatTypar: g: TcGlobals -> m: range -> TType

/// The type accepted by the '%s' specifier: ambivalent about nullness when nullness is checked.
val stringFormatTy: g: TcGlobals -> TType

val ParseFormatString:
m: range ->
fragmentRanges: range list ->
Expand Down
227 changes: 115 additions & 112 deletions src/Compiler/Checking/Expressions/CheckExpressions.fs

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion src/Compiler/Service/SynExpr.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1087,10 +1087,13 @@ module SynExpr =
| SynExpr.InterpolatedString _, SynExpr.Sequential _
| SynExpr.InterpolatedString _, SynExpr.Tuple(isStruct = false) -> true

// Removing the parens would let a trailing alignment or format be parsed as part of the hole,
// e.g. the ',-3' in '$"{(if b then 1 else 0),-3}"' becoming a tuple in the else branch.
| SynExpr.InterpolatedString(contents = contents), Dangling.Problematic _ ->
contents
|> List.exists (function
| SynInterpolatedStringPart.FillExpr(qualifiers = Some _) -> true
| SynInterpolatedStringPart.FillExpr(formatting = SynInterpolationFormatting.DotNet(alignment = Some _))
| SynInterpolatedStringPart.FillExpr(formatting = SynInterpolationFormatting.DotNet(format = Some _)) -> true
| _ -> false)

// {| A = (1; 2) |}
Expand Down
46 changes: 46 additions & 0 deletions src/Compiler/SyntaxTree/ParseHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,52 @@ let rhs2 (parseState: IParseState) i j =
/// Get the range corresponding to one of the r.h.s. symbols of a grammar rule while it is being reduced
let rhs parseState i = rhs2 parseState i i

/// Split a trailing printf specifier (e.g. "%d") off an interpolated-string literal that precedes a
/// hole. '%%' is a literal escape, not a specifier.
let peelTrailingPrintfSpecifier (litText: string) : string * string option =
let n = litText.Length
let mutable i = 0
let mutable specStart = -1

while i < n && specStart < 0 do
if litText[i] = '%' then
if i + 1 < n && litText[i + 1] = '%' then
i <- i + 2 // '%%' escape, keep scanning
else
specStart <- i // start of a real specifier
else
i <- i + 1

// A real printf specifier ends, immediately before the hole, with a type character. Anything else
// (for example the explicit '%P(' placeholder syntax) is left in the literal untouched.
if specStart < 0 || "bscdiuxXoBeEfFgGMOAat".IndexOf litText[n - 1] < 0 then
litText, None
else
litText[.. specStart - 1], Some litText[specStart..]

/// Build the [String literal; FillExpr hole] pair for one interpolation hole, splitting the '{x,n}'
/// alignment out of its tuple encoding and peeling a trailing printf specifier onto the hole.
let mkInterpolatedStringFillParts (litText: string, litRange: range, fill: SynExpr * Ident option) =
let fillExpr, qualifier = fill

let holeExpr, alignment =
match fillExpr with
| SynExpr.Tuple(false, [ e; (SynExpr.Const(SynConst.Int32 _, _) as n) ], _, _) -> e, Some n
| _ -> fillExpr, None

let litValue, formatting =
match qualifier, alignment with
| None, None ->
match peelTrailingPrintfSpecifier litText with
| lit, Some spec -> lit, SynInterpolationFormatting.Printf(spec, litRange)
| _, None -> litText, SynInterpolationFormatting.DotNet(None, None)
| _ -> litText, SynInterpolationFormatting.DotNet(alignment, qualifier)

[
SynInterpolatedStringPart.String(litValue, litRange)
SynInterpolatedStringPart.FillExpr(holeExpr, formatting)
]

//------------------------------------------------------------------------
// Parsing/lexing: status of #if/#endif processing in lexing, used for continuations
// for whitespace tokens in parser specification.
Expand Down
10 changes: 10 additions & 0 deletions src/Compiler/SyntaxTree/ParseHelpers.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ val rhs2: parseState: IParseState -> i: int -> j: int -> range

val rhs: parseState: IParseState -> i: int -> range

/// Peel a trailing printf specifier (e.g. "%d") off an interpolated-string literal that precedes a
/// hole, returning the literal without it and the specifier text. '%%' is a literal escape.
val peelTrailingPrintfSpecifier: litText: string -> string * string option

/// Build the [String literal; FillExpr hole] pair for one interpolation hole, splitting the
/// '{x,n}' alignment out of its tuple encoding and peeling a trailing printf specifier off the
/// literal onto the hole.
val mkInterpolatedStringFillParts:
litText: string * litRange: range * fill: (SynExpr * Ident option) -> SynInterpolatedStringPart list

type LexerIfdefStackEntry =
| IfDefIf
| IfDefElse
Expand Down
7 changes: 6 additions & 1 deletion src/Compiler/SyntaxTree/SyntaxTree.fs
Original file line number Diff line number Diff line change
Expand Up @@ -898,7 +898,12 @@ type SynExprAnonRecordFieldOrSpread =
[<NoEquality; NoComparison; RequireQualifiedAccess>]
type SynInterpolatedStringPart =
| String of value: string * range: range
| FillExpr of fillExpr: SynExpr * qualifiers: Ident option
| FillExpr of fillExpr: SynExpr * formatting: SynInterpolationFormatting

[<NoEquality; NoComparison; RequireQualifiedAccess>]
type SynInterpolationFormatting =
| DotNet of alignment: SynExpr option * format: Ident option
| Printf of specifier: string * range: range

[<NoEquality; NoComparison; RequireQualifiedAccess>]
type SynSimplePat =
Expand Down
11 changes: 10 additions & 1 deletion src/Compiler/SyntaxTree/SyntaxTree.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -1034,7 +1034,16 @@ type SynExprAnonRecordFieldOrSpread =
[<NoEquality; NoComparison; RequireQualifiedAccess>]
type SynInterpolatedStringPart =
| String of value: string * range: range
| FillExpr of fillExpr: SynExpr * qualifiers: Ident option
| FillExpr of fillExpr: SynExpr * formatting: SynInterpolationFormatting

/// Represents how an interpolation hole in an interpolated string is formatted.
[<NoEquality; NoComparison; RequireQualifiedAccess>]
type SynInterpolationFormatting =
/// .NET-style formatting: optional alignment '{x,n}' and optional format '{x:fmt}'.
| DotNet of alignment: SynExpr option * format: Ident option

/// printf-style formatting: a single specifier, the '%d' in '%d{x}'.
| Printf of specifier: string * range: range

/// Represents a syntax tree for simple F# patterns
[<NoEquality; NoComparison; RequireQualifiedAccess>]
Expand Down
1 change: 0 additions & 1 deletion src/Compiler/TypedTree/TcGlobals.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1725,7 +1725,6 @@ type TcGlobals(
member _.seq_map_info = v_seq_map_info
member _.seq_singleton_info = v_seq_singleton_info
member _.seq_empty_info = v_seq_empty_info
member _.sprintf_info = v_sprintf_info
member _.new_format_info = v_new_format_info
member _.unbox_info = v_unbox_info
member _.get_generic_comparer_info = v_get_generic_comparer_info
Expand Down
2 changes: 0 additions & 2 deletions src/Compiler/TypedTree/TcGlobals.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -1007,8 +1007,6 @@ type internal TcGlobals =

member splice_raw_expr_vref: TypedTree.ValRef

member sprintf_info: IntrinsicValRef

member sprintf_vref: TypedTree.ValRef

member string_ty: TypedTree.TType
Expand Down
3 changes: 0 additions & 3 deletions src/Compiler/TypedTree/TypedTreeOps.ExprOps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1454,9 +1454,6 @@ module internal Makers =
let mkCallSeqEmpty g m ty1 =
mkApps g (typedExprForIntrinsic g m g.seq_empty_info, [ [ ty1 ] ], [], m)

let mkCall_sprintf (g: TcGlobals) m funcTy fmtExpr fillExprs =
mkApps g (typedExprForIntrinsic g m g.sprintf_info, [ [ funcTy ] ], fmtExpr :: fillExprs, m)

let mkCallDeserializeQuotationFSharp20Plus g m e1 e2 e3 e4 =
let args = [ e1; e2; e3; e4 ]
mkApps g (typedExprForIntrinsic g m g.deserialize_quoted_FSharp_20_plus_info, [], [ mkRefTupledNoTypes g m args ], m)
Expand Down
3 changes: 0 additions & 3 deletions src/Compiler/TypedTree/TypedTreeOps.ExprOps.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -404,9 +404,6 @@ module internal Makers =

val mkCallSeqEmpty: TcGlobals -> range -> TType -> Expr

/// Make a call to the 'isprintf' function for string interpolation
val mkCall_sprintf: g: TcGlobals -> m: range -> funcTy: TType -> fmtExpr: Expr -> fillExprs: Expr list -> Expr

val mkCallDeserializeQuotationFSharp20Plus: TcGlobals -> range -> Expr -> Expr -> Expr -> Expr -> Expr

val mkCallDeserializeQuotationFSharp40Plus: TcGlobals -> range -> Expr -> Expr -> Expr -> Expr -> Expr -> Expr
Expand Down
4 changes: 2 additions & 2 deletions src/Compiler/pars.fsy
Original file line number Diff line number Diff line change
Expand Up @@ -7235,7 +7235,7 @@ interpolatedStringParts:
{ [ SynInterpolatedStringPart.String(fst $1, rhs parseState 1) ] }

| INTERP_STRING_PART interpolatedStringFill interpolatedStringParts
{ SynInterpolatedStringPart.String(fst $1, rhs parseState 1) :: SynInterpolatedStringPart.FillExpr $2 :: $3 }
{ mkInterpolatedStringFillParts (fst $1, rhs parseState 1, $2) @ $3 }

| INTERP_STRING_PART interpolatedStringParts
{ let rbrace = parseState.InputEndPosition 1
Expand All @@ -7249,7 +7249,7 @@ interpolatedStringParts:
interpolatedString:
| INTERP_STRING_BEGIN_PART interpolatedStringFill interpolatedStringParts
{ let s, synStringKind, _ = $1
SynInterpolatedStringPart.String(s, rhs parseState 1) :: SynInterpolatedStringPart.FillExpr $2 :: $3, synStringKind }
mkInterpolatedStringFillParts (s, rhs parseState 1, $2) @ $3, synStringKind }

| INTERP_STRING_BEGIN_END
{ let s, synStringKind, _ = $1
Expand Down
36 changes: 36 additions & 0 deletions tests/AheadOfTime/NativeAOT/NativeAOT_Test.fsproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net9.0</TargetFrameworks>
<LangVersion>preview</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>

<PropertyGroup>
<DisableImplicitFSharpCoreReference>true</DisableImplicitFSharpCoreReference>
<DisableImplicitNuGetFallbackFolder>true</DisableImplicitNuGetFallbackFolder>
<DisableImplicitLibraryPacksFolder>true</DisableImplicitLibraryPacksFolder>
<PublishAot>true</PublishAot>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
</PropertyGroup>

<PropertyGroup>
<FSharpBuildAssemblyFile>$(LocalFSharpBuildBinPath)/FSharp.Build.dll</FSharpBuildAssemblyFile>
<DotnetFscCompilerPath>$(LocalFSharpBuildBinPath)/fsc.dll</DotnetFscCompilerPath>
<Fsc_DotNET_DotnetFscCompilerPath>$(LocalFSharpBuildBinPath)/fsc.dll</Fsc_DotNET_DotnetFscCompilerPath>
<FSharpPreferNetFrameworkTools>False</FSharpPreferNetFrameworkTools>
<FSharpPrefer64BitTools>True</FSharpPrefer64BitTools>
</PropertyGroup>

<ItemGroup>
<Compile Include="Program.fs" />
</ItemGroup>

<Import Project="$(MSBuildThisFileDirectory)../../../eng/Versions.props" />

<ItemGroup>
<PackageReference Include="FSharp.Core" Version="$(FSharpCorePreviewPackageVersionValue)" />
</ItemGroup>

</Project>
34 changes: 34 additions & 0 deletions tests/AheadOfTime/NativeAOT/Program.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module Program

open System

// Check a rendering against an expected string literal; a mismatch prints a "FAILED" line.
let check (actual: string, expected: string) =
if actual <> expected then
Console.WriteLine $"FAILED: expected '{expected}' but got '{actual}'"

let runChecks () =
let x = 42
let name = "world"
let pi = 3.14159
let initial = 'F'
check ($"answer = {x}", "answer = 42")
check ($"hello {name}", "hello world")
check ($"pi ~ {pi:F2}", "pi ~ 3.14")
check ($"padded:{x,6}", "padded: 42")
check ($"greeting %s{name}", "greeting world")
// Bare '%d'/'%i'/'%c'/'%M' specifiers lower to the same reflection-free path as a plain hole.
check ($"answer = %d{x}", "answer = 42")
check ($"initial = %c{initial}", "initial = F")

// The following use printf specifiers that still route through 'sprintf', so they would make the
// NativeAOT publish fail with IL2026/IL2070/IL3050.
// check ($"pi ~ %.2f{pi}", "pi ~ 3.14")
// check ($"value = %A{x}", "value = 42")

[<EntryPoint>]
let main _ =
runChecks ()
// Success sentinel; a failed check above printed a "FAILED" line first, so the output won't be just this.
Console.WriteLine "Finished"
0
2 changes: 2 additions & 0 deletions tests/AheadOfTime/NativeAOT/check.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@echo off
powershell -ExecutionPolicy ByPass -NoProfile -command "& """%~dp0check.ps1""""
37 changes: 37 additions & 0 deletions tests/AheadOfTime/NativeAOT/check.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Publish the test project with NativeAOT and check that it runs.
#
# The point of this check is that the publish succeeds: a string-typed interpolated string
# must lower to a reflection-free form (System.String.Concat), not the reflection-based
# printf engine. If it regresses to printf, FSharp.Reflection becomes statically reachable,
# NativeAOT analysis emits IL2026/IL2070/IL3050, TreatWarningsAsErrors turns them into errors,
# and this publish fails.

$ErrorActionPreference = "Stop"

$root = "NativeAOT_Test"
$tfm = "net9.0"

$cwd = Get-Location
Set-Location $PSScriptRoot

dotnet publish -restore -c release -f:$tfm "$root.fsproj" -bl:"$PSScriptRoot/../../../artifacts/log/Release/AheadOfTime/NativeAOT/$root.binlog"
if (-not ($LASTEXITCODE -eq 0)) {
Set-Location $cwd
Write-Error "NativeAOT publish failed with exit code $LASTEXITCODE" -ErrorAction Stop
}

$exe = Join-Path $PSScriptRoot "bin/release/$tfm/win-x64/publish/$root.exe"
$output = (& $exe) -join "`n"
$exitCode = $LASTEXITCODE
Set-Location $cwd

# The app prints a "FAILED" line per mismatch and "Finished" last, so its output is exactly "Finished" only if all checks passed.
if (-not ($exitCode -eq 0)) {
Write-Error "NativeAOT app crashed with exit code $exitCode.`nOutput:`n$output" -ErrorAction Stop
}

if ($output.Trim() -ne "Finished") {
Write-Error "NativeAOT interpolation checks failed.`nOutput:`n$output" -ErrorAction Stop
}

Write-Host "NativeAOT interpolated-string test passed."
4 changes: 2 additions & 2 deletions tests/AheadOfTime/Trimming/check.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ $allErrors += CheckTrim -root "SelfContained_Trimming_Test" -tfm "net9.0" -outpu
# Check net9.0 trimmed assemblies with static linked FSharpCore.
# Statically links FSharp.Compiler.Service; the size is stable now that its codegen is
# deterministic (#19928/#19929). Update if compiler/trimming output intentionally changes.
$allErrors += CheckTrim -root "StaticLinkedFSharpCore_Trimming_Test" -tfm "net9.0" -outputfile "StaticLinkedFSharpCore_Trimming_Test.dll" -expected_len 9173504 -callerLineNumber 71
$allErrors += CheckTrim -root "StaticLinkedFSharpCore_Trimming_Test" -tfm "net9.0" -outputfile "StaticLinkedFSharpCore_Trimming_Test.dll" -expected_len 9174016 -callerLineNumber 71

# Check net9.0 trimmed assemblies with F# metadata resources removed
$allErrors += CheckTrim -root "FSharpMetadataResource_Trimming_Test" -tfm "net9.0" -outputfile "FSharpMetadataResource_Trimming_Test.dll" -expected_len 7612928 -callerLineNumber 74
$allErrors += CheckTrim -root "FSharpMetadataResource_Trimming_Test" -tfm "net9.0" -outputfile "FSharpMetadataResource_Trimming_Test.dll" -expected_len 7613440 -callerLineNumber 74

# Report all errors and exit with failure if any occurred
if ($allErrors.Count -gt 0) {
Expand Down
1 change: 1 addition & 0 deletions tests/AheadOfTime/check.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ Write-Host "AheadOfTime: check1.ps1"

Equality\check.ps1
Trimming\check.ps1
NativeAOT\check.ps1
Loading
Loading