diff --git a/.github/memory/testing/compiler.md b/.github/memory/testing/compiler.md index ac0b53b1188de..5dfaa6354cf0c 100644 --- a/.github/memory/testing/compiler.md +++ b/.github/memory/testing/compiler.md @@ -27,6 +27,11 @@ public class MyTests : CSharpTestBase - **Unit tests** target individual compiler phases (lexing, parsing); **compilation tests** create `Compilation` objects and verify symbols/diagnostics. +- **Version-specific C# features** use the matching + `src/Compilers/CSharp/Test/CSharpN/` project. Use `TestOptions.RegularNext` + for the upcoming language version and the latest stable `RegularN` option for + the disabled-feature boundary; retain an explicit `RegularPreview` case when + testing the feature's language-version gate. - **Cross-language patterns**: many test patterns work for both C# and VB with minor syntax changes. - **Verification baselines**: when helpers like `VerifyDiagnostics`, diff --git a/Compilers.slnf b/Compilers.slnf index 0c7a62089a14c..eb7a6934a21b3 100644 --- a/Compilers.slnf +++ b/Compilers.slnf @@ -6,6 +6,7 @@ "src\\Compilers\\CSharp\\Portable\\Microsoft.CodeAnalysis.CSharp.csproj", "src\\Compilers\\CSharp\\Test\\CommandLine\\Microsoft.CodeAnalysis.CSharp.CommandLine.UnitTests.csproj", "src\\Compilers\\CSharp\\Test\\CSharp15\\Microsoft.CodeAnalysis.CSharp.CSharp15.UnitTests.csproj", + "src\\Compilers\\CSharp\\Test\\CSharp16\\Microsoft.CodeAnalysis.CSharp.CSharp16.UnitTests.csproj", "src\\Compilers\\CSharp\\Test\\Emit3\\Microsoft.CodeAnalysis.CSharp.Emit3.UnitTests.csproj", "src\\Compilers\\CSharp\\Test\\Emit2\\Microsoft.CodeAnalysis.CSharp.Emit2.UnitTests.csproj", "src\\Compilers\\CSharp\\Test\\Emit\\Microsoft.CodeAnalysis.CSharp.Emit.UnitTests.csproj", diff --git a/Ide.slnf b/Ide.slnf index d15708ca46582..e0814992baff0 100644 --- a/Ide.slnf +++ b/Ide.slnf @@ -27,6 +27,7 @@ "src\\Compilers\\CSharp\\Test\\Emit2\\Microsoft.CodeAnalysis.CSharp.Emit2.UnitTests.csproj", "src\\Compilers\\CSharp\\Test\\Emit3\\Microsoft.CodeAnalysis.CSharp.Emit3.UnitTests.csproj", "src\\Compilers\\CSharp\\Test\\CSharp15\\Microsoft.CodeAnalysis.CSharp.CSharp15.UnitTests.csproj", + "src\\Compilers\\CSharp\\Test\\CSharp16\\Microsoft.CodeAnalysis.CSharp.CSharp16.UnitTests.csproj", "src\\Compilers\\CSharp\\Test\\Emit\\Microsoft.CodeAnalysis.CSharp.Emit.UnitTests.csproj", "src\\Compilers\\CSharp\\Test\\EndToEnd\\Microsoft.CodeAnalysis.CSharp.EndToEnd.UnitTests.csproj", "src\\Compilers\\CSharp\\Test\\IOperation\\Microsoft.CodeAnalysis.CSharp.IOperation.UnitTests.csproj", diff --git a/Roslyn.slnx b/Roslyn.slnx index f4484b68cf574..59a8d3b95466c 100644 --- a/Roslyn.slnx +++ b/Roslyn.slnx @@ -109,6 +109,7 @@ + diff --git a/docs/compilers/CSharp/compiler-breaking-changes-dotnet-12.md b/docs/compilers/CSharp/compiler-breaking-changes-dotnet-12.md new file mode 100644 index 0000000000000..5168cc1726f5b --- /dev/null +++ b/docs/compilers/CSharp/compiler-breaking-changes-dotnet-12.md @@ -0,0 +1,29 @@ +# Breaking changes in Roslyn after .NET 11.0.100 through .NET 12.0.100 + +This document lists known breaking changes in Roslyn after .NET 11 general release (.NET SDK version 11.0.100) through .NET 12 general release (.NET SDK version 12.0.100). + +## Type parameter inference considers generic constraints + +***Introduced in .NET 12*** + +In C# 16, method type inference can infer type parameters that occur only in the constraints of other inferred type parameters. As a result, a generic method that was previously excluded from overload resolution may become applicable. This can change the selected overload, introduce an ambiguity, or produce a more specific diagnostic from the newly applicable candidate. + +For example, the following call previously selected the non-generic overload because `U` could not be inferred. It now selects the generic overload because `T` is fixed to `string` and `U` is then inferred as `char` from the `IEnumerable` constraint. + +```cs +using System; +using System.Collections.Generic; + +static void M(object value) => Console.WriteLine("non-generic"); +static void M(T value) where T : IEnumerable => Console.WriteLine("generic"); + +M("test"); // C# 15: "non-generic"; C# 16: "generic" +``` + +To preserve the previous overload selection, explicitly convert the argument to the intended parameter type: + +```cs +M((object)"test"); // "non-generic" +``` + +See [the type parameter inference from constraints proposal](https://github.com/dotnet/csharplang/issues/9453) and [the Roslyn implementation](https://github.com/dotnet/roslyn/pull/84655). diff --git a/eng/build.ps1 b/eng/build.ps1 index f8f3e55816a65..b1aa9ecb49b0e 100644 --- a/eng/build.ps1 +++ b/eng/build.ps1 @@ -383,6 +383,7 @@ function GetCompilerTestAssembliesIncludePaths() { $assemblies += " --include '^Microsoft\.CodeAnalysis\.CSharp\.Emit2\.UnitTests$'" $assemblies += " --include '^Microsoft\.CodeAnalysis\.CSharp\.Emit3\.UnitTests$'" $assemblies += " --include '^Microsoft\.CodeAnalysis\.CSharp\.CSharp15\.UnitTests$'" + $assemblies += " --include '^Microsoft\.CodeAnalysis\.CSharp\.CSharp16\.UnitTests$'" $assemblies += " --include '^Microsoft\.CodeAnalysis\.CSharp\.IOperation\.UnitTests$'" $assemblies += " --include '^Microsoft\.CodeAnalysis\.CSharp\.CommandLine\.UnitTests$'" $assemblies += " --include '^Microsoft\.CodeAnalysis\.VisualBasic\.Syntax\.UnitTests$'" diff --git a/eng/build.sh b/eng/build.sh index e77f68b0e9772..b13929364b0b7 100755 --- a/eng/build.sh +++ b/eng/build.sh @@ -378,6 +378,7 @@ function GetCompilerTestAssembliesIncludePaths { assemblies+=" --include '^Microsoft\.CodeAnalysis\.CSharp\.Emit2\.UnitTests$'" assemblies+=" --include '^Microsoft\.CodeAnalysis\.CSharp\.Emit3\.UnitTests$'" assemblies+=" --include '^Microsoft\.CodeAnalysis\.CSharp\.CSharp15\.UnitTests$'" + assemblies+=" --include '^Microsoft\.CodeAnalysis\.CSharp\.CSharp16\.UnitTests$'" assemblies+=" --include '^Microsoft\.CodeAnalysis\.CSharp\.IOperation\.UnitTests$'" assemblies+=" --include '^Microsoft\.CodeAnalysis\.CSharp\.CommandLine\.UnitTests$'" assemblies+=" --include '^Microsoft\.CodeAnalysis\.VisualBasic\.Syntax\.UnitTests$'" diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs index 9abe0240201fe..1a0203ffc3d8c 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/MethodTypeInference.cs @@ -149,6 +149,13 @@ private enum Dependency private readonly HashSet[] _upperBounds; private readonly HashSet[] _lowerBounds; + // Constraint-derived bounds are used only when the corresponding type parameter + // has no bounds from ordinary argument or output inference. + private readonly HashSet[] _constraintExactBounds; + private readonly HashSet[] _constraintUpperBounds; + private readonly HashSet[] _constraintLowerBounds; + private bool _isInferringFromConstraint; + // https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/nullable-reference-types-specification.md#fixing // If the resulting candidate is a reference type and *all* of the exact bounds or *any* of // the lower bounds are nullable reference types, `null` or `default`, then `?` is added to @@ -350,6 +357,19 @@ private MethodTypeInferrer( _exactBounds = new HashSet[methodTypeParameters.Length]; _upperBounds = new HashSet[methodTypeParameters.Length]; _lowerBounds = new HashSet[methodTypeParameters.Length]; + if (IsFeatureTypeParameterInferenceFromConstraintsEnabled) + { + _constraintExactBounds = new HashSet[methodTypeParameters.Length]; + _constraintUpperBounds = new HashSet[methodTypeParameters.Length]; + _constraintLowerBounds = new HashSet[methodTypeParameters.Length]; + } + else + { + _constraintExactBounds = null; + _constraintUpperBounds = null; + _constraintLowerBounds = null; + } + _isInferringFromConstraint = false; _nullableAnnotationLowerBounds = new NullableAnnotation[methodTypeParameters.Length]; Debug.Assert(_nullableAnnotationLowerBounds.All(annotation => annotation.IsNotAnnotated())); _dependencies = null; @@ -469,10 +489,7 @@ private ImmutableArray GetResults(out bool inferredFromFunc { if (!fixedResultType.Type.IsErrorType()) { - if (_conversions.IncludeNullability && _nullableAnnotationLowerBounds[i].IsAnnotated()) - { - _fixedResults[i] = _fixedResults[i] with { Type = fixedResultType.AsAnnotated() }; - } + _fixedResults[i] = _fixedResults[i] with { Type = GetTypeWithInferredNullability(i, fixedResultType) }; continue; } @@ -488,6 +505,14 @@ private ImmutableArray GetResults(out bool inferredFromFunc return GetInferredTypeArguments(out inferredFromFunctionType); } + private TypeWithAnnotations GetTypeWithInferredNullability(int iParam, TypeWithAnnotations type) + { + Debug.Assert(type.HasType); + return _conversions.IncludeNullability && _nullableAnnotationLowerBounds[iParam].IsAnnotated() + ? type.AsAnnotated() + : type; + } + private bool ValidIndex(int index) { return 0 <= index && index < _methodTypeParameters.Length; @@ -534,12 +559,28 @@ private bool AllFixed() return true; } - private void AddBound(TypeWithAnnotations addedBound, HashSet[] collectedBounds, TypeWithAnnotations methodTypeParameterWithAnnotations) + private void AddBound( + TypeWithAnnotations addedBound, + HashSet[] ordinaryBounds, + HashSet[] constraintBounds, + TypeWithAnnotations methodTypeParameterWithAnnotations) { Debug.Assert(IsUnfixedTypeParameter(methodTypeParameterWithAnnotations)); var methodTypeParameter = (TypeParameterSymbol)methodTypeParameterWithAnnotations.Type; int methodTypeParameterIndex = GetOrdinal(methodTypeParameter); + var collectedBounds = ordinaryBounds; + + if (_isInferringFromConstraint) + { + if (HasOrdinaryBound(methodTypeParameterIndex)) + { + return; + } + + Debug.Assert(constraintBounds != null); + collectedBounds = constraintBounds; + } if (collectedBounds[methodTypeParameterIndex] == null) { @@ -549,7 +590,7 @@ private void AddBound(TypeWithAnnotations addedBound, HashSet useSiteInfo) { Debug.Assert(source is not null); @@ -2069,7 +2167,7 @@ private bool LowerBoundTypeParameterInference(TypeWithAnnotations source, TypeWi // SPEC: for Xi. if (IsUnfixedTypeParameter(target)) { - AddBound(source, _lowerBounds, target); + AddBound(source, _lowerBounds, _constraintLowerBounds, target); return true; } return false; @@ -2544,7 +2642,7 @@ private bool UpperBoundTypeParameterInference(TypeWithAnnotations source, TypeWi // SPEC: for Xi. if (IsUnfixedTypeParameter(target)) { - AddBound(source, _upperBounds, target); + AddBound(source, _upperBounds, _constraintUpperBounds, target); return true; } return false; @@ -2824,19 +2922,123 @@ private bool UpperBoundFunctionPointerTypeInference(TypeSymbol source, TypeSymbo // // Fixing // - private bool Fix(int iParam, ref CompoundUseSiteInfo useSiteInfo) + private bool FixParameter(int iParam, ref CompoundUseSiteInfo useSiteInfo) + { + Debug.Assert(!IsFeatureTypeParameterInferenceFromConstraintsEnabled); + + var fixedResult = GetFixResult(iParam, ref useSiteInfo); + if (!fixedResult.Type.HasType) + { + return false; + } + + _fixedResults[iParam] = fixedResult; + UpdateDependenciesAfterFix(iParam); + return true; + } + + /// + /// Fixes the parameters selected by . + /// Results are calculated from the same bounds state, committed together, and only + /// then propagated through constraints, so parameter order cannot affect the result. + /// + /// + /// if every selected parameter was fixed; otherwise, + /// . Successful fixes are still committed for error recovery. + /// + private bool FixSelectedParameters(BitVector needsFixing, ref CompoundUseSiteInfo useSiteInfo) + { + Debug.Assert(IsFeatureTypeParameterInferenceFromConstraintsEnabled); + + var fixedResults = ArrayBuilder<(TypeWithAnnotations Type, bool FromFunctionType)>.GetInstance(_methodTypeParameters.Length); + fixedResults.AddMany(default, _methodTypeParameters.Length); + var success = true; + + // Calculate every result from the same bounds snapshot before committing any + // of them. Constraint propagation from one result therefore cannot affect + // another parameter selected by the same fixing step. + for (int param = 0; param < _methodTypeParameters.Length; param++) + { + if (!needsFixing[param]) + { + continue; + } + + var fixedResult = GetFixResult(param, ref useSiteInfo); + fixedResults[param] = fixedResult; + if (!fixedResult.Type.HasType) + { + success = false; + } + } + + for (int parameter = 0; parameter < fixedResults.Count; parameter++) + { + var fixedResult = fixedResults[parameter]; + if (fixedResult.Type.HasType) + { + _fixedResults[parameter] = fixedResult; + } + } + + Debug.Assert(!_isInferringFromConstraint); + _isInferringFromConstraint = true; + for (int parameter = 0; parameter < fixedResults.Count; parameter++) + { + var type = fixedResults[parameter].Type; + if (!type.HasType) + { + continue; + } + + var constraintSource = GetTypeWithInferredNullability(parameter, type); + foreach (var constraintType in _methodTypeParameters[parameter].ConstraintTypesNoUseSiteDiagnostics) + { + LowerBoundInference(constraintSource, constraintType, ref useSiteInfo); + } + } + _isInferringFromConstraint = false; + + for (int parameter = 0; parameter < fixedResults.Count; parameter++) + { + if (fixedResults[parameter].Type.HasType) + { + UpdateDependenciesAfterFix(parameter); + } + } + + fixedResults.Free(); + return success; + } + + private (TypeWithAnnotations Type, bool FromFunctionType) GetFixResult( + int iParam, + ref CompoundUseSiteInfo useSiteInfo) { Debug.Assert(IsUnfixed(iParam)); var typeParameter = _methodTypeParameters[iParam]; - var exact = _exactBounds[iParam]; - var lower = _lowerBounds[iParam]; - var upper = _upperBounds[iParam]; + HashSet exact; + HashSet lower; + HashSet upper; + if (HasOrdinaryBound(iParam)) + { + exact = _exactBounds[iParam]; + lower = _lowerBounds[iParam]; + upper = _upperBounds[iParam]; + } + else + { + Debug.Assert(_constraintExactBounds != null); + exact = _constraintExactBounds[iParam]; + lower = _constraintLowerBounds[iParam]; + upper = _constraintUpperBounds[iParam]; + } var best = Fix(_compilation, _conversions, typeParameter, exact, lower, upper, ref useSiteInfo); if (!best.Type.HasType) { - return false; + return default; } #if DEBUG @@ -2852,9 +3054,7 @@ private bool Fix(int iParam, ref CompoundUseSiteInfo useSiteInfo } #endif - _fixedResults[iParam] = best; - UpdateDependenciesAfterFix(iParam); - return true; + return best; } private static (TypeWithAnnotations Type, bool FromFunctionType) Fix( @@ -3311,8 +3511,32 @@ private bool InferTypeArgumentsFromFirstArgument(ref CompoundUseSiteInfo closed classes + + type parameter inference from constraints + Types and aliases cannot be named 'closed'. diff --git a/src/Compilers/CSharp/Portable/Errors/MessageID.cs b/src/Compilers/CSharp/Portable/Errors/MessageID.cs index 4f14c04be82ba..9e84a3b8bfbb2 100644 --- a/src/Compilers/CSharp/Portable/Errors/MessageID.cs +++ b/src/Compilers/CSharp/Portable/Errors/MessageID.cs @@ -314,6 +314,7 @@ internal enum MessageID IDS_FeatureClosedClasses = MessageBase + 12862, IDS_FeatureExtensionIndexers = MessageBase + 12863, IDS_FeatureLabeledBreakContinue = MessageBase + 12864, + IDS_FeatureTypeParameterInferenceFromConstraints = MessageBase + 12865, } // Message IDs may refer to strings that need to be localized. @@ -495,6 +496,7 @@ internal static LanguageVersion RequiredVersion(this MessageID feature) // C# preview features. case MessageID.IDS_FeatureUnsafeEvolution: // https://github.com/dotnet/roslyn/issues/82546: keep this in preview until C# 16 + case MessageID.IDS_FeatureTypeParameterInferenceFromConstraints: return LanguageVersion.Preview; // C# 15.0 features. diff --git a/src/Compilers/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.csproj b/src/Compilers/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.csproj index 9dccec2deeff3..de2b20fbf19d7 100644 --- a/src/Compilers/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.csproj +++ b/src/Compilers/CSharp/Portable/Microsoft.CodeAnalysis.CSharp.csproj @@ -64,6 +64,7 @@ + diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index e542e66546c05..66afb27fc2c21 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -3247,6 +3247,11 @@ Inicializátory polí struktury + + type parameter inference from constraints + type parameter inference from constraints + + unbound generic types in nameof operator nevázané obecné typy v operátoru nameof diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 4174ac4da3048..94787adb8a86a 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -3247,6 +3247,11 @@ Strukturfeldinitialisierer + + type parameter inference from constraints + type parameter inference from constraints + + unbound generic types in nameof operator Ungebundene generische Typen im nameof-Operator diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index e2238894f58aa..b51b4fa21f6af 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -3247,6 +3247,11 @@ inicializadores de campo de estructura + + type parameter inference from constraints + type parameter inference from constraints + + unbound generic types in nameof operator tipos genéricos no enlazados en el operador nameof diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index b0766a71659ff..edba2371ddcdc 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -3247,6 +3247,11 @@ initialiseurs de champ de struct + + type parameter inference from constraints + type parameter inference from constraints + + unbound generic types in nameof operator types génériques indépendants dans l’opérateur nameof diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index ddd2e6151a9f5..c7b5d07392bdf 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -3247,6 +3247,11 @@ inizializzatori di campo struct + + type parameter inference from constraints + type parameter inference from constraints + + unbound generic types in nameof operator tipi generici non collegati nell'operatore nameof diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index ab9798cc7ad35..8862b16d60a40 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -3247,6 +3247,11 @@ 構造体フィールド初期化子 + + type parameter inference from constraints + type parameter inference from constraints + + unbound generic types in nameof operator nameof 演算子でバインドされていないジェネリック型 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index f2bca5eebe67f..14dd5feed8577 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -3247,6 +3247,11 @@ 구조체 필드 이니셜라이저 + + type parameter inference from constraints + type parameter inference from constraints + + unbound generic types in nameof operator nameof 연산자의 바인딩되지 않은 제네릭 형식 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 1000222f04295..44e380209d308 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -3247,6 +3247,11 @@ inicjatory pola struktury + + type parameter inference from constraints + type parameter inference from constraints + + unbound generic types in nameof operator niepowiązane typy ogólne w operatorze nameof diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 228fad4932cef..fd2c81b39c8e0 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -3247,6 +3247,11 @@ inicializadores de campo de struct + + type parameter inference from constraints + type parameter inference from constraints + + unbound generic types in nameof operator tipos genéricos não associados no operador nameof diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 0cd9635c2ce8d..1b017d9d23726 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -3247,6 +3247,11 @@ инициализаторы полей структуры + + type parameter inference from constraints + type parameter inference from constraints + + unbound generic types in nameof operator не привязанные универсальные типы в операторе nameof diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 57c5bd6e5f886..55c8e3bd4b9c2 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -3247,6 +3247,11 @@ struct alan başlatıcıları + + type parameter inference from constraints + type parameter inference from constraints + + unbound generic types in nameof operator nameof işlecindeki bağlı olmayan genel türler diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 81a5f7075828c..37da8f8d2c791 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -3247,6 +3247,11 @@ 结构字段初始化表达式 + + type parameter inference from constraints + type parameter inference from constraints + + unbound generic types in nameof operator nameof 运算符中的未绑定泛型类型 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index a23845117b725..4828b98083efa 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -3247,6 +3247,11 @@ 結構欄位初始設定式 + + type parameter inference from constraints + type parameter inference from constraints + + unbound generic types in nameof operator 運算子 nameof 中的未系結泛型型別 diff --git a/src/Compilers/CSharp/Test/CSharp16/Microsoft.CodeAnalysis.CSharp.CSharp16.UnitTests.csproj b/src/Compilers/CSharp/Test/CSharp16/Microsoft.CodeAnalysis.CSharp.CSharp16.UnitTests.csproj new file mode 100644 index 0000000000000..8a016082a80cf --- /dev/null +++ b/src/Compilers/CSharp/Test/CSharp16/Microsoft.CodeAnalysis.CSharp.CSharp16.UnitTests.csproj @@ -0,0 +1,25 @@ + + + + + Library + Microsoft.CodeAnalysis.CSharp.UnitTests + $(NetRoslyn);net472 + true + + + + + + + + + + + + + + + + + diff --git a/src/Compilers/CSharp/Test/CSharp16/TypeParameterInferenceFromConstraintsTests.cs b/src/Compilers/CSharp/Test/CSharp16/TypeParameterInferenceFromConstraintsTests.cs new file mode 100644 index 0000000000000..e7629794c0afe --- /dev/null +++ b/src/Compilers/CSharp/Test/CSharp16/TypeParameterInferenceFromConstraintsTests.cs @@ -0,0 +1,1083 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable disable + +using System.Linq; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests +{ + /// + /// Tests for "Type Parameter Inference from Constraints" + /// (https://github.com/dotnet/csharplang/issues/9453). + /// + public class TypeParameterInferenceFromConstraintsTests : CSharpTestBase + { + private static IMethodSymbol GetInferredMethod(CSharpCompilation comp, string methodName = "M") + { + var tree = comp.SyntaxTrees.Single(); + var model = comp.GetSemanticModel(tree); + var syntax = tree.GetRoot().DescendantNodes().OfType() + .Single(i => model.GetSymbolInfo(i).Symbol is IMethodSymbol { Name: var name } && name == methodName); + return (IMethodSymbol)model.GetSymbolInfo(syntax).Symbol; + } + + [Fact] + public void Basic_IEnumerable_LanguageVersions() + { + var source = """ + using System.Collections.Generic; + + class C + { + static void Main() + { + List l = [1, 2, 3]; + M(l); + } + + static void M(TEnumerable t) where TEnumerable : IEnumerable + { + } + } + """; + + CreateCompilation(source, parseOptions: TestOptions.Regular15).VerifyDiagnostics( + // (8,9): error CS0411: The type arguments for method 'C.M(TEnumerable)' cannot be inferred from the usage. Try specifying the type arguments explicitly. + // M(l); + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "M").WithArguments("C.M(TEnumerable)").WithLocation(8, 9)); + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + var method = GetInferredMethod(comp); + Assert.Equal( + "void C.M, System.Int32>(System.Collections.Generic.List t)", + method.ToTestDisplayString()); + + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics(); + } + + [Fact] + public void ContravariantConstraint_DoesNotChangeInferredType() + { + var source = """ + using System; + + static Type M(T t, U u) where T : I => typeof(U); + Console.WriteLine(M(new Holder(), new B()).Name); + interface I { } + class A { } + class B : A { } + class Holder : I { } + """; + + CompileAndVerify( + CreateCompilation(source, parseOptions: TestOptions.Regular15, options: TestOptions.DebugExe), + expectedOutput: "B").VerifyDiagnostics(); + + CompileAndVerify( + CreateCompilation(source, parseOptions: TestOptions.RegularNext, options: TestOptions.DebugExe), + expectedOutput: "B").VerifyDiagnostics(); + } + + [Fact] + public void ConstraintInference_PrecedingOutputInference_DoesNotChangeInferredType() + { + var source = """ + using System; + + static Type M(T t, V v, Func f) where T : I => typeof(U); + Console.WriteLine(M(new Holder(), 0, x => new B()).Name); + interface I { } + class A { } + class B : A { } + class Holder : I { } + """; + + CompileAndVerify( + CreateCompilation(source, parseOptions: TestOptions.RegularNext, options: TestOptions.DebugExe), + expectedOutput: "B").VerifyDiagnostics(); + } + + [Fact] + public void MultipleConstraints() + { + var source = """ + using System; + using System.Collections.Generic; + + class C + { + static void M(T obj) where T : IEnumerable, IComparable + { + } + + class MyClass : IComparable, IEnumerable + { + public int CompareTo(string other) => 0; + public IEnumerator GetEnumerator() => null; + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => null; + } + + static void Main() + { + var c = new MyClass(); + M(c); + } + } + """; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + var method = GetInferredMethod(comp); + Assert.Equal( + "void C.M(C.MyClass obj)", + method.ToTestDisplayString()); + } + + [Fact] + public void ConstraintWithBaseClass() + { + var source = """ + class Base { } + class Derived : Base { } + + class C + { + static void M(TDerived t) where TDerived : Base + { + } + + static void Main() + { + M(new Derived()); + } + } + """; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + var method = GetInferredMethod(comp); + Assert.Equal("void C.M(Derived t)", method.ToTestDisplayString()); + } + + [Fact] + public void TransitiveConstraintDependence() + { + // TInner depends on TMiddle (constraint), TMiddle depends on TOuter (constraint). + // Fixing TOuter -> TMiddle -> TInner via successive constraint lower-bound inferences. + var source = """ + using System.Collections.Generic; + + class C + { + static void M(TOuter t) + where TOuter : IEnumerable + where TMiddle : IEnumerable + { + } + + static void Main() + { + M(new List>()); + } + } + """; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + var method = GetInferredMethod(comp); + Assert.Equal( + "void C.M>, System.Collections.Generic.List, System.Int32>(System.Collections.Generic.List> t)", + method.ToTestDisplayString()); + } + + [Fact] + public void LambdaParameterTypeFromConstraint() + { + // The constraint-inferred type parameter is used as the lambda's delegate parameter type. + // TElement must be fixed (via the constraint) before the lambda can be bound. + var source = """ + using System; + using System.Collections.Generic; + + class C + { + static void M(TEnumerable t, Func predicate) + where TEnumerable : IEnumerable + { + } + + static void Main() + { + var l = new List { 1, 2, 3 }; + M(l, x => x.ToString("X") == "2"); + } + } + """; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + var method = GetInferredMethod(comp); + Assert.Equal( + "void C.M, System.Int32>(System.Collections.Generic.List t, System.Func predicate)", + method.ToTestDisplayString()); + } + + [Fact] + public void LambdaParameterAndReturnTypeFromConstraint() + { + var source = """ + using System; + using System.Collections.Generic; + + class C + { + static TElement M(TEnumerable t, Func f) + where TEnumerable : IEnumerable + { + TElement last = default; + foreach (var e in t) last = f(e); + return last; + } + + static void Main() + { + var r = M(new List { 5 }, x => x + 1); + System.Console.WriteLine(r); + } + } + """; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext, options: TestOptions.DebugExe); + CompileAndVerify(comp, expectedOutput: "6").VerifyDiagnostics(); + + var method = GetInferredMethod(comp); + Assert.Equal( + "System.Int32 C.M, System.Int32>(System.Collections.Generic.List t, System.Func f)", + method.ToTestDisplayString()); + } + + [Fact] + public void BreakingChange_OverloadResolution() + { + // Documented breaking change: with the feature, the generic overload becomes applicable + // and is preferred over the object overload. + var source = """ + using System; + using System.Collections.Generic; + + class C + { + static void M(object obj) => Console.WriteLine("non-generic"); + static void M(T t) where T : IEnumerable => Console.WriteLine("generic"); + + static void Main() + { + M("test"); + } + } + """; + + CompileAndVerify( + CreateCompilation(source, parseOptions: TestOptions.RegularNext, options: TestOptions.DebugExe), + expectedOutput: "generic").VerifyDiagnostics(); + + CompileAndVerify( + CreateCompilation(source, parseOptions: TestOptions.Regular15, options: TestOptions.DebugExe), + expectedOutput: "non-generic").VerifyDiagnostics(); + } + + [Fact] + public void ConstraintDoesNotMentionOtherTypeParameter_StillFails() + { + // TEnumerable has a constraint (so the feature's dependence/lower-bound logic runs), but + // that constraint does not mention TElement, so TElement still cannot be inferred. + var source = """ + using System; + + class C + { + static void M(TEnumerable t) where TEnumerable : IComparable + { + } + + static void Main() + { + M(1); + } + } + """; + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics( + // (11,9): error CS0411: The type arguments for method 'C.M(TEnumerable)' cannot be inferred from the usage. Try specifying the type arguments explicitly. + // M(1); + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "M").WithArguments("C.M(TEnumerable)").WithLocation(11, 9)); + } + + [Fact] + public void DirectTypeParameterConstraint() + { + // where TEnumerable : IEnumerable, and the element bound flows to TElement, + // while a second type parameter constrains directly to another. + var source = """ + using System.Collections.Generic; + + class C + { + static void M(T t) where T : U where U : IEnumerable + { + } + + static void Main() + { + M(new List()); + } + } + """; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + var method = GetInferredMethod(comp); + Assert.Equal( + "void C.M, System.Collections.Generic.List>(System.Collections.Generic.List t)", + method.ToTestDisplayString()); + } + + [Fact] + public void ExtensionMethod() + { + var source = """ + using System.Collections.Generic; + + static class Extensions + { + public static void M(this TEnumerable t) where TEnumerable : IEnumerable + { + } + } + + class C + { + static void Main() + { + List l = [1, 2, 3]; + l.M(); + } + } + """; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + var method = GetInferredMethod(comp); + Assert.Equal("M", method.Name); + Assert.Equal( + new[] { "System.Collections.Generic.List", "System.Int32" }, + method.TypeArguments.Select(t => t.ToTestDisplayString())); + } + + [Fact] + public void NullableReferenceType_AnnotationFlows() + { + var source = """ + #nullable enable + using System.Collections.Generic; + + class C + { + static void M(TEnumerable t) where TEnumerable : IEnumerable + { + } + + static void Main() + { + M(new List()); + } + } + """; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + var method = GetInferredMethod(comp); + var element = method.TypeArguments[1]; + Assert.Equal(SpecialType.System_String, element.SpecialType); + Assert.Equal(CodeAnalysis.NullableAnnotation.Annotated, element.NullableAnnotation); + } + + [Fact] + public void NullableReferenceType_AnnotationFlowsFromOrdinaryBound() + { + var source = """ + #nullable enable + + class C + { + static void M(T x, T y) where T : U + { + } + + static void Main() + { + M("", null); + } + } + """; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + var method = GetInferredMethod(comp); + AssertEx.Equal( + new[] { "System.String?", "System.String?" }, + method.TypeArguments.SelectAsArray(t => t.ToTestDisplayString(includeNonNullable: true))); + } + + [Fact] + public void NullableReferenceType_DoesNotCrashNullableAnalysis() + { + // Exercises the NullableWalker re-inference path through the constraint lower-bound inference. + var source = """ + #nullable enable + using System; + using System.Collections.Generic; + + class C + { + static void M(TEnumerable t, Func f) + where TEnumerable : IEnumerable + { + } + + static void Main() + { + M(new List(), x => x); + } + } + """; + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(); + } + + [Fact] + public void OrdinaryBoundConflictsWithConstraint_ConstraintFails() + { + // The direct argument fixes TElement as string. Constraint inference does not replace + // that ordinary bound, so inference succeeds and the incompatible constraint is reported. + var source = """ + using System.Collections.Generic; + + class C + { + static void M(TEnumerable t, TElement e) where TEnumerable : IEnumerable + { + } + + static void Main() + { + M(new List(), "hello"); + } + } + """; + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics( + // (11,9): error CS0311: The type 'System.Collections.Generic.List' cannot be used as type parameter 'TEnumerable' in the generic type or method 'C.M(TEnumerable, TElement)'. There is no implicit reference conversion from 'System.Collections.Generic.List' to 'System.Collections.Generic.IEnumerable'. + // M(new List(), "hello"); + Diagnostic(ErrorCode.ERR_GenericConstraintNotSatisfiedRefType, "M").WithArguments("C.M(TEnumerable, TElement)", "System.Collections.Generic.IEnumerable", "TEnumerable", "System.Collections.Generic.List").WithLocation(11, 9)); + } + + [Fact] + public void SelfReferentialConstraint_DoesNotLoop() + { + // where T : IComparable is satisfied; the constraint lower-bound inference is a + // no-op (T is already fixed), so this must terminate and succeed. + var source = """ + using System; + + class C + { + static void M(T t) where T : IComparable + { + } + + static void Main() + { + M(5); + } + } + """; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + var method = GetInferredMethod(comp); + Assert.Equal("void C.M(System.Int32 t)", method.ToTestDisplayString()); + } + + [Fact] + public void ConstraintReferencesContainingTypeParameter() + { + // The constraint mentions both the containing type's type parameter (T, already bound + // via the constructed receiver C) and a method type parameter (V, inferred). + var source = """ + using System.Collections.Generic; + + class C + { + public void M(U u) where U : IDictionary + { + } + } + + class D + { + static void Test(C c) + { + c.M(new Dictionary()); + } + } + """; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + var method = GetInferredMethod(comp); + Assert.Equal( + "void C.M, System.Int32>(System.Collections.Generic.Dictionary u)", + method.ToTestDisplayString()); + } + + [Fact] + public void OverloadResolution_GenericWithConstraintBeatsObject_WithLambda() + { + // The user's flagged risk area: overload resolution involving a lambda whose delegate + // parameter type is the constraint-inferred type parameter. With the feature, the generic + // overload becomes applicable (and is more specific than the object overload). + var source = """ + using System; + using System.Collections.Generic; + + class C + { + static void M(object o, Action a) => Console.WriteLine("object"); + static void M(TEnumerable t, Action a) + where TEnumerable : IEnumerable => Console.WriteLine("generic"); + + static void Main() + { + M(new List(), x => { }); + } + } + """; + + CompileAndVerify( + CreateCompilation(source, parseOptions: TestOptions.RegularNext, options: TestOptions.DebugExe), + expectedOutput: "generic").VerifyDiagnostics(); + + CompileAndVerify( + CreateCompilation(source, parseOptions: TestOptions.Regular15, options: TestOptions.DebugExe), + expectedOutput: "object").VerifyDiagnostics(); + } + + [Fact] + public void NonGenericPreferredOverConstraintInferredGeneric() + { + // With the feature the generic overload becomes applicable (T=List, E=int), but the + // more specific non-generic overload is still preferred. This verifies that promoting the + // generic into the candidate set does not change selection of the better candidate. + var source = """ + using System; + using System.Collections.Generic; + + class C + { + static void M(List l) => Console.WriteLine("specific"); + static void M(T t) where T : IEnumerable => Console.WriteLine("generic"); + + static void Main() + { + M(new List()); + } + } + """; + + CompileAndVerify( + CreateCompilation(source, parseOptions: TestOptions.RegularNext, options: TestOptions.DebugExe), + expectedOutput: "specific").VerifyDiagnostics(); + } + + [Fact] + public void GenericArgument_ConstraintElementInferred() + { + // The element type itself is a generic type referencing another inferred argument. + var source = """ + using System.Collections.Generic; + + class C + { + static void M(TEnumerable t) where TEnumerable : IEnumerable> + { + } + + static void Main() + { + M(new List>()); + } + } + """; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + var method = GetInferredMethod(comp); + Assert.Equal("System.Int32", method.TypeArguments[1].ToTestDisplayString()); + } + + [Fact] + public void InferenceIsOneDirectional_ConstrainedParameterNotInferred() + { + // The feature flows bounds FROM a fixed constrained parameter (TEnumerable) INTO the + // type parameters that appear in its constraint (TElement) -- never the other way around. + // Here TElement is inferable from the argument, but TEnumerable has no bound of its own, + // so it can never be fixed and inference fails even though the constraint mentions TElement. + var source = """ + using System.Collections.Generic; + + class C + { + static void M(TElement e) where TEnumerable : IEnumerable + { + } + + static void Main() + { + M(42); + } + } + """; + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics( + // (11,9): error CS0411: The type arguments for method 'C.M(TElement)' cannot be inferred from the usage. Try specifying the type arguments explicitly. + // M(42); + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "M").WithArguments("C.M(TElement)").WithLocation(11, 9)); + } + + [Fact] + public void MultipleConstraintBoundsMergeToWeakerType() + { + // TElement is not inferable from any argument directly (without the feature this fails); + // it is inferred only via the constraints. The first constraint contributes 'string' + // (List : IEnumerable) and the second contributes 'object'. Merging the + // two lower bounds yields the common base 'object', so TElement is inferred as the weaker + // 'object' even though the user passed a List. + var source = """ + using System.Collections.Generic; + + class C + { + static void M(TA a, TB b) + where TA : IEnumerable + where TB : IEnumerable + { + } + + static void Main() + { + M(new List(), new List()); + } + } + """; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + var method = GetInferredMethod(comp); + Assert.Equal("System.Object", method.TypeArguments[2].ToTestDisplayString()); + + // Without the feature TElement is not inferable at all (it only appears in constraints). + CreateCompilation(source, parseOptions: TestOptions.Regular15).VerifyDiagnostics( + // (13,9): error CS0411: The type arguments for method 'C.M(TA, TB)' cannot be inferred from the usage. Try specifying the type arguments explicitly. + // M(new List(), new List()); + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "M").WithArguments("C.M(TA, TB)").WithLocation(13, 9)); + } + + [Fact] + public void ArrayImplementingIEnumerable() + { + // Arrays implement IEnumerable; the element type is inferred from the constraint. + var source = """ + using System.Collections.Generic; + + class C + { + static void M(TEnumerable t) where TEnumerable : IEnumerable + { + } + + static void Main() + { + M(new int[0]); + } + } + """; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + + var method = GetInferredMethod(comp); + Assert.Equal("void C.M(System.Int32[] t)", method.ToTestDisplayString()); + } + + [Fact] + public void InferredTypeViolatesPrimaryConstraint() + { + // Inference (from the constraint) succeeds and fixes TElement to 'int', but the separate + // primary constraint 'where TElement : class' is then checked and is not satisfied. + var source = """ + using System.Collections.Generic; + + class C + { + static void M(TEnumerable t) + where TEnumerable : IEnumerable + where TElement : class + { + } + + static void Main() + { + M(new List()); + } + } + """; + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics( + // (13,9): error CS0452: The type 'int' must be a reference type in order to use it as parameter 'TElement' in the generic type or method 'C.M(TEnumerable)' + // M(new List()); + Diagnostic(ErrorCode.ERR_RefConstraintNotSatisfied, "M").WithArguments("C.M(TEnumerable)", "TElement", "int").WithLocation(13, 9)); + + // Without the feature TElement is not inferred at all, so the user sees a different + // (arguably clearer) error: CS0411 rather than the primary-constraint violation. + CreateCompilation(source, parseOptions: TestOptions.Regular15).VerifyDiagnostics( + // (13,9): error CS0411: The type arguments for method 'C.M(TEnumerable)' cannot be inferred from the usage. Try specifying the type arguments explicitly. + // M(new List()); + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "M").WithArguments("C.M(TEnumerable)").WithLocation(13, 9)); + } + + [Fact] + public void CyclicConstraintDependence_TerminatesAndReportsConstraintErrors() + { + // The feature makes T depend on U and U depend on T (each occurs in the other's + // constraint), forming a dependency cycle. Inference must still terminate; both are + // fixed from their arguments and the (unsatisfiable) constraints are reported normally. + var source = """ + using System.Collections.Generic; + + class C + { + static void M(T t, U u) where T : IEnumerable where U : IEnumerable + { + } + + static void Main() + { + M(1, 2); + } + } + """; + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics( + // (11,9): error CS0315: The type 'int' cannot be used as type parameter 'T' in the generic type or method 'C.M(T, U)'. There is no boxing conversion from 'int' to 'System.Collections.Generic.IEnumerable'. + // M(1, 2); + Diagnostic(ErrorCode.ERR_GenericConstraintNotSatisfiedValType, "M").WithArguments("C.M(T, U)", "System.Collections.Generic.IEnumerable", "T", "int").WithLocation(11, 9), + // (11,9): error CS0315: The type 'int' cannot be used as type parameter 'U' in the generic type or method 'C.M(T, U)'. There is no boxing conversion from 'int' to 'System.Collections.Generic.IEnumerable'. + // M(1, 2); + Diagnostic(ErrorCode.ERR_GenericConstraintNotSatisfiedValType, "M").WithArguments("C.M(T, U)", "System.Collections.Generic.IEnumerable", "U", "int").WithLocation(11, 9)); + } + + [Fact] + public void CyclicConstraints_WithOrdinaryBounds_DoNotDependOnTypeParameterOrder() + { + var source = """ + interface I { } + interface J { } + class A : I { } + class B1 : J { } + class B2 : B1 { } + + class Program + { + static void M1(T t, U u) where T : I where U : J { } + static void M2(U u, T t) where T : I where U : J { } + + static void Main() + { + M1(new A(), new B2()); + M2(new B2(), new A()); + } + } + """; + + var expected = new[] + { + // (14,9): error CS0311: The type 'A' cannot be used as type parameter 'T' in the generic type or method 'Program.M1(T, U)'. There is no implicit reference conversion from 'A' to 'I'. + // M1(new A(), new B2()); + Diagnostic(ErrorCode.ERR_GenericConstraintNotSatisfiedRefType, "M1").WithArguments("Program.M1(T, U)", "I", "T", "A").WithLocation(14, 9), + // (15,9): error CS0311: The type 'A' cannot be used as type parameter 'T' in the generic type or method 'Program.M2(U, T)'. There is no implicit reference conversion from 'A' to 'I'. + // M2(new B2(), new A()); + Diagnostic(ErrorCode.ERR_GenericConstraintNotSatisfiedRefType, "M2").WithArguments("Program.M2(U, T)", "I", "T", "A").WithLocation(15, 9) + }; + + CreateCompilation(source, parseOptions: TestOptions.Regular15).VerifyDiagnostics(expected); + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics(expected); + } + + [Fact] + public void CyclicConstraints_WithConstraintBounds_DoNotDependOnTypeParameterOrder() + { + var source = """ + interface P { } + interface I { } + interface J { } + class A : I { } + class B1 : J { } + class B2 : B1 { } + class Seed : P { } + + class Program + { + static void M1(S s) where S : P where T : I where U : J { } + static void M2(S s) where S : P where T : I where U : J { } + + static void Main() + { + M1(new Seed()); + M2(new Seed()); + } + } + """; + + CreateCompilation(source, parseOptions: TestOptions.Regular15).VerifyDiagnostics( + // (16,9): error CS0411: The type arguments for method 'Program.M1(S)' cannot be inferred from the usage. Try specifying the type arguments explicitly. + // M1(new Seed()); + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "M1").WithArguments("Program.M1(S)").WithLocation(16, 9), + // (17,9): error CS0411: The type arguments for method 'Program.M2(S)' cannot be inferred from the usage. Try specifying the type arguments explicitly. + // M2(new Seed()); + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "M2").WithArguments("Program.M2(S)").WithLocation(17, 9)); + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics( + // (16,9): error CS0311: The type 'A' cannot be used as type parameter 'T' in the generic type or method 'Program.M1(S)'. There is no implicit reference conversion from 'A' to 'I'. + // M1(new Seed()); + Diagnostic(ErrorCode.ERR_GenericConstraintNotSatisfiedRefType, "M1").WithArguments("Program.M1(S)", "I", "T", "A").WithLocation(16, 9), + // (17,9): error CS0311: The type 'A' cannot be used as type parameter 'T' in the generic type or method 'Program.M2(S)'. There is no implicit reference conversion from 'A' to 'I'. + // M2(new Seed()); + Diagnostic(ErrorCode.ERR_GenericConstraintNotSatisfiedRefType, "M2").WithArguments("Program.M2(S)", "I", "T", "A").WithLocation(17, 9)); + } + + [Fact] + public void MultiLevelEnumeratorPattern_InfersThroughConstraintChain() + { + // The allocation-free "generic struct enumerator" LINQ pattern: the enumerator type is + // itself a type parameter, resolved through a two-level constraint chain. From just the + // receiver (MyList) the feature infers TEnumerable=MyList, then via its constraint + // TEnumerator=MyEnumerator and T=int, and TEnumerator's own (self-referential) constraint + // confirms T. All three are inferred with no explicit type arguments. + var source = """ + interface IEnumerator2 where TSelf : IEnumerator2 + { + T Current { get; } + bool MoveNext(); + } + + interface IEnumerable2 where TEnumerator : IEnumerator2 + { + TEnumerator GetEnumerator(); + } + + struct MyEnumerator : IEnumerator2 + { + public int Current => 0; + public bool MoveNext() => false; + } + + class MyList : IEnumerable2 + { + public MyEnumerator GetEnumerator() => default; + } + + static class Ext + { + public static void Where(this TEnumerable e) + where TEnumerable : IEnumerable2 + where TEnumerator : IEnumerator2 + { } + } + + class C + { + static void Main() + { + new MyList().Where(); + } + } + """; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + var inv = comp.SyntaxTrees.Single().GetRoot().DescendantNodes().OfType().Single(); + var m = (IMethodSymbol)comp.GetSemanticModel(comp.SyntaxTrees.Single()).GetSymbolInfo(inv).Symbol; + Assert.Equal("MyList", m.TypeArguments[0].ToTestDisplayString()); + Assert.Equal("MyEnumerator", m.TypeArguments[1].ToTestDisplayString()); + Assert.Equal("System.Int32", m.TypeArguments[2].ToTestDisplayString()); + + // Without the feature none of the constraint-only parameters can be inferred. + CreateCompilation(source, parseOptions: TestOptions.Regular15).VerifyDiagnostics( + // (35,22): error CS0411: The type arguments for method 'Ext.Where(TEnumerable)' cannot be inferred from the usage. Try specifying the type arguments explicitly. + // new MyList().Where(); + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "Where").WithArguments("Ext.Where(TEnumerable)").WithLocation(35, 22)); + } + + [Fact] + public void AsyncLambda_TaskReturnDrivesConstrainedTaskParameter() + { + // The async lambda's inferred return type (Task) fixes the constrained parameter + // TTask = Task; the constraint 'where TTask : Task' then infers TResult = int. + var source = """ + using System.Threading.Tasks; + + class C + { + static void M(System.Func f) where TTask : Task + { + } + + static void Main() + { + M(async () => 42); + } + } + """; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + var m = GetInferredMethod(comp); + Assert.Equal("System.Threading.Tasks.Task", m.TypeArguments[0].ToTestDisplayString()); + Assert.Equal("System.Int32", m.TypeArguments[1].ToTestDisplayString()); + + // Without the feature TResult only appears in the constraint and cannot be inferred. + CreateCompilation(source, parseOptions: TestOptions.Regular15).VerifyDiagnostics( + // (11,9): error CS0411: The type arguments for method 'C.M(Func)' cannot be inferred from the usage. Try specifying the type arguments explicitly. + // M(async () => 42); + Diagnostic(ErrorCode.ERR_CantInferMethTypeArgs, "M").WithArguments("C.M(System.Func)").WithLocation(11, 9)); + } + + [Fact] + public void TaskAsClassConstraint_InfersResult() + { + // Task (a sealed-ish library class) used as a naked class constraint: passing a + // Task argument fixes TTask = Task, and the constraint infers TResult = int. + var source = """ + using System.Threading.Tasks; + + class C + { + static void M(TTask t) where TTask : Task + { + } + + static void Main() + { + M(Task.FromResult(42)); + } + } + """; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + var m = GetInferredMethod(comp); + Assert.Equal("System.Threading.Tasks.Task", m.TypeArguments[0].ToTestDisplayString()); + Assert.Equal("System.Int32", m.TypeArguments[1].ToTestDisplayString()); + } + + [Fact] + public void AsyncLambda_InputTypeFromConstraint() + { + // TElement is inferred from the constraint (List : IEnumerable), and that + // inferred type then flows into the async lambda's parameter 'x' (typed as TElement), + // letting 'await Task.Delay(x)' bind with x : int. + var source = """ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + + class C + { + static void M(TEnumerable t, Func f) + where TEnumerable : IEnumerable + { + } + + static void Main() + { + M(new List(), async x => { await Task.Delay(x); }); + } + } + """; + + var comp = CreateCompilation(source, parseOptions: TestOptions.RegularNext); + comp.VerifyDiagnostics(); + var m = GetInferredMethod(comp); + Assert.Equal("System.Collections.Generic.List", m.TypeArguments[0].ToTestDisplayString()); + Assert.Equal("System.Int32", m.TypeArguments[1].ToTestDisplayString()); + } + + [Fact] + public void AsyncLambda_ReturnConflictsWithConstraint_OrdinaryBoundWins() + { + // The async lambda's Task return fixes TElement as int. Constraint inference does + // not replace that ordinary bound, so both language versions report the same constraint + // failure for List. + var source = """ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + + class C + { + static void M(TEnumerable t, Func> f) + where TEnumerable : IEnumerable + { + } + + static void Main() + { + M(new List(), async () => 42); + } + } + """; + + CreateCompilation(source, parseOptions: TestOptions.RegularNext).VerifyDiagnostics( + // (14,9): error CS0311: The type 'System.Collections.Generic.List' cannot be used as type parameter 'TEnumerable' in the generic type or method 'C.M(TEnumerable, Func>)'. There is no implicit reference conversion from 'System.Collections.Generic.List' to 'System.Collections.Generic.IEnumerable'. + // M(new List(), async () => 42); + Diagnostic(ErrorCode.ERR_GenericConstraintNotSatisfiedRefType, "M").WithArguments("C.M(TEnumerable, System.Func>)", "System.Collections.Generic.IEnumerable", "TEnumerable", "System.Collections.Generic.List").WithLocation(14, 9)); + + CreateCompilation(source, parseOptions: TestOptions.Regular15).VerifyDiagnostics( + // (14,9): error CS0311: The type 'System.Collections.Generic.List' cannot be used as type parameter 'TEnumerable' in the generic type or method 'C.M(TEnumerable, Func>)'. There is no implicit reference conversion from 'System.Collections.Generic.List' to 'System.Collections.Generic.IEnumerable'. + // M(new List(), async () => 42); + Diagnostic(ErrorCode.ERR_GenericConstraintNotSatisfiedRefType, "M").WithArguments("C.M(TEnumerable, System.Func>)", "System.Collections.Generic.IEnumerable", "TEnumerable", "System.Collections.Generic.List").WithLocation(14, 9)); + } + } +} \ No newline at end of file diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index 73242f3b3bc44..b1f9e46381930 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -110052,8 +110052,34 @@ static void M(U u) } "; - var comp = CreateCompilation(new[] { source }, options: WithNullableEnable()); + var comp = CreateCompilation(new[] { source }, parseOptions: TestOptions.RegularNext, options: WithNullableEnable()); // https://github.com/dotnet/roslyn/issues/29983: Should not report warning for `x6.ToString()`. + // Note: `CopyOutInherit(u, out var x7)` now succeeds because T2 is inferred from the + // constraint `where T1 : T2` (type parameter inference from constraints). + comp.VerifyDiagnostics( + // (10,9): warning CS8602: Dereference of a possibly null reference. + // x1.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x1").WithLocation(10, 9), + // (13,9): warning CS8602: Dereference of a possibly null reference. + // x2.ToString(); // 2 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x2").WithLocation(13, 9), + // (16,9): warning CS8602: Dereference of a possibly null reference. + // x3.ToString(); // 3 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x3").WithLocation(16, 9), + // (21,9): warning CS8602: Dereference of a possibly null reference. + // x4.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x4").WithLocation(21, 9), + // (24,9): warning CS8602: Dereference of a possibly null reference. + // x5.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x5").WithLocation(24, 9), + // (27,9): warning CS8602: Dereference of a possibly null reference. + // x6.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x6").WithLocation(27, 9), + // (30,9): warning CS8602: Dereference of a possibly null reference. + // x7.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "x7").WithLocation(30, 9)); + + comp = CreateCompilation(new[] { source }, parseOptions: TestOptions.Regular15, options: WithNullableEnable()); comp.VerifyDiagnostics( // (29,9): error CS0411: The type arguments for method 'C.CopyOutInherit(T1, out T2)' cannot be inferred from the usage. Try specifying the type arguments explicitly. // CopyOutInherit(u, out var x7); diff --git a/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj b/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj index c4b23686d08f1..bf70991fb92c0 100644 --- a/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj +++ b/src/Compilers/Core/Portable/Microsoft.CodeAnalysis.csproj @@ -65,6 +65,7 @@ + diff --git a/src/Compilers/Test/Core/Microsoft.CodeAnalysis.Test.Utilities.csproj b/src/Compilers/Test/Core/Microsoft.CodeAnalysis.Test.Utilities.csproj index aeb06a363d3e8..b26d553bdf520 100644 --- a/src/Compilers/Test/Core/Microsoft.CodeAnalysis.Test.Utilities.csproj +++ b/src/Compilers/Test/Core/Microsoft.CodeAnalysis.Test.Utilities.csproj @@ -25,6 +25,7 @@ + diff --git a/src/Compilers/Test/Utilities/CSharp/Microsoft.CodeAnalysis.CSharp.Test.Utilities.csproj b/src/Compilers/Test/Utilities/CSharp/Microsoft.CodeAnalysis.CSharp.Test.Utilities.csproj index ce75935481220..09ed40f6cf237 100644 --- a/src/Compilers/Test/Utilities/CSharp/Microsoft.CodeAnalysis.CSharp.Test.Utilities.csproj +++ b/src/Compilers/Test/Utilities/CSharp/Microsoft.CodeAnalysis.CSharp.Test.Utilities.csproj @@ -25,6 +25,7 @@ + diff --git a/src/Scripting/CSharp/Microsoft.CodeAnalysis.CSharp.Scripting.csproj b/src/Scripting/CSharp/Microsoft.CodeAnalysis.CSharp.Scripting.csproj index 4867bd1e572bf..0a1ed8708fb06 100644 --- a/src/Scripting/CSharp/Microsoft.CodeAnalysis.CSharp.Scripting.csproj +++ b/src/Scripting/CSharp/Microsoft.CodeAnalysis.CSharp.Scripting.csproj @@ -27,6 +27,7 @@ + diff --git a/src/Test/PdbUtilities/Roslyn.Test.PdbUtilities.csproj b/src/Test/PdbUtilities/Roslyn.Test.PdbUtilities.csproj index 02beb78bd1d39..49cc644b4df0f 100644 --- a/src/Test/PdbUtilities/Roslyn.Test.PdbUtilities.csproj +++ b/src/Test/PdbUtilities/Roslyn.Test.PdbUtilities.csproj @@ -15,6 +15,7 @@ +