Interpolated string annotated `obj` produces unverifiable IL
- Dominant language
- F#
- Stars
- 4.3k
- Forks
- 876
- Avg merge
- 4d 22h
- Merged PRs (30d)
- 144
Description
(Bug found by Claude while I was implementing my [deterministic .NET runtime](https://github.com/Smaug123/WoofWare.PawPrint). This report is entirely my understanding, though the code snippets are Claude.)
Interpolated strings can produce unverifiable IL. This appears to be benign on CoreCLR (the reason appears to be because every mismatched type is a reference type, so `ldfld` only ever sees the same FieldDesc regardless of the type), but it can break other conforming IL consumers.
There are at least two distinct verification errors you can make this way, but the repro below is only one to make it cleaner; you get two simultaneous errors if you use `let f (x:int) : System.IComparable = $"{x}"`.
**Repro steps**
This is the code:
```fsharp
// Program.fs
let f (x : int) : obj = $"{x}"
System.Console.WriteLine (f 1)
```
```xml
Exe
net8.0
```
(The `obj` annotation is what causes the bug to appear. Without the annotation, the IL is fine.)
Then `dotnet build` (produces a Debug binary). This is not sensitive to net8 vs net10.
Then `rm -rf .tools && dotnet tool install --tool-path .tools dotnet-ilverify` and `dotnet .tools/.store/dotnet-ilverify/10.0.11/dotnet-ilverify/10.0.11/tools/net10.0/any/ILVerify.dll -r "/*.dll" -r ./bin/Debug/net8.0/FSharp.Core.dll` .
You can also produce a bug with `let f (x:int) : System.IComparable = $"{x}"` (which produces two errors) or with `let x = 3 in let o : obj = $"{x}"`.
**Expected behavior**
Clean verified IL like the following:
```
$ dotnet .tools/.store/dotnet-ilverify/10.0.11/dotnet-ilverify/10.0.11/tools/net10.0/any/ILVerify.dll ./bin/Debug/net8.0/repro4.dll -r "/nix/store/ngvwp9ba5xg3a9wilmy3nasgjzkylrpb-dotnet-sdk-10.0.203/share/dotnet/shared/Microsoft.NETCore.App/10.0.7/*.dll" -r ./bin/Debug/net8.0/FSharp.Core.dll
All Classes and Methods in /Users/patrick/Documents/GitHub/repro4/bin/Debug/net8.0/repro4.dll Verified.
```
(The paths look a bit exotic here, but I'm confident this is not a problem caused by Nix.)
**Actual behavior**
```
$ dotnet .tools/.store/dotnet-ilverify/10.0.11/dotnet-ilverify/10.0.11/tools/net10.0/any/ILVerify.dll ./bin/Debug/net8.0/repro4.dll -r "/nix/store/ngvwp9ba5xg3a9wilmy3nasgjzkylrpb-dotnet-sdk-10.0.203/share/dotnet/shared/Microsoft.NETCore.App/10.0.7/*.dll" -r ./bin/Debug/net8.0/FSharp.Core.dll
[IL]: Error [StackUnexpected]: [/Users/patrick/Documents/GitHub/repro4/bin/Debug/net8.0/repro4.dll : Program::f(int32)][offset 0x0000001E][found ref '[FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`5'][expected ref '[FSharp.Core]Microsoft.FSharp.Core.PrintfFormat`4'] Unexpected type on the stack.
1 Error(s) Verifying /Users/patrick/Documents/GitHub/repro4/bin/Debug/net8.0/repro4.dll
```
The compiler has allocated `PrintfFormat`, not the correct `PrintfFormat`.
The silent upcast which appears to cause this problem will become visible if you turn on warnings 3559 and 3388.
The `IComparable` thing I mentioned earlier has this additional verification error:
```
[IL]: Error [StackUnexpected]: [repro4.dll : Program::cmp(int32)][offset 0x00000023]
[found ref 'object'][expected ref '[S.P.CoreLib]System.IComparable'] Unexpected type on the stack.
```
**Known workarounds**
Boxing the output works: `let f (x : int) : obj = $"{x}" :> obj` (or a call to `box`).
**Related information**
I've reproduced this in some combinations of the net8 and net10 SDKs and a target framework of net8.0 and net10.0, on darwin-aarch64 and linux-x64 (though not the entire Cartesian product).
```
$ dotnet --info
.NET SDK:
Version: 10.0.203
Commit: c23858a6d8
Workload version: 10.0.200-manifests.ef86bccd
MSBuild version: 18.3.3+c23858a6d
Runtime Environment:
OS Name: Mac OS X
OS Version: 26.6
OS Platform: Darwin
RID: osx-arm64
Base Path: /nix/store/ngvwp9ba5xg3a9wilmy3nasgjzkylrpb-dotnet-sdk-10.0.203/share/dotnet/sdk/10.0.203/
.NET workloads installed:
There are no installed workloads to display.
Configured to use workload sets when installing new manifests.
No workload sets are installed. Run "dotnet workload restore" to install a workload set.
Host:
Version: 10.0.7
Architecture: arm64
Commit: b16286c228
.NET SDKs installed:
10.0.203 [/nix/store/ngvwp9ba5xg3a9wilmy3nasgjzkylrpb-dotnet-sdk-10.0.203/share/dotnet/sdk]
.NET runtimes installed:
Microsoft.AspNetCore.App 10.0.7 [/nix/store/ngvwp9ba5xg3a9wilmy3nasgjzkylrpb-dotnet-sdk-10.0.203/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 10.0.7 [/nix/store/ngvwp9ba5xg3a9wilmy3nasgjzkylrpb-dotnet-sdk-10.0.203/share/dotnet/shared/Microsoft.NETCore.App]
Other architectures found:
None
Environment variables:
DOTNET_CLI_TELEMETRY_OPTOUT [1]
DOTNET_LINUX_FRAMEWORK_DIR [/nix/store/fmkdxq8nv51yha9bqkc2695mcqxgrdq1-dotnet-linux-x64-framework-10.0.7]
DOTNET_NOLOGO [1]
DOTNET_RUNTIME_SRC [/nix/store/a34mfixzm2kyz64h9kqv31cdbiw4n83c-runtime-7706f54]
DOTNET_SKIP_FIRST_TIME_EXPERIENCE [1]
DOTNET_SKIP_WORKLOAD_INTEGRITY_CHECK [1]
DOTNET_USE_POLLING_FILE_WATCHER [1]
global.json file:
Not found
```
Contributor guide
Research direction
Start with the Program.fs reproducer and build it with dotnet, then run dotnet-ilverify against the generated assembly and FSharp.Core.dll. Compare the annotated and unannotated interpolated-string cases, including the IComparable variant and warning levels 3559 and 3388. Done means the affected cases produce clean verified IL without requiring the boxing workaround.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- fsharp
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100