From b0490dc45b0644b6008383f1a6897ded4abc7d82 Mon Sep 17 00:00:00 2001 From: Christoph Wille Date: Fri, 10 Oct 2025 07:07:00 +0200 Subject: [PATCH 1/3] Roslyn 5.0.0 --- Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 0341b7e9e..64faa2284 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -14,8 +14,8 @@ - - + + From 2f8f753a8c6b37d40dc2cf731fbf6dea462fc557 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 2 Nov 2025 08:49:01 +0100 Subject: [PATCH 2/3] Allow nullable annotations in ExtensionProperties test case --- ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index d4b62f34d..15dc35b8a 100644 --- a/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs +++ b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs @@ -562,7 +562,7 @@ namespace ICSharpCode.Decompiler.Tests [Test] public async Task ExtensionProperties([ValueSource(nameof(roslyn4OrNewerOptions))] CompilerOptions cscOptions) { - await RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview); + await RunForLibrary(cscOptions: cscOptions | CompilerOptions.Preview | CompilerOptions.NullableEnable); } [Test] From 396b58031ba0a40269fae743843468178f236685 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Fri, 21 Nov 2025 21:35:04 +0100 Subject: [PATCH 3/3] Add support for C# 14 first-class span types in the type system. --- .../Helpers/Tester.cs | 2 +- ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 2 +- .../CSharp/Resolver/CSharpConversions.cs | 174 ++++++++++++++++-- .../CSharp/Resolver/CSharpResolver.cs | 2 +- .../CSharp/Resolver/OverloadResolution.cs | 4 +- .../CSharp/Resolver/TypeInference.cs | 66 +++++-- ICSharpCode.Decompiler/DecompilerSettings.cs | 21 ++- .../Semantics/Conversion.cs | 22 +++ .../TypeSystem/DecompilerTypeSystem.cs | 26 ++- .../TypeSystem/ICompilation.cs | 2 + .../Implementation/SimpleCompilation.cs | 2 + ILSpy/Properties/Resources.Designer.cs | 11 +- ILSpy/Properties/Resources.resx | 3 + 13 files changed, 288 insertions(+), 49 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs index 6a9d9a06c..7317827da 100644 --- a/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs +++ b/ICSharpCode.Decompiler.Tests/Helpers/Tester.cs @@ -721,7 +721,7 @@ namespace System.Runtime.CompilerServices CompilerOptions.UseRoslyn1_3_2 => CSharp.LanguageVersion.CSharp6, CompilerOptions.UseRoslyn2_10_0 => CSharp.LanguageVersion.CSharp7_3, CompilerOptions.UseRoslyn3_11_0 => CSharp.LanguageVersion.CSharp9_0, - _ => cscOptions.HasFlag(CompilerOptions.Preview) ? CSharp.LanguageVersion.Latest : CSharp.LanguageVersion.CSharp13_0, + _ => cscOptions.HasFlag(CompilerOptions.Preview) ? CSharp.LanguageVersion.Latest : CSharp.LanguageVersion.CSharp14_0, }; DecompilerSettings settings = new(langVersion) { // Never use file-scoped namespaces diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 4eb81a8cd..8727f6d87 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -1438,7 +1438,7 @@ namespace ICSharpCode.Decompiler.CSharp var conversions = CSharpConversions.Get(expressionBuilder.compilation); IType targetType = method.ReturnType; var conv = conversions.ImplicitConversion(argument.Type, targetType); - if (!(conv.IsUserDefined && conv.IsValid && conv.Method.Equals(method))) + if (!(conv.IsUserDefined && conv.IsValid && conv.Method.Equals(method, NormalizeTypeVisitor.TypeErasure))) { // implicit conversion to targetType isn't directly possible, so first insert a cast to the argument type argument = argument.ConvertTo(method.Parameters[0].Type, expressionBuilder); diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs index 056ef2d54..044c61487 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs @@ -247,6 +247,10 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver if (IdentityConversion(elementType, spanElementType)) return Conversion.InlineArrayConversion; } + if (IsImplicitSpanConversion(fromType, toType)) + { + return Conversion.ImplicitSpanConversion; + } return Conversion.None; } @@ -1229,6 +1233,49 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver } #endregion + #region Implicit Span Conversion + + bool IsImplicitSpanConversion(IType fromType, IType toType) + { + if (!compilation.TypeSystemOptions.HasFlag(TypeSystemOptions.FirstClassSpanTypes)) + { + return false; + } + + // An implicit span conversion permits array_types, System.Span, System.ReadOnlySpan, + // and string to be converted between each other + // see https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-14.0/first-class-span-types#span-conversions + + switch (fromType) + { + case ArrayType { Dimensions: 1, ElementType: var elementType }: + if (toType.IsKnownType(KnownTypeCode.SpanOfT)) + { + return IdentityConversion(elementType, toType.TypeArguments[0]); + } + if (toType.IsKnownType(KnownTypeCode.ReadOnlySpanOfT)) + { + return IdentityConversion(elementType, toType.TypeArguments[0]) + || IsImplicitReferenceConversion(elementType, toType.TypeArguments[0]); + } + break; + case ParameterizedType pt when pt.IsKnownType(KnownTypeCode.SpanOfT) || pt.IsKnownType(KnownTypeCode.ReadOnlySpanOfT): + if (toType.IsKnownType(KnownTypeCode.ReadOnlySpanOfT)) + { + return IdentityConversion(pt.TypeArguments[0], toType.TypeArguments[0]) + || IsImplicitReferenceConversion(pt.TypeArguments[0], toType.TypeArguments[0]); + } + break; + case var s when s.IsKnownType(KnownTypeCode.String): + return toType.IsKnownType(KnownTypeCode.ReadOnlySpanOfT) + && toType.TypeArguments[0].IsKnownType(KnownTypeCode.Char); + } + + return false; + } + + #endregion + #region AnonymousFunctionConversion Conversion AnonymousFunctionConversion(ResolveResult resolveResult, IType toType) { @@ -1487,11 +1534,32 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver #region BetterConversion /// - /// Gets the better conversion (C# 4.0 spec, §7.5.3.3) + /// Gets the better conversion (from expression) (C# 8.0 spec, §12.6.4.5) /// /// 0 = neither is better; 1 = t1 is better; 2 = t2 is better public int BetterConversion(ResolveResult resolveResult, IType t1, IType t2) { + bool t1Exact = IsExactlyMatching(resolveResult, t1); + bool t2Exact = IsExactlyMatching(resolveResult, t2); + if (t1Exact && !t2Exact) + return 1; + if (t2Exact && !t1Exact) + return 2; + if (!t1Exact && !t2Exact) + { + bool c1ImplicitSpanConversion = IsImplicitSpanConversion(resolveResult.Type, t1); + bool c2ImplicitSpanConversion = IsImplicitSpanConversion(resolveResult.Type, t2); + if (c1ImplicitSpanConversion && !c2ImplicitSpanConversion) + return 1; + if (c2ImplicitSpanConversion && !c1ImplicitSpanConversion) + return 2; + } + if (t1Exact == t2Exact) + { + int r = BetterConversionTarget(t1, t2); + if (r != 0) + return r; + } LambdaResolveResult lambda = resolveResult as LambdaResolveResult; if (lambda != null) { @@ -1542,20 +1610,56 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver } /// - /// Unpacks the generic Task[T]. Returns null if the input is not Task[T]. + /// Gets whether an expression E exactly matches a type T (C# 8.0 spec, §12.6.4.6) /// - static IType UnpackTask(IType type) + bool IsExactlyMatching(ResolveResult e, IType t) { - ParameterizedType pt = type as ParameterizedType; - if (pt != null && pt.TypeParameterCount == 1 && pt.Name == "Task" && pt.Namespace == "System.Threading.Tasks") + var s = e.Type; + if (IdentityConversion(s, t)) + return true; + if (e is LambdaResolveResult lambda) { - return pt.GetTypeArgument(0); + if (!lambda.IsAnonymousMethod) + { + t = UnpackExpressionTreeType(t); + } + IMethod m = t.GetDelegateInvokeMethod(); + if (m == null) + return false; + IType[] parameterTypes = new IType[m.Parameters.Count]; + for (int i = 0; i < parameterTypes.Length; i++) + parameterTypes[i] = m.Parameters[i].Type; + var x = lambda.GetInferredReturnType(parameterTypes); + var y = m.ReturnType; + if (IdentityConversion(x, y)) + return true; + if (lambda.IsAsync) + { + x = UnpackTask(x); + y = UnpackTask(y); + } + if (x != null && y != null) + return IsExactlyMatching(new ResolveResult(x), y); + return false; + } + else + { + return false; } - return null; } /// - /// Gets the better conversion (C# 4.0 spec, §7.5.3.4) + /// Unpacks the generic TaskType[T]. Returns null if the input is not TaskType[T]. + /// + static IType UnpackTask(IType type) + { + return (TaskType.IsTask(type) || TaskType.IsCustomTask(type, out _)) && type.TypeParameterCount == 1 + ? type.TypeArguments[0] + : null; + } + + /// + /// Gets the better conversion (from type) (C# 4.0 spec, §7.5.3.4) /// /// 0 = neither is better; 1 = t1 is better; 2 = t2 is better public int BetterConversion(IType s, IType t1, IType t2) @@ -1570,17 +1674,57 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver } /// - /// Gets the better conversion target (C# 4.0 spec, §7.5.3.5) + /// Gets the better conversion target (C# 9.0 spec, §12.6.4.7) /// /// 0 = neither is better; 1 = t1 is better; 2 = t2 is better int BetterConversionTarget(IType t1, IType t2) { - bool t1To2 = ImplicitConversion(t1, t2).IsValid; - bool t2To1 = ImplicitConversion(t2, t1).IsValid; - if (t1To2 && !t2To1) - return 1; - if (t2To1 && !t1To2) - return 2; + if (t1.IsKnownType(KnownTypeCode.ReadOnlySpanOfT)) + { + if (t2.IsKnownType(KnownTypeCode.SpanOfT)) + { + if (IdentityConversion(t1.TypeArguments[0], t2.TypeArguments[0])) + return 1; + } + if (t2.IsKnownType(KnownTypeCode.ReadOnlySpanOfT)) + { + bool t1To2 = ImplicitConversion(t1.TypeArguments[0], t2.TypeArguments[0]).IsValid; + bool t2To1 = ImplicitConversion(t2.TypeArguments[0], t1.TypeArguments[0]).IsValid; + if (t1To2 && !t2To1) + return 1; + } + } + + if (t2.IsKnownType(KnownTypeCode.ReadOnlySpanOfT)) + { + if (t1.IsKnownType(KnownTypeCode.SpanOfT)) + { + if (IdentityConversion(t2.TypeArguments[0], t1.TypeArguments[0])) + return 2; + } + if (t1.IsKnownType(KnownTypeCode.ReadOnlySpanOfT)) + { + bool t1To2 = ImplicitConversion(t1.TypeArguments[0], t2.TypeArguments[0]).IsValid; + bool t2To1 = ImplicitConversion(t2.TypeArguments[0], t1.TypeArguments[0]).IsValid; + if (t2To1 && !t1To2) + return 2; + } + } + + { + bool t1To2 = ImplicitConversion(t1, t2).IsValid; + bool t2To1 = ImplicitConversion(t2, t1).IsValid; + if (t1To2 && !t2To1) + return 1; + if (t2To1 && !t1To2) + return 2; + } + + var s1 = UnpackTask(t1); + var s2 = UnpackTask(t2); + if (s1 != null && s2 != null) + return BetterConversionTarget(s1, s2); + TypeCode t1Code = ReflectionHelper.GetTypeCode(t1); TypeCode t2Code = ReflectionHelper.GetTypeCode(t2); if (IsBetterIntegralType(t1Code, t2Code)) diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs index 1f69df969..d3a49fbc9 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs @@ -2171,7 +2171,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver thisParameterType = thisParameterType.AcceptVisitor(substitution); } Conversion c = conversions.ImplicitConversion(targetType, thisParameterType); - return c.IsValid && (c.IsIdentityConversion || c.IsReferenceConversion || c.IsBoxingConversion); + return c.IsValid && (c.IsIdentityConversion || c.IsReferenceConversion || c.IsBoxingConversion || c.IsImplicitSpanConversion); } /// diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs b/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs index 7e562179c..8e77d3133 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs @@ -710,8 +710,8 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver candidate.ArgumentConversions[i] = c; if (IsExtensionMethodInvocation && parameterIndex == 0) { - // First parameter to extension method must be an identity, reference or boxing conversion - if (!(c == Conversion.IdentityConversion || c == Conversion.ImplicitReferenceConversion || c == Conversion.BoxingConversion)) + // First parameter to extension method must be an identity, reference, boxing or span conversion + if (!(c == Conversion.IdentityConversion || c == Conversion.ImplicitReferenceConversion || c == Conversion.BoxingConversion || c == Conversion.ImplicitSpanConversion)) candidate.AddError(OverloadResolutionErrors.ArgumentTypeMismatch); } else diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs b/ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs index 9ce37eff1..f90d45200 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs @@ -659,17 +659,31 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver return; } // Handle array types: - ArrayType arrU = U as ArrayType; - ArrayType arrV = V as ArrayType; - if (arrU != null && arrV != null && arrU.Dimensions == arrV.Dimensions) + U = U.TupleUnderlyingTypeOrSelf(); + V = V.TupleUnderlyingTypeOrSelf(); + switch ((U, V)) { - MakeExactInference(arrU.ElementType, arrV.ElementType); - return; + case (ArrayType arrU, ArrayType arrV) when arrU.Dimensions == arrV.Dimensions: + MakeExactInference(arrU.ElementType, arrV.ElementType); + return; + case (ArrayType arrU, ParameterizedType spanV) when compilation.TypeSystemOptions.HasFlag(TypeSystemOptions.FirstClassSpanTypes) && spanV.IsKnownType(KnownTypeCode.SpanOfT): + MakeExactInference(arrU.ElementType, spanV.TypeArguments[0]); + return; + case (ParameterizedType spanU, ParameterizedType spanV) when compilation.TypeSystemOptions.HasFlag(TypeSystemOptions.FirstClassSpanTypes) && spanU.IsKnownType(KnownTypeCode.SpanOfT) && spanV.IsKnownType(KnownTypeCode.SpanOfT): + MakeExactInference(spanU.TypeArguments[0], spanV.TypeArguments[0]); + return; + case (ArrayType arrU, ParameterizedType rosV) when compilation.TypeSystemOptions.HasFlag(TypeSystemOptions.FirstClassSpanTypes) && rosV.IsKnownType(KnownTypeCode.ReadOnlySpanOfT): + MakeExactInference(arrU.ElementType, rosV.TypeArguments[0]); + return; + case (ParameterizedType spanU, ParameterizedType rosV) when compilation.TypeSystemOptions.HasFlag(TypeSystemOptions.FirstClassSpanTypes) && spanU.IsKnownType(KnownTypeCode.SpanOfT) && rosV.IsKnownType(KnownTypeCode.ReadOnlySpanOfT): + MakeExactInference(spanU.TypeArguments[0], rosV.TypeArguments[0]); + return; + case (ParameterizedType rosU, ParameterizedType rosV) when compilation.TypeSystemOptions.HasFlag(TypeSystemOptions.FirstClassSpanTypes) && rosU.IsKnownType(KnownTypeCode.ReadOnlySpanOfT) && rosV.IsKnownType(KnownTypeCode.ReadOnlySpanOfT): + MakeExactInference(rosU.TypeArguments[0], rosV.TypeArguments[0]); + return; } // Handle parameterized type: - ParameterizedType pU = U.TupleUnderlyingTypeOrSelf() as ParameterizedType; - ParameterizedType pV = V.TupleUnderlyingTypeOrSelf() as ParameterizedType; - if (pU != null && pV != null + if (U is ParameterizedType pU && V is ParameterizedType pV && object.Equals(pU.GenericType, pV.GenericType) && pU.TypeParameterCount == pV.TypeParameterCount) { @@ -751,21 +765,33 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver return; } // Handle array types: - ArrayType arrU = U as ArrayType; - ArrayType arrV = V as ArrayType; - ParameterizedType pV = V.TupleUnderlyingTypeOrSelf() as ParameterizedType; - if (arrU != null && arrV != null && arrU.Dimensions == arrV.Dimensions) + V = V.TupleUnderlyingTypeOrSelf(); + switch ((U, V)) { - MakeLowerBoundInference(arrU.ElementType, arrV.ElementType); - return; - } - else if (arrU != null && pV.IsArrayInterfaceType() && arrU.Dimensions == 1) - { - MakeLowerBoundInference(arrU.ElementType, pV.GetTypeArgument(0)); - return; + case (ArrayType arrU, ArrayType arrV) when arrU.Dimensions == arrV.Dimensions: + MakeLowerBoundInference(arrU.ElementType, arrV.ElementType); + return; + case (ArrayType arrU, ParameterizedType spanV) when compilation.TypeSystemOptions.HasFlag(TypeSystemOptions.FirstClassSpanTypes) && spanV.IsKnownType(KnownTypeCode.SpanOfT): + MakeLowerBoundInference(arrU.ElementType, spanV.TypeArguments[0]); + return; + case (ParameterizedType spanU, ParameterizedType spanV) when compilation.TypeSystemOptions.HasFlag(TypeSystemOptions.FirstClassSpanTypes) && spanU.IsKnownType(KnownTypeCode.SpanOfT) && spanV.IsKnownType(KnownTypeCode.SpanOfT): + MakeLowerBoundInference(spanU.TypeArguments[0], spanV.TypeArguments[0]); + return; + case (ArrayType arrU, ParameterizedType rosV) when compilation.TypeSystemOptions.HasFlag(TypeSystemOptions.FirstClassSpanTypes) && rosV.IsKnownType(KnownTypeCode.ReadOnlySpanOfT): + MakeLowerBoundInference(arrU.ElementType, rosV.TypeArguments[0]); + return; + case (ParameterizedType spanU, ParameterizedType rosV) when compilation.TypeSystemOptions.HasFlag(TypeSystemOptions.FirstClassSpanTypes) && spanU.IsKnownType(KnownTypeCode.SpanOfT) && rosV.IsKnownType(KnownTypeCode.ReadOnlySpanOfT): + MakeLowerBoundInference(spanU.TypeArguments[0], rosV.TypeArguments[0]); + return; + case (ParameterizedType rosU, ParameterizedType rosV) when compilation.TypeSystemOptions.HasFlag(TypeSystemOptions.FirstClassSpanTypes) && rosU.IsKnownType(KnownTypeCode.ReadOnlySpanOfT) && rosV.IsKnownType(KnownTypeCode.ReadOnlySpanOfT): + MakeLowerBoundInference(rosU.TypeArguments[0], rosV.TypeArguments[0]); + return; + case (ArrayType arrU, ParameterizedType arrIntfV) when arrIntfV.IsArrayInterfaceType() && arrU.Dimensions == 1: + MakeLowerBoundInference(arrU.ElementType, arrIntfV.TypeArguments[0]); + return; } // Handle parameterized types: - if (pV != null) + if (V is ParameterizedType pV) { ParameterizedType uniqueBaseType = null; foreach (IType baseU in U.GetAllBaseTypes()) diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs index 79055229d..8853717ab 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -175,12 +175,13 @@ namespace ICSharpCode.Decompiler if (languageVersion < CSharp.LanguageVersion.CSharp14_0) { extensionMembers = false; + firstClassSpanTypes = false; } } public CSharp.LanguageVersion GetMinimumRequiredVersion() { - if (extensionMembers) + if (extensionMembers || firstClassSpanTypes) return CSharp.LanguageVersion.CSharp14_0; if (paramsCollections) return CSharp.LanguageVersion.CSharp13_0; @@ -2179,6 +2180,24 @@ namespace ICSharpCode.Decompiler } } + bool firstClassSpanTypes = true; + + /// + /// Gets/Sets whether (ReadOnly)Span<T> should be treated like built-in types. + /// + [Category("C# 14.0 / VS 202x.yy")] + [Description("DecompilerSettings.FirstClassSpanTypes")] + public bool FirstClassSpanTypes { + get { return firstClassSpanTypes; } + set { + if (firstClassSpanTypes != value) + { + firstClassSpanTypes = value; + OnPropertyChanged(); + } + } + } + bool separateLocalVariableDeclarations = false; /// diff --git a/ICSharpCode.Decompiler/Semantics/Conversion.cs b/ICSharpCode.Decompiler/Semantics/Conversion.cs index e4adbe746..fe94ce022 100644 --- a/ICSharpCode.Decompiler/Semantics/Conversion.cs +++ b/ICSharpCode.Decompiler/Semantics/Conversion.cs @@ -92,6 +92,11 @@ namespace ICSharpCode.Decompiler.Semantics /// public static readonly Conversion InlineArrayConversion = new BuiltinConversion(true, 12); + /// + /// C# 14 implicit span conversion from an array type to or . + /// + public static readonly Conversion ImplicitSpanConversion = new BuiltinConversion(true, 13); + public static Conversion UserDefinedConversion(IMethod operatorMethod, bool isImplicit, Conversion conversionBeforeUserDefinedOperator, Conversion conversionAfterUserDefinedOperator, bool isLifted = false, bool isAmbiguous = false) { if (operatorMethod == null) @@ -250,6 +255,9 @@ namespace ICSharpCode.Decompiler.Semantics get { return type == 11; } } + public override bool IsInlineArrayConversion => type == 12; + public override bool IsImplicitSpanConversion => type == 13; + public override string ToString() { string name = null; @@ -284,6 +292,10 @@ namespace ICSharpCode.Decompiler.Semantics return "interpolated string"; case 11: return "throw-expression conversion"; + case 12: + return "inline array conversion"; + case 13: + return "implicit span conversion"; } return (isImplicit ? "implicit " : "explicit ") + name + " conversion"; } @@ -621,6 +633,16 @@ namespace ICSharpCode.Decompiler.Semantics /// public virtual bool IsInterpolatedStringConversion => false; + /// + /// Gets whether this is an inline array conversion to or . + /// + public virtual bool IsInlineArrayConversion => false; + + /// + /// Gets whether this is an implicit span conversion from an array type to or . + /// + public virtual bool IsImplicitSpanConversion => false; + /// /// For a tuple conversion, gets the individual tuple element conversions. /// diff --git a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs index bc97dc56a..697f64b9d 100644 --- a/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs +++ b/ICSharpCode.Decompiler/TypeSystem/DecompilerTypeSystem.cs @@ -145,12 +145,17 @@ namespace ICSharpCode.Decompiler.TypeSystem /// ParamsCollections = 0x20000, /// - /// Default settings: typical options for the decompiler, with all C# languages features enabled. + /// If this option is active, span types (Span<T> and ReadOnlySpan<T>) are treated like + /// built-in types and language rules of C# 14 and later are applied. + /// + FirstClassSpanTypes = 0x40000, + /// + /// Default settings: typical options for the decompiler, with all C# language features enabled. /// Default = Dynamic | Tuple | ExtensionMethods | DecimalConstants | ReadOnlyStructsAndParameters | RefStructs | UnmanagedConstraints | NullabilityAnnotations | ReadOnlyMethods | NativeIntegers | FunctionPointers | ScopedRef | NativeIntegersWithoutAttribute - | RefReadOnlyParameters | ParamsCollections + | RefReadOnlyParameters | ParamsCollections | FirstClassSpanTypes } /// @@ -194,6 +199,8 @@ namespace ICSharpCode.Decompiler.TypeSystem typeSystemOptions |= TypeSystemOptions.RefReadOnlyParameters; if (settings.ParamsCollections) typeSystemOptions |= TypeSystemOptions.ParamsCollections; + if (settings.FirstClassSpanTypes) + typeSystemOptions |= TypeSystemOptions.FirstClassSpanTypes; return typeSystemOptions; } @@ -213,16 +220,18 @@ namespace ICSharpCode.Decompiler.TypeSystem throw new ArgumentNullException(nameof(mainModule)); if (assemblyResolver == null) throw new ArgumentNullException(nameof(assemblyResolver)); - var ts = new DecompilerTypeSystem(); - await ts.InitializeAsync(mainModule, assemblyResolver, typeSystemOptions) + var ts = new DecompilerTypeSystem(typeSystemOptions); + await ts.InitializeAsync(mainModule, assemblyResolver) .ConfigureAwait(false); return ts; } private MetadataModule mainModule; + private TypeSystemOptions typeSystemOptions; - private DecompilerTypeSystem() + private DecompilerTypeSystem(TypeSystemOptions typeSystemOptions) { + this.typeSystemOptions = typeSystemOptions; } public DecompilerTypeSystem(MetadataFile mainModule, IAssemblyResolver assemblyResolver) @@ -236,12 +245,13 @@ namespace ICSharpCode.Decompiler.TypeSystem } public DecompilerTypeSystem(MetadataFile mainModule, IAssemblyResolver assemblyResolver, TypeSystemOptions typeSystemOptions) + : this(typeSystemOptions) { if (mainModule == null) throw new ArgumentNullException(nameof(mainModule)); if (assemblyResolver == null) throw new ArgumentNullException(nameof(assemblyResolver)); - InitializeAsync(mainModule, assemblyResolver, typeSystemOptions).GetAwaiter().GetResult(); + InitializeAsync(mainModule, assemblyResolver).GetAwaiter().GetResult(); } static readonly string[] implicitReferences = new[] { @@ -249,7 +259,7 @@ namespace ICSharpCode.Decompiler.TypeSystem "System.Runtime.CompilerServices.Unsafe" }; - private async Task InitializeAsync(MetadataFile mainModule, IAssemblyResolver assemblyResolver, TypeSystemOptions typeSystemOptions) + private async Task InitializeAsync(MetadataFile mainModule, IAssemblyResolver assemblyResolver) { // Load referenced assemblies and type-forwarder references. // This is necessary to make .NET Core/PCL binaries work better. @@ -410,5 +420,7 @@ namespace ICSharpCode.Decompiler.TypeSystem } public new MetadataModule MainModule => mainModule; + + public override TypeSystemOptions TypeSystemOptions => typeSystemOptions; } } diff --git a/ICSharpCode.Decompiler/TypeSystem/ICompilation.cs b/ICSharpCode.Decompiler/TypeSystem/ICompilation.cs index d350f33b3..344e92a58 100644 --- a/ICSharpCode.Decompiler/TypeSystem/ICompilation.cs +++ b/ICSharpCode.Decompiler/TypeSystem/ICompilation.cs @@ -76,6 +76,8 @@ namespace ICSharpCode.Decompiler.TypeSystem StringComparer NameComparer { get; } CacheManager CacheManager { get; } + + TypeSystemOptions TypeSystemOptions { get; } } public interface ICompilationProvider diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/SimpleCompilation.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/SimpleCompilation.cs index fd7a217a2..0a30f0273 100644 --- a/ICSharpCode.Decompiler/TypeSystem/Implementation/SimpleCompilation.cs +++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/SimpleCompilation.cs @@ -157,6 +157,8 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation get { return StringComparer.Ordinal; } } + public virtual TypeSystemOptions TypeSystemOptions => TypeSystemOptions.Default; + public override string ToString() { return "[" + GetType().Name + " " + mainModule.AssemblyName + "]"; diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs index 322b94689..9e354a1b2 100644 --- a/ILSpy/Properties/Resources.Designer.cs +++ b/ILSpy/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace ICSharpCode.ILSpy.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] public class Resources { @@ -1053,6 +1053,15 @@ namespace ICSharpCode.ILSpy.Properties { } } + /// + /// Looks up a localized string similar to Treat Span<T> and ReadOnlySpan<T> as built-in types. + /// + public static string DecompilerSettings_FirstClassSpanTypes { + get { + return ResourceManager.GetString("DecompilerSettings.FirstClassSpanTypes", resourceCulture); + } + } + /// /// Looks up a localized string similar to Transform to for, if possible. /// diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx index 29c73bb9c..541c3bac1 100644 --- a/ILSpy/Properties/Resources.resx +++ b/ILSpy/Properties/Resources.resx @@ -375,6 +375,9 @@ Are you sure you want to continue? Use file-scoped namespace declarations + + Treat Span<T> and ReadOnlySpan<T> as built-in types + Transform to for, if possible