From 5078796b1777ce78bff1347579771846fd52a532 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Tue, 7 Dec 2021 19:22:20 +0100 Subject: [PATCH 1/2] Add support for string format alignment. --- .../TestCases/Pretty/StringInterpolation.cs | 2 + ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 64 ++++++++++++++++--- .../OutputVisitor/CSharpOutputVisitor.cs | 5 ++ .../InterpolatedStringExpression.cs | 12 +++- 4 files changed, 72 insertions(+), 11 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StringInterpolation.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StringInterpolation.cs index 41f57d523..fc667d275 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StringInterpolation.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/StringInterpolation.cs @@ -20,6 +20,8 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty Console.WriteLine($"\ta{args[0][0] == 'a'}"); Console.WriteLine($"\ta{$"a{args.Length}" == args[0]}"); Console.WriteLine($"\ta{args.Length}}}"); + Console.WriteLine($"{args.Length,5:x}"); + Console.WriteLine($"{args.Length,5}"); } public static void ArrayExpansionSpecialCases(object[] args) diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 00f7fc91f..05c1568cc 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -357,7 +357,7 @@ namespace ICSharpCode.Decompiler.CSharp if (tokens.Count > 0) { - foreach (var (kind, index, text) in tokens) + foreach (var (kind, index, alignment, text) in tokens) { TranslatedExpression argument; switch (kind) @@ -373,7 +373,17 @@ namespace ICSharpCode.Decompiler.CSharp case TokenKind.ArgumentWithFormat: argument = arguments[index + 1]; UnpackSingleElementArray(ref argument); - content.Add(new Interpolation(argument, text)); + content.Add(new Interpolation(argument, suffix: text)); + break; + case TokenKind.ArgumentWithAlignment: + argument = arguments[index + 1]; + UnpackSingleElementArray(ref argument); + content.Add(new Interpolation(argument, alignment)); + break; + case TokenKind.ArgumentWithAlignmentAndFormat: + argument = arguments[index + 1]; + UnpackSingleElementArray(ref argument); + content.Add(new Interpolation(argument, alignment, text)); break; } } @@ -566,7 +576,7 @@ namespace ICSharpCode.Decompiler.CSharp ); } - private bool TryGetStringInterpolationTokens(ArgumentList argumentList, out string format, out List<(TokenKind, int, string)> tokens) + private bool TryGetStringInterpolationTokens(ArgumentList argumentList, out string format, out List<(TokenKind Kind, int Index, int Alignment, string Format)> tokens) { tokens = null; format = null; @@ -577,33 +587,56 @@ namespace ICSharpCode.Decompiler.CSharp return false; if (!arguments.Skip(1).All(a => !a.Expression.DescendantsAndSelf.OfType().Any(p => p.Value is string))) return false; - tokens = new List<(TokenKind, int, string)>(); + tokens = new List<(TokenKind Kind, int Index, int Alignment, string Format)>(); int i = 0; format = (string)crr.ConstantValue; foreach (var (kind, data) in TokenizeFormatString(format)) { int index; + string[] arg; switch (kind) { case TokenKind.Error: return false; case TokenKind.String: - tokens.Add((kind, -1, data)); + tokens.Add((kind, -1, 0, data)); break; case TokenKind.Argument: if (!int.TryParse(data, out index) || index != i) return false; i++; - tokens.Add((kind, index, null)); + tokens.Add((kind, index, 0, null)); break; case TokenKind.ArgumentWithFormat: - string[] arg = data.Split(new[] { ':' }, 2); + arg = data.Split(new[] { ':' }, 2); if (arg.Length != 2 || arg[1].Length == 0) return false; if (!int.TryParse(arg[0], out index) || index != i) return false; i++; - tokens.Add((kind, index, arg[1])); + tokens.Add((kind, index, 0, arg[1])); + break; + case TokenKind.ArgumentWithAlignment: + arg = data.Split(new[] { ',' }, 2); + if (arg.Length != 2 || arg[1].Length == 0) + return false; + if (!int.TryParse(arg[0], out index) || index != i) + return false; + if (!int.TryParse(arg[1], out int alignment)) + return false; + i++; + tokens.Add((kind, index, alignment, null)); + break; + case TokenKind.ArgumentWithAlignmentAndFormat: + arg = data.Split(new[] { ',', ':' }, 3); + if (arg.Length != 3 || arg[1].Length == 0 || arg[2].Length == 0) + return false; + if (!int.TryParse(arg[0], out index) || index != i) + return false; + if (!int.TryParse(arg[1], out alignment)) + return false; + i++; + tokens.Add((kind, index, alignment, arg[2])); break; default: return false; @@ -617,7 +650,9 @@ namespace ICSharpCode.Decompiler.CSharp Error, String, Argument, - ArgumentWithFormat + ArgumentWithFormat, + ArgumentWithAlignment, + ArgumentWithAlignmentAndFormat, } private IEnumerable<(TokenKind, string)> TokenizeFormatString(string value) @@ -685,8 +720,19 @@ namespace ICSharpCode.Decompiler.CSharp { kind = TokenKind.ArgumentWithFormat; } + else if (kind == TokenKind.ArgumentWithAlignment) + { + kind = TokenKind.ArgumentWithAlignmentAndFormat; + } sb.Append(':'); break; + case ',': + if (kind == TokenKind.Argument) + { + kind = TokenKind.ArgumentWithAlignment; + } + sb.Append(','); + break; default: sb.Append((char)next); break; diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs index 8f1bcc552..789253ad4 100644 --- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs +++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs @@ -1168,6 +1168,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor writer.WriteToken(Interpolation.LBrace, "{"); interpolation.Expression.AcceptVisitor(this); + if (interpolation.Alignment != 0) + { + writer.WriteToken(Roles.Comma, ","); + writer.WritePrimitiveValue(interpolation.Alignment); + } if (interpolation.Suffix != null) { writer.WriteToken(Roles.Colon, ":"); diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/InterpolatedStringExpression.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/InterpolatedStringExpression.cs index 9f7c6ab47..562755a0a 100644 --- a/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/InterpolatedStringExpression.cs +++ b/ICSharpCode.Decompiler/CSharp/Syntax/Expressions/InterpolatedStringExpression.cs @@ -20,6 +20,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax } + public InterpolatedStringExpression(IList content) + { + Content.AddRange(content); + } + public override void AcceptVisitor(IAstVisitor visitor) { visitor.VisitInterpolatedStringExpression(this); @@ -83,7 +88,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax } /// - /// { Expression } + /// { Expression , Alignment : Suffix } /// public class Interpolation : InterpolatedStringContent { @@ -99,6 +104,8 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax set { SetChildByRole(Roles.Expression, value); } } + public int Alignment { get; } + public string Suffix { get; } public CSharpTokenNode RBraceToken { @@ -110,9 +117,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax } - public Interpolation(Expression expression, string suffix = null) + public Interpolation(Expression expression, int alignment = 0, string suffix = null) { Expression = expression; + Alignment = alignment; Suffix = suffix; } From f695bbcf3a3b3957e93c232910556974b9785004 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 13 Jul 2022 14:14:45 +0200 Subject: [PATCH 2/2] Add support for DefaultInterpolatedStringHandler --- .../Helpers/Tester.cs | 1 + .../PrettyTestRunner.cs | 6 - .../TestCases/Correctness/StringConcat.cs | 17 +++ .../CSharp/CSharpDecompiler.cs | 3 +- .../CSharp/ExpressionBuilder.cs | 37 +++++ .../ICSharpCode.Decompiler.csproj | 3 +- .../IL/Instructions/Block.cs | 26 ++++ .../Transforms/InterpolatedStringTransform.cs | 132 ++++++++++++++++++ .../TypeSystem/KnownTypeReference.cs | 3 + 9 files changed, 220 insertions(+), 8 deletions(-) create mode 100644 ICSharpCode.Decompiler/IL/Transforms/InterpolatedStringTransform.cs diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index e71a1cb71..a116a04cd 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -321,6 +321,7 @@ namespace ICSharpCode.Decompiler.Tests.Helpers if (!flags.HasFlag(CompilerOptions.TargetNet40)) { preprocessorSymbols.Add("NETCORE"); + preprocessorSymbols.Add("NET60"); } preprocessorSymbols.Add("ROSLYN"); preprocessorSymbols.Add("CS60"); diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index 57cea8eba..bc5b895f8 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -532,12 +532,6 @@ namespace ICSharpCode.Decompiler.Tests [Test] public async Task StringInterpolation([ValueSource(nameof(roslynOnlyWithNet40Options))] CompilerOptions cscOptions) { - if (!cscOptions.HasFlag(CompilerOptions.TargetNet40) && cscOptions.HasFlag(CompilerOptions.UseRoslynLatest)) - { - Assert.Ignore("DefaultInterpolatedStringHandler is not yet supported!"); - return; - } - await Run(cscOptions: cscOptions); } diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/StringConcat.cs b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/StringConcat.cs index 01d15eccd..ffc6a16fe 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Correctness/StringConcat.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Correctness/StringConcat.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness { @@ -114,12 +115,28 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Correctness Console.WriteLine(a[0].ToString() + a[1].ToString()); } +#if NET60 && ROSLYN2 + static void TestManualDefaultStringInterpolationHandler() + { + Console.WriteLine("TestManualDefaultStringInterpolationHandler:"); + C c = new C(42); + DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(0, 1); + defaultInterpolatedStringHandler.AppendFormatted(c); + M2(Space(), defaultInterpolatedStringHandler.ToStringAndClear()); + } + + static void M2(object x, string y) { } +#endif + static void Main() { TestClass(); TestStruct(); TestStructMutation(); TestCharPlusChar("ab"); +#if NET60 && ROSLYN2 + TestManualDefaultStringInterpolationHandler(); +#endif } } } diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 0e9b6ad54..dca1897f8 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -150,7 +150,8 @@ namespace ICSharpCode.Decompiler.CSharp new IndexRangeTransform(), new DeconstructionTransform(), new NamedArgumentTransform(), - new UserDefinedLogicTransform() + new UserDefinedLogicTransform(), + new InterpolatedStringTransform() ), } }, diff --git a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs index cd3918ff6..fb86ca411 100644 --- a/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/ExpressionBuilder.cs @@ -22,6 +22,7 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Reflection.Metadata; +using System.Runtime.CompilerServices; using System.Threading; using ICSharpCode.Decompiler.CSharp.Resolver; @@ -3116,11 +3117,47 @@ namespace ICSharpCode.Decompiler.CSharp return TranslateSetterCallAssignment(block); case BlockKind.CallWithNamedArgs: return TranslateCallWithNamedArgs(block); + case BlockKind.InterpolatedString: + return TranslateInterpolatedString(block); default: return ErrorExpression("Unknown block type: " + block.Kind); } } + private TranslatedExpression TranslateInterpolatedString(Block block) + { + var content = new List(); + + for (int i = 1; i < block.Instructions.Count; i++) + { + var call = (Call)block.Instructions[i]; + switch (call.Method.Name) + { + case "AppendLiteral": + content.Add(new InterpolatedStringText(((LdStr)call.Arguments[1]).Value.Replace("{", "{{").Replace("}", "}}"))); + break; + case "AppendFormatted" when call.Arguments.Count == 2: + content.Add(new Interpolation(Translate(call.Arguments[1]))); + break; + case "AppendFormatted" when call.Arguments.Count == 3 && call.Arguments[2] is LdStr ldstr: + content.Add(new Interpolation(Translate(call.Arguments[1]), suffix: ldstr.Value)); + break; + case "AppendFormatted" when call.Arguments.Count == 3 && call.Arguments[2] is LdcI4 ldci4: + content.Add(new Interpolation(Translate(call.Arguments[1]), alignment: ldci4.Value)); + break; + case "AppendFormatted" when call.Arguments.Count == 4 && call.Arguments[2] is LdcI4 ldci4 && call.Arguments[3] is LdStr ldstr: + content.Add(new Interpolation(Translate(call.Arguments[1]), ldci4.Value, ldstr.Value)); + break; + default: + throw new NotSupportedException(); + } + } + + return new InterpolatedStringExpression(content) + .WithILInstruction(block) + .WithRR(new ResolveResult(compilation.FindType(KnownTypeCode.String))); + } + private TranslatedExpression TranslateCallWithNamedArgs(Block block) { return WrapInRef( diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index b17df4ce6..4f66c233f 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -27,7 +27,7 @@ False false - 9.0 + 10 true True ICSharpCode.Decompiler.snk @@ -114,6 +114,7 @@ + diff --git a/ICSharpCode.Decompiler/IL/Instructions/Block.cs b/ICSharpCode.Decompiler/IL/Instructions/Block.cs index cd0b43d4c..e8c486dba 100644 --- a/ICSharpCode.Decompiler/IL/Instructions/Block.cs +++ b/ICSharpCode.Decompiler/IL/Instructions/Block.cs @@ -194,6 +194,20 @@ namespace ICSharpCode.Decompiler.IL case BlockKind.DeconstructionAssignments: Debug.Assert(this.SlotInfo == DeconstructInstruction.AssignmentsSlot); break; + case BlockKind.InterpolatedString: + Debug.Assert(FinalInstruction is Call { Method: { Name: "ToStringAndClear" }, Arguments: { Count: 1 } }); + var interpolInit = Instructions[0] as StLoc; + DebugAssert(interpolInit != null + && interpolInit.Variable.Kind == VariableKind.InitializerTarget + && interpolInit.Variable.AddressCount == Instructions.Count + && interpolInit.Variable.StoreCount == 1); + for (int i = 1; i < Instructions.Count; i++) + { + Call? inst = Instructions[i] as Call; + DebugAssert(inst != null); + DebugAssert(inst.Arguments.Count >= 1 && inst.Arguments[0].MatchLdLoca(interpolInit.Variable)); + } + break; } } @@ -465,5 +479,17 @@ namespace ICSharpCode.Decompiler.IL /// DeconstructionAssignments, WithInitializer, + /// + /// String interpolation using DefaultInterpolatedStringHandler. + /// + /// + /// Block { + /// stloc I_0 = newobj DefaultInterpolatedStringHandler(...) + /// call AppendXXX(I_0, ...) + /// ... + /// final: call ToStringAndClear(ldloc I_0) + /// } + /// + InterpolatedString, } } diff --git a/ICSharpCode.Decompiler/IL/Transforms/InterpolatedStringTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/InterpolatedStringTransform.cs new file mode 100644 index 000000000..6efe01f26 --- /dev/null +++ b/ICSharpCode.Decompiler/IL/Transforms/InterpolatedStringTransform.cs @@ -0,0 +1,132 @@ +// Copyright (c) 2021 Siegfried Pammer +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Diagnostics; + +using ICSharpCode.Decompiler.TypeSystem; + +namespace ICSharpCode.Decompiler.IL.Transforms +{ + public class InterpolatedStringTransform : IStatementTransform + { + void IStatementTransform.Run(Block block, int pos, StatementTransformContext context) + { + if (!context.Settings.StringInterpolation) + return; + int interpolationStart = pos; + int interpolationEnd; + ILInstruction insertionPoint; + // stloc v(newobj DefaultInterpolatedStringHandler..ctor(ldc.i4 literalLength, ldc.i4 formattedCount)) + if (block.Instructions[pos] is StLoc + { + Variable: ILVariable { Kind: VariableKind.Local } v, + Value: NewObj { Arguments: { Count: 2 } } newObj + } stloc + && v.Type.IsKnownType(KnownTypeCode.DefaultInterpolatedStringHandler) + && newObj.Method.DeclaringType.IsKnownType(KnownTypeCode.DefaultInterpolatedStringHandler) + && newObj.Arguments[0].MatchLdcI4(out _) + && newObj.Arguments[1].MatchLdcI4(out _)) + { + // { call MethodName(ldloca v, ...) } + do + { + pos++; + } + while (IsKnownCall(block, pos, v)); + interpolationEnd = pos; + // ... call ToStringAndClear(ldloca v) ... + if (!FindToStringAndClear(block, pos, interpolationStart, interpolationEnd, v, out insertionPoint)) + { + return; + } + if (!(v.StoreCount == 1 && v.AddressCount == interpolationEnd - interpolationStart && v.LoadCount == 0)) + { + return; + } + } + else + { + return; + } + context.Step($"Transform DefaultInterpolatedStringHandler {v.Name}", stloc); + v.Kind = VariableKind.InitializerTarget; + var replacement = new Block(BlockKind.InterpolatedString); + for (int i = interpolationStart; i < interpolationEnd; i++) + { + replacement.Instructions.Add(block.Instructions[i]); + } + var callToStringAndClear = insertionPoint; + insertionPoint.ReplaceWith(replacement); + replacement.FinalInstruction = callToStringAndClear; + block.Instructions.RemoveRange(interpolationStart, interpolationEnd - interpolationStart); + } + + private bool IsKnownCall(Block block, int pos, ILVariable v) + { + if (pos >= block.Instructions.Count - 1) + return false; + if (!(block.Instructions[pos] is Call call)) + return false; + if (!(call.Arguments.Count > 1)) + return false; + if (!call.Arguments[0].MatchLdLoca(v)) + return false; + if (call.Method.IsStatic) + return false; + if (!call.Method.DeclaringType.IsKnownType(KnownTypeCode.DefaultInterpolatedStringHandler)) + return false; + switch (call.Method.Name) + { + case "AppendLiteral" when call.Arguments.Count == 2 && call.Arguments[1] is LdStr: + case "AppendFormatted" when call.Arguments.Count == 2: + case "AppendFormatted" when call.Arguments.Count == 3 && call.Arguments[2] is LdStr: + case "AppendFormatted" when call.Arguments.Count == 3 && call.Arguments[2] is LdcI4: + case "AppendFormatted" when call.Arguments.Count == 4 && call.Arguments[2] is LdcI4 && call.Arguments[3] is LdStr: + break; + default: + return false; + } + return true; + } + + private bool FindToStringAndClear(Block block, int pos, int interpolationStart, int interpolationEnd, ILVariable v, out ILInstruction insertionPoint) + { + insertionPoint = null; + if (pos >= block.Instructions.Count) + return false; + // find + // ... call ToStringAndClear(ldloca v) ... + // in block.Instructions[pos] + for (int i = interpolationStart; i < interpolationEnd; i++) + { + var result = ILInlining.FindLoadInNext(block.Instructions[pos], v, block.Instructions[i], InliningOptions.None); + if (result.Type != ILInlining.FindResultType.Found) + return false; + insertionPoint ??= result.LoadInst.Parent; + Debug.Assert(insertionPoint == result.LoadInst.Parent); + } + + return insertionPoint is Call + { + Arguments: { Count: 1 }, + Method: { Name: "ToStringAndClear", IsStatic: false } + }; + } + } +} \ No newline at end of file diff --git a/ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs b/ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs index 06ba9d6cd..1c8a31674 100644 --- a/ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs +++ b/ICSharpCode.Decompiler/TypeSystem/KnownTypeReference.cs @@ -137,6 +137,8 @@ namespace ICSharpCode.Decompiler.TypeSystem IFormattable, /// System.FormattableString FormattableString, + /// System.Runtime.CompilerServices.DefaultInterpolatedStringHandler + DefaultInterpolatedStringHandler, /// System.Span{T} SpanOfT, /// System.ReadOnlySpan{T} @@ -218,6 +220,7 @@ namespace ICSharpCode.Decompiler.TypeSystem new KnownTypeReference(KnownTypeCode.TypedReference, TypeKind.Struct, "System", "TypedReference"), new KnownTypeReference(KnownTypeCode.IFormattable, TypeKind.Interface, "System", "IFormattable"), new KnownTypeReference(KnownTypeCode.FormattableString, TypeKind.Class, "System", "FormattableString", baseType: KnownTypeCode.IFormattable), + new KnownTypeReference(KnownTypeCode.DefaultInterpolatedStringHandler, TypeKind.Struct, "System.Runtime.CompilerServices", "DefaultInterpolatedStringHandler"), new KnownTypeReference(KnownTypeCode.SpanOfT, TypeKind.Struct, "System", "Span", 1), new KnownTypeReference(KnownTypeCode.ReadOnlySpanOfT, TypeKind.Struct, "System", "ReadOnlySpan", 1), new KnownTypeReference(KnownTypeCode.MemoryOfT, TypeKind.Struct, "System", "Memory", 1),