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