From 382ee0fcadd9088d2abf6ce3b52535a930db7f95 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 30 Nov 2025 17:57:03 +0000 Subject: [PATCH] Merge latest master with firstClassSpanTypes feature Co-authored-by: christophwille <344208+christophwille@users.noreply.github.com> --- Directory.Packages.props | 4 +- .../Helpers/Tester.cs | 2 +- .../PrettyTestRunner.cs | 2 +- .../TestCases/Pretty/NamedArguments.cs | 28 +++ .../CSharp/CSharpDecompiler.cs | 2 +- ICSharpCode.Decompiler/CSharp/CallBuilder.cs | 74 +++++--- .../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 + 17 files changed, 368 insertions(+), 77 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 @@ - - + + 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.Tests/PrettyTestRunner.cs b/ICSharpCode.Decompiler.Tests/PrettyTestRunner.cs index 691344dab..2baec77fb 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] diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NamedArguments.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NamedArguments.cs index 8b2281051..293d23e04 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NamedArguments.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/NamedArguments.cs @@ -32,6 +32,34 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } + private class MustNotUseNamedArgsInCtor + { + public MustNotUseNamedArgsInCtor(string start = "", bool enable = false) + { + } + + public MustNotUseNamedArgsInCtor(bool enable, string start = "") + { + } + + public static MustNotUseNamedArgsInCtor Use() + { + // second overload + MustNotUseNamedArgsInCall(true); + // first overload + MustNotUseNamedArgsInCall(); + return new MustNotUseNamedArgsInCtor(true); + } + + public static void MustNotUseNamedArgsInCall(string start = "", bool enable = false) + { + } + + public static void MustNotUseNamedArgsInCall(bool enable, string start = "") + { + } + } + public void Use(int a, int b, int c) { } diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs index 29cca7526..9fa3c9f5f 100644 --- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs @@ -1310,7 +1310,7 @@ namespace ICSharpCode.Decompiler.CSharp decompileRun.RecordDecompilers.Add(typeDef, recordDecompiler); // With C# 9 records, the relative order of fields and properties matters: - IEnumerable fieldsAndProperties = typeDef.IsRecord + IEnumerable fieldsAndProperties = isRecord ? recordDecompiler.FieldsAndProperties : typeDef.Fields.Concat(typeDef.Properties); diff --git a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs index 4eb81a8cd..82977db20 100644 --- a/ICSharpCode.Decompiler/CSharp/CallBuilder.cs +++ b/ICSharpCode.Decompiler/CSharp/CallBuilder.cs @@ -64,6 +64,30 @@ namespace ICSharpCode.Decompiler.CSharp return FirstOptionalArgumentIndex; } + public string[] GetArgumentNames(int skipCount = 0) + { + string[] argumentNames = ArgumentNames; + if (AddNamesToPrimitiveValues && IsPrimitiveValue.Any() && !IsExpandedForm + && !ParameterNames.Any(string.IsNullOrEmpty)) + { + Debug.Assert(skipCount == 0); + if (argumentNames == null) + { + argumentNames = new string[Arguments.Length]; + } + + for (int i = 0; i < Arguments.Length; i++) + { + if (IsPrimitiveValue[i] && argumentNames[i] == null) + { + argumentNames[i] = ParameterNames[i]; + } + } + } + + return argumentNames; + } + public IList GetArgumentResolveResults(int skipCount = 0) { var expectedParameters = ExpectedParameters; @@ -95,33 +119,17 @@ namespace ICSharpCode.Decompiler.CSharp public IEnumerable GetArgumentExpressions(int skipCount = 0) { - if (AddNamesToPrimitiveValues && IsPrimitiveValue.Any() && !IsExpandedForm - && !ParameterNames.Any(p => string.IsNullOrEmpty(p))) - { - Debug.Assert(skipCount == 0); - if (ArgumentNames == null) - { - ArgumentNames = new string[Arguments.Length]; - } - - for (int i = 0; i < Arguments.Length; i++) - { - if (IsPrimitiveValue[i] && ArgumentNames[i] == null) - { - ArgumentNames[i] = ParameterNames[i]; - } - } - } + var argumentNames = GetArgumentNames(skipCount); int argumentCount = GetActualArgumentCount(); var useImplicitlyTypedOut = UseImplicitlyTypedOut; - if (ArgumentNames == null) + if (argumentNames == null) { return Arguments.Skip(skipCount).Take(argumentCount).Select(arg => AddAnnotations(arg.Expression)); } else { Debug.Assert(skipCount == 0); - return Arguments.Take(argumentCount).Zip(ArgumentNames.Take(argumentCount), + return Arguments.Take(argumentCount).Zip(argumentNames.Take(argumentCount), (arg, name) => { if (name == null) return AddAnnotations(arg.Expression); @@ -531,6 +539,10 @@ namespace ICSharpCode.Decompiler.CSharp Expression targetExpr; string methodName = method.Name; AstNodeCollection typeArgumentList; + if ((transform & CallTransformation.NoNamedArgsForPrettiness) != 0) + { + argumentList.AddNamesToPrimitiveValues = false; + } if ((transform & CallTransformation.NoOptionalArgumentAllowed) != 0) { argumentList.FirstOptionalArgumentIndex = -1; @@ -674,7 +686,7 @@ namespace ICSharpCode.Decompiler.CSharp argumentList.UseImplicitlyTypedOut = false; var transform = GetRequiredTransformationsForCall(expectedTargetDetails, method, ref unused, ref argumentList, CallTransformation.None, out _); - Debug.Assert(transform == CallTransformation.None || transform == CallTransformation.NoOptionalArgumentAllowed); + Debug.Assert((transform & ~(CallTransformation.NoOptionalArgumentAllowed | CallTransformation.NoNamedArgsForPrettiness)) == 0); // Calls with only one argument do not need an array initializer expression to wrap them. // Any special cases are handled by the caller (i.e., ExpressionBuilder.TranslateObjectAndCollectionInitializer) @@ -1121,7 +1133,8 @@ namespace ICSharpCode.Decompiler.CSharp /// Add calls to AsRefReadOnly for in parameters that did not have an explicit DirectionExpression yet. /// EnforceExplicitIn = 8, - All = 0xf, + NoNamedArgsForPrettiness = 0x10, + All = 0x1f, } private CallTransformation GetRequiredTransformationsForCall(ExpectedTargetDetails expectedTargetDetails, IMethod method, @@ -1198,7 +1211,7 @@ namespace ICSharpCode.Decompiler.CSharp bool skipTargetCast = method.Accessibility <= Accessibility.Protected && expressionBuilder.IsBaseTypeOfCurrentType(method.DeclaringTypeDefinition); OverloadResolutionErrors errors; while ((errors = IsUnambiguousCall(expectedTargetDetails, method, targetResolveResult, typeArguments, - argumentList.GetArgumentResolveResults().ToArray(), argumentList.ArgumentNames, argumentList.FirstOptionalArgumentIndex, out foundMethod, + argumentList.GetArgumentResolveResults().ToArray(), argumentList.GetArgumentNames(), argumentList.FirstOptionalArgumentIndex, out foundMethod, out var bestCandidateIsExpandedForm)) != OverloadResolutionErrors.None || bestCandidateIsExpandedForm != argumentList.IsExpandedForm) { switch (errors) @@ -1228,7 +1241,11 @@ namespace ICSharpCode.Decompiler.CSharp default: // TODO : implement some more intelligent algorithm that decides which of these fixes (cast args, add target, cast target, add type args) // is best in this case. Additionally we should not cast all arguments at once, but step-by-step try to add only a minimal number of casts. - if (argumentList.FirstOptionalArgumentIndex >= 0) + if (argumentList.AddNamesToPrimitiveValues) + { + argumentList.AddNamesToPrimitiveValues = false; + } + else if (argumentList.FirstOptionalArgumentIndex >= 0) { argumentList.FirstOptionalArgumentIndex = -1; } @@ -1293,6 +1310,8 @@ namespace ICSharpCode.Decompiler.CSharp transform |= CallTransformation.RequireTypeArguments; if (argumentList.FirstOptionalArgumentIndex < 0) transform |= CallTransformation.NoOptionalArgumentAllowed; + if (!argumentList.AddNamesToPrimitiveValues) + transform |= CallTransformation.NoNamedArgsForPrettiness; return transform; } @@ -1438,7 +1457,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); @@ -1766,9 +1785,14 @@ namespace ICSharpCode.Decompiler.CSharp { while (IsUnambiguousCall(expectedTargetDetails, method, null, Empty.Array, argumentList.GetArgumentResolveResults().ToArray(), - argumentList.ArgumentNames, argumentList.FirstOptionalArgumentIndex, out _, + argumentList.GetArgumentNames(), argumentList.FirstOptionalArgumentIndex, out _, out var bestCandidateIsExpandedForm) != OverloadResolutionErrors.None || bestCandidateIsExpandedForm != argumentList.IsExpandedForm) { + if (argumentList.AddNamesToPrimitiveValues) + { + argumentList.AddNamesToPrimitiveValues = false; + continue; + } if (argumentList.FirstOptionalArgumentIndex >= 0) { argumentList.FirstOptionalArgumentIndex = -1; 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 b6a8df47a..1e890fef4 100644 --- a/ICSharpCode.Decompiler/DecompilerSettings.cs +++ b/ICSharpCode.Decompiler/DecompilerSettings.cs @@ -175,13 +175,14 @@ namespace ICSharpCode.Decompiler if (languageVersion < CSharp.LanguageVersion.CSharp14_0) { extensionMembers = false; + firstClassSpanTypes = false; semiAutoProperties = false; } } public CSharp.LanguageVersion GetMinimumRequiredVersion() { - if (extensionMembers || semiAutoProperties) + if (extensionMembers || firstClassSpanTypes || semiAutoProperties) return CSharp.LanguageVersion.CSharp14_0; if (paramsCollections) return CSharp.LanguageVersion.CSharp13_0; @@ -2180,6 +2181,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 semiAutoProperties = true; /// 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 d70aac931..a0451fefd 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 859783cf8..08febabe2 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