From 997b50d26bacb5a4fefdaee42eee7f66ff3862bf Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 1 Aug 2026 08:48:42 +0200 Subject: [PATCH] Update resolver spec references to current C# standard numbering The resolver comments cited section numbers from the C# 4.0 spec (and a few from C# 9.0 drafts), which no longer match the published ECMA-334 standard. Renumber them against dotnet/csharpstandard draft-v11; every reference was checked against the actual section headings. The old 'better conversion from type' subclause (7.5.3.4) no longer exists as such and its rules live in 12.6.4.5-12.6.4.7, so that comment now says so instead of pointing at a dead number. Assisted-by: Claude:claude-fable-5:Claude Code --- .../CSharp/Resolver/CSharpConversions.cs | 39 +++++++-------- .../CSharp/Resolver/CSharpOperators.cs | 2 +- .../CSharp/Resolver/CSharpResolver.cs | 14 +++--- .../CSharp/Resolver/OverloadResolution.cs | 6 +-- .../CSharp/Resolver/TypeInference.cs | 47 +++++++++---------- 5 files changed, 54 insertions(+), 54 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs index 044c61487..832e4ab3c 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpConversions.cs @@ -165,7 +165,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver private Conversion ImplicitConversion(IType fromType, IType toType, bool allowUserDefined, bool allowTuple) { - // C# 4.0 spec: §6.1 + // C# spec (draft-v11): §10.2 Implicit conversions var c = StandardImplicitConversion(fromType, toType, allowTuple); if (c == Conversion.None && allowUserDefined) { @@ -234,7 +234,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver if (allowTupleConversion) { // TODO are tuple conversions really standard implicit conversions? - // the C# 9.0 spec doesn't list them as standard implicit conversions. + // The C# spec (draft-v11, §10.4.2) doesn't list them as standard implicit conversions. c = TupleConversion(fromType, toType, isExplicit: false); if (c != Conversion.None) return c; @@ -256,7 +256,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver /// /// Gets whether the type 'fromType' is convertible to 'toType' - /// using one of the conversions allowed when satisfying constraints (§4.4.4) + /// using one of the conversions allowed when satisfying constraints (§8.4.5) /// public bool IsConstraintConvertible(IType fromType, IType toType) { @@ -366,7 +366,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver /// public bool IdentityConversion(IType fromType, IType toType) { - // C# 4.0 spec: §6.1.1 + // C# spec (draft-v11): §10.2.2 Identity conversion fromType = fromType.AcceptVisitor(NormalizeTypeVisitor.TypeErasure); toType = toType.AcceptVisitor(NormalizeTypeVisitor.TypeErasure); return fromType.Equals(toType); @@ -450,7 +450,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver bool AnyNumericConversion(IType fromType, IType toType) { - // C# 4.0 spec: §6.1.2 + §6.2.1 + // C# spec (draft-v11): §10.2.3 + §10.3.2 (numeric conversions) return IsNumericType(fromType) && IsNumericType(toType); } #endregion @@ -473,7 +473,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver bool ExplicitEnumerationConversion(IType fromType, IType toType) { - // C# 4.0 spec: §6.2.2 + // C# spec (draft-v11): §10.3.3 Explicit enumeration conversions if (fromType.Kind == TypeKind.Enum) { return toType.Kind == TypeKind.Enum || IsNumericType(toType); @@ -504,7 +504,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver Conversion ExplicitNullableConversion(IType fromType, IType toType) { - // C# 4.0 spec: §6.1.4 + // C# spec (draft-v11): §10.3.4 Explicit nullable conversions if (NullableType.IsNullable(toType) || NullableType.IsNullable(fromType)) { IType t = NullableType.GetUnderlyingType(toType); @@ -638,7 +638,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver ParameterizedType pt = t as ParameterizedType; if (ps != null && pt != null) { - // C# 4.0 spec: §13.1.3.2 Variance Conversion + // C# spec (draft-v11): §19.2.3.3 Variance conversion for (int i = 0; i < def.TypeParameters.Count; i++) { IType si = ps.GetTypeArgument(i); @@ -678,7 +678,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver #region Explicit Reference Conversion bool ExplicitReferenceConversion(IType fromType, IType toType) { - // C# 4.0 spec: §6.2.4 + // C# spec (draft-v11): §10.3.5 Explicit reference conversions // test that the types are reference types: if (toType.IsReferenceType != true) @@ -811,7 +811,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver bool UnboxingConversion(IType fromType, IType toType) { - // C# 4.0 spec: §6.2.5 + // C# spec (draft-v11): §10.3.7 Unboxing conversions toType = NullableType.GetUnderlyingType(toType); if (fromType.IsReferenceType == true && toType.IsReferenceType == false) return IsSubtypeOf(toType, fromType, 0); @@ -898,7 +898,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver #region Pointer Conversions bool ImplicitPointerConversion(IType fromType, IType toType) { - // C# 4.0 spec: §18.4 Pointer conversions + // C# spec (draft-v11): §24.5 Pointer conversions if (fromType.Kind.IsAnyPointer() && toType is PointerType && toType.ReflectionName == "System.Void*") return true; if (fromType.Kind == TypeKind.Null && toType.Kind.IsAnyPointer()) @@ -929,7 +929,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver bool ExplicitPointerConversion(IType fromType, IType toType) { - // C# 4.0 spec: §18.4 Pointer conversions + // C# spec (draft-v11): §24.5 Pointer conversions if (fromType.Kind.IsAnyPointer()) { return toType.Kind.IsAnyPointer() || IsIntegerType(toType); @@ -1029,7 +1029,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver Conversion UserDefinedImplicitConversion(ResolveResult fromResult, IType fromType, IType toType) { - // C# 4.0 spec §6.4.4 User-defined implicit conversions + // C# spec (draft-v11): §10.5.4 User-defined implicit conversions // user-defined conversions are not supported with interfaces if (fromType.Kind == TypeKind.Interface || toType.Kind == TypeKind.Interface) @@ -1078,7 +1078,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver Conversion UserDefinedExplicitConversion(ResolveResult fromResult, IType fromType, IType toType) { - // C# 4.0 spec §6.4.5 User-defined explicit conversions + // C# spec (draft-v11): §10.5.5 User-defined explicit conversions // user-defined conversions are not supported with interfaces if (fromType.Kind == TypeKind.Interface || toType.Kind == TypeKind.Interface) @@ -1414,7 +1414,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver /// /// Gets whether a is compatible with a delegate type. - /// §15.2 Delegate compatibility + /// C# spec (draft-v11): §21.4 Delegate compatibility /// /// The method to test for compatibility /// The delegate type @@ -1439,7 +1439,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver /// If this parameter is true, the first parameter of will be ignored. bool IsDelegateCompatible(IMethod m, IMethod d, bool isExtensionMethodInvocation) { - // C# 9.0 §20.4 Delegate compatibility + // C# spec (draft-v11): §21.4 Delegate compatibility if (m == null) throw new ArgumentNullException(nameof(m)); if (d == null) @@ -1479,7 +1479,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver #region Tuple Conversion Conversion TupleConversion(TupleResolveResult fromRR, IType toType, bool isExplicit) { - // C# 9.0 spec: §10.2.13 (implicit tuple conversions) + $10.3.6 (explicit tuple conversions) + // C# 9.0 spec: §10.2.13 (implicit tuple conversions) + §10.3.6 (explicit tuple conversions) var fromElements = fromRR.Elements; var toElements = TupleType.GetTupleElementTypes(toType); if (toElements.IsDefault || fromElements.Length != toElements.Length) @@ -1505,7 +1505,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver Conversion TupleConversion(IType fromType, IType toType, bool isExplicit) { - // C# 9.0 spec: §10.2.13 (implicit tuple conversions) + $10.3.6 (explicit tuple conversions) + // C# 9.0 spec: §10.2.13 (implicit tuple conversions) + §10.3.6 (explicit tuple conversions) var fromElements = TupleType.GetTupleElementTypes(fromType); if (fromElements.IsDefaultOrEmpty) return Conversion.None; @@ -1659,7 +1659,8 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver } /// - /// Gets the better conversion (from type) (C# 4.0 spec, §7.5.3.4) + /// Gets the better conversion (from type) (C# 4.0 spec, §7.5.3.4; the current standard + /// no longer has this subclause, it was folded into §12.6.4.5-§12.6.4.7) /// /// 0 = neither is better; 1 = t1 is better; 2 = t2 is better public int BetterConversion(IType s, IType t1, IType t2) diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpOperators.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpOperators.cs index 9db00cbe9..34f11b7db 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpOperators.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpOperators.cs @@ -366,7 +366,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver } } - // C# 4.0 spec: §7.7.3 Logical negation operator + // C# spec (draft-v11): §12.9.4 Logical negation operator OperatorMethod[]? logicalNegationOperators; public OperatorMethod[] LogicalNegationOperators { diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs index d3a49fbc9..b54f309d6 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/CSharpResolver.cs @@ -337,7 +337,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver } } - // C# 4.0 spec: §7.3.3 Unary operator overload resolution + // C# spec (draft-v11): §12.4.4 Unary operator overload resolution string overloadableOperatorName = GetOverloadableOperatorName(op); if (overloadableOperatorName == null) { @@ -426,8 +426,8 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver case UnaryOperatorType.Decrement: case UnaryOperatorType.PostIncrement: case UnaryOperatorType.PostDecrement: - // C# 4.0 spec: §7.6.9 Postfix increment and decrement operators - // C# 4.0 spec: §7.7.5 Prefix increment and decrement operators + // C# spec (draft-v11): §12.8.16 Postfix increment and decrement operators + // C# spec (draft-v11): §12.9.7 Prefix increment and decrement operators TypeCode code = ReflectionHelper.GetTypeCode(type); if ((code >= TypeCode.Char && code <= TypeCode.Decimal) || type.Kind == TypeKind.Enum || type.Kind == TypeKind.Pointer || type.IsCSharpNativeIntegerType()) return UnaryOperatorResolveResult(expression.Type, op, expression, isNullable); @@ -535,7 +535,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver #region UnaryNumericPromotion ResolveResult UnaryNumericPromotion(UnaryOperatorType op, ref IType type, bool isNullable, ResolveResult expression) { - // C# 4.0 spec: §7.3.6.1 + // C# spec (draft-v11): §12.4.7.2 Unary numeric promotions TypeCode code = ReflectionHelper.GetTypeCode(type); if (isNullable && type.Kind == TypeKind.Null) code = TypeCode.SByte; // cause promotion of null to int32 @@ -1064,7 +1064,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver #region BinaryNumericPromotion bool BinaryNumericPromotion(bool isNullable, ref ResolveResult lhs, ref ResolveResult rhs, bool allowNullableConstants) { - // C# 4.0 spec: §7.3.6.2 + // C# spec (draft-v11): §12.4.7.3 Binary numeric promotions var lhsUType = NullableType.GetUnderlyingType(lhs.Type); var rhsUType = NullableType.GetUnderlyingType(rhs.Type); TypeCode lhsCode = ReflectionHelper.GetTypeCode(lhsUType); @@ -1288,7 +1288,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver // However, we must not use those as user-defined operators (we would skip numeric promotion). return EmptyList.Instance; } - // C# 4.0 spec: §7.3.5 Candidate user-defined operators + // C# spec (draft-v11): §12.4.6 Candidate user-defined operators var operators = type.GetMethods(m => m.IsOperator && m.Name == operatorName).ToList(); LiftUserDefinedOperators(operators); return operators; @@ -1396,7 +1396,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver public ResolveResult ResolveCast(IType targetType, ResolveResult expression) { - // C# 4.0 spec: §7.7.6 Cast expressions + // C# spec (draft-v11): §12.9.8 Cast expressions Conversion c = conversions.ExplicitConversion(expression, targetType); if (expression.IsCompileTimeConstant && !c.IsUserDefined) { diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs b/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs index 8e77d3133..53d972467 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/OverloadResolution.cs @@ -29,7 +29,7 @@ using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.CSharp.Resolver { /// - /// C# overload resolution (C# 4.0 spec: §7.5). + /// C# overload resolution (C# spec draft-v11: §12.6.4). /// public class OverloadResolution { @@ -390,8 +390,8 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver #region MapCorrespondingParameters void MapCorrespondingParameters(Candidate candidate) { - // C# 4.0 spec: §7.5.1.1 Corresponding parameters - // Updated for C# 7.2 non-trailing named arguments + // C# spec (draft-v11): §12.6.2.2 Corresponding parameters + // (includes the non-trailing named arguments rule from C# 7.2) candidate.ArgumentToParameterMap = new int[arguments.Length]; bool hasPositionalArgument = false; // go backwards, so that hasPositionalArgument tells us whether there diff --git a/ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs b/ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs index f90d45200..a46a0478e 100644 --- a/ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs +++ b/ICSharpCode.Decompiler/CSharp/Resolver/TypeInference.cs @@ -46,7 +46,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver } /// - /// Implements C# 4.0 Type Inference (§7.5.2). + /// Implements C# type inference (C# spec draft-v11: §12.6.3). /// public sealed class TypeInference { @@ -318,7 +318,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver bool PhaseTwo() { - // C# 4.0 spec: §7.5.2.2 The second phase + // C# spec (draft-v11): §12.6.3.3 The second phase Log.WriteLine("Phase Two"); // All unfixed type variables Xi which do not depend on any Xj are fixed. List typeParametersToFix = new List(); @@ -373,30 +373,30 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver } else { - // Otherwise, for all arguments ei with corresponding parameter type Ti + // Otherwise, for all arguments ei with corresponding parameter type Ti for (int i = 0; i < arguments.Length; i++) { ResolveResult Ei = arguments[i]; IType Ti = parameterTypes[i]; - // where the output types (§7.4.2.4) contain unfixed type variables Xj - // but the input types (§7.4.2.3) do not + // where the output types (§12.6.3.5) contain unfixed type variables Xj + // but the input types (§12.6.3.4) do not if (OutputTypeContainsUnfixed(Ei, Ti) && !InputTypesContainsUnfixed(Ei, Ti)) { - // an output type inference (§7.4.2.6) is made for ei with type Ti. + // an output type inference (§12.6.3.8) is made for ei with type Ti. Log.WriteLine("MakeOutputTypeInference for argument #" + i); MakeOutputTypeInference(Ei, Ti); } } - // Then the second phase is repeated. + // Then the second phase is repeated. return PhaseTwo(); } } #endregion - #region Input Types / Output Types (§7.5.2.3 + §7.5.2.4) + #region Input Types / Output Types (§12.6.3.4 + §12.6.3.5) IType[] InputTypes(ResolveResult e, IType t) { - // C# 4.0 spec: §7.5.2.3 Input types + // C# spec (draft-v11): §12.6.3.4 Input types LambdaResolveResult lrr = e as LambdaResolveResult; if (lrr != null && lrr.IsImplicitlyTyped || e is MethodGroupResolveResult) { @@ -416,7 +416,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver IType[] OutputTypes(ResolveResult e, IType t) { - // C# 4.0 spec: §7.5.2.4 Output types + // C# spec (draft-v11): §12.6.3.5 Output types LambdaResolveResult lrr = e as LambdaResolveResult; if (lrr != null || e is MethodGroupResolveResult) { @@ -465,8 +465,8 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver } #endregion - #region DependsOn (§7.5.2.5) - // C# 4.0 spec: §7.5.2.5 Dependance + #region DependsOn (§12.6.3.6) + // C# spec (draft-v11): §12.6.3.6 Dependence void CalculateDependencyMatrix() { @@ -522,8 +522,8 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver void MakeOutputTypeInference(ResolveResult e, IType t) { Log.WriteLine(" MakeOutputTypeInference from " + e + " to " + t); - // If E is an anonymous function with inferred return type U (§7.5.2.12) and T is a delegate type or expression - // tree type with return type Tb, then a lower-bound inference (§7.5.2.9) is made from U to Tb. + // If E is an anonymous function with inferred return type U (§12.6.3.14) and T is a delegate type or expression + // tree type with return type Tb, then a lower-bound inference (§12.6.3.11) is made from U to Tb. LambdaResolveResult lrr = e as LambdaResolveResult; if (lrr != null) { @@ -610,10 +610,10 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver } #endregion - #region MakeExplicitParameterTypeInference (§7.5.2.7) + #region MakeExplicitParameterTypeInference (§12.6.3.9) void MakeExplicitParameterTypeInference(LambdaResolveResult e, IType t) { - // C# 4.0 spec: §7.5.2.7 Explicit parameter type inferences + // C# spec (draft-v11): §12.6.3.9 Explicit parameter type inferences if (e.IsImplicitlyTyped || !e.HasParameterList) return; Log.WriteLine(" MakeExplicitParameterTypeInference from " + e + " to " + t); @@ -728,10 +728,10 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver } #endregion - #region MakeLowerBoundInference (§7.5.2.9) + #region MakeLowerBoundInference (§12.6.3.11) /// /// Make lower bound inference from U to V. - /// C# 4.0 spec: §7.5.2.9 Lower-bound inferences + /// C# spec (draft-v11): §12.6.3.11 Lower-bound inferences /// void MakeLowerBoundInference(IType U, IType V) { @@ -961,7 +961,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver } #endregion - #region Fixing (§7.5.2.11) + #region Fixing (§12.6.3.13) bool Fix(TP tp) { Log.WriteLine(" Trying to fix " + tp); @@ -996,7 +996,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver #region Finding the best common type of a set of expresssions /// - /// Gets the best common type (C# 4.0 spec: §7.5.2.14) of a set of expressions. + /// Gets the best common type (C# spec draft-v11: §12.6.3.17) of a set of expressions. /// public IType GetBestCommonType(IList expressions, out bool success) { @@ -1071,7 +1071,7 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver Log.WriteCollection("FindTypesInBound, LowerBounds=", lowerBounds); Log.WriteCollection("FindTypesInBound, UpperBounds=", upperBounds); - // First try the Fixing algorithm from the C# spec (§7.5.2.11) + // First try the Fixing algorithm from the C# spec (§12.6.3.13) List candidateTypes = lowerBounds.Union(upperBounds) .Where(c => lowerBounds.All(b => conversions.ImplicitConversion(b, c).IsValid)) .Where(c => upperBounds.All(b => conversions.ImplicitConversion(c, b).IsValid)) @@ -1079,9 +1079,8 @@ namespace ICSharpCode.Decompiler.CSharp.Resolver Log.WriteCollection("FindTypesInBound, Candidates=", candidateTypes); - // According to the C# specification, we need to pick the most specific - // of the candidate types. (the type which has conversions to all others) - // However, csc actually seems to choose the least specific. + // C# spec (draft-v11) §12.6.3.13: the result is the unique candidate type + // to which there is an implicit conversion from all the other candidate types. candidateTypes = candidateTypes.Where( c => candidateTypes.All(o => conversions.ImplicitConversion(o, c).IsValid) ).ToList();