Skip to content

Commit

Permalink
Merge pull request #11807 from gafter/master-errlambda04
Browse files Browse the repository at this point in the history
Improved error recovery for lambdas in a call with extra arguments.
  • Loading branch information
gafter authored Jun 13, 2016
2 parents 85f6003 + dfa0aeb commit d19ec6f
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,12 @@ private enum Dependency
private Dependency[,] _dependencies; // Initialized lazily
private bool _dependenciesDirty;

/// <summary>
/// For error recovery, we allow a mismatch between the number of arguments and parameters
/// during type inference. This sometimes enables inferring the type for a lambda parameter.
/// </summary>
private int NumberArgumentsToProcess => System.Math.Min(_arguments.Length, _formalParameterTypes.Length);

public static MethodTypeInferenceResult Infer(
Binder binder,
ImmutableArray<TypeParameterSymbol> methodTypeParameters,
Expand Down Expand Up @@ -524,14 +530,13 @@ private void InferTypeArgsFirstPhase(Binder binder, ref HashSet<DiagnosticInfo>
{
Debug.Assert(!_formalParameterTypes.IsDefault);
Debug.Assert(!_arguments.IsDefault);
Debug.Assert(_arguments.Length == _formalParameterTypes.Length);

// We expect that we have been handed a list of arguments and a list of the
// formal parameter types they correspond to; all the details about named and
// optional parameters have already been dealt with.

// SPEC: For each of the method arguments Ei:
for (int arg = 0; arg < _arguments.Length; arg++)
for (int arg = 0, length = this.NumberArgumentsToProcess; arg < length; arg++)
{
var argument = _arguments[arg];

Expand Down Expand Up @@ -783,7 +788,7 @@ private void MakeOutputTypeInferences(Binder binder, ref HashSet<DiagnosticInfo>
// SPEC: where the output types contain unfixed type parameters but the input
// SPEC: types do not, an output type inference is made from Ei to Ti.

for (int arg = 0; arg < _arguments.Length; arg++)
for (int arg = 0, length = this.NumberArgumentsToProcess; arg < length; arg++)
{
var formalType = _formalParameterTypes[arg];
var argument = _arguments[arg];
Expand Down Expand Up @@ -1043,7 +1048,7 @@ private bool DependsDirectlyOn(int iParam, int jParam)
Debug.Assert(IsUnfixed(iParam));
Debug.Assert(IsUnfixed(jParam));

for (int iArg = 0; iArg < _arguments.Length; iArg++)
for (int iArg = 0, length = this.NumberArgumentsToProcess; iArg < length; iArg++)
{
var formalParameterType = _formalParameterTypes[iArg];
var argument = _arguments[iArg];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2394,8 +2394,8 @@ private EffectiveParameters GetEffectiveParametersInNormalForm<TMember>(
for (int arg = 0; arg < argumentCount; ++arg)
{
int parm = argToParamMap.IsDefault ? arg : argToParamMap[arg];
// If this is the __arglist parameter, just skip it.
if (parm == parameters.Length)
// If this is the __arglist parameter, or an extra argument in error situations, just skip it.
if (parm >= parameters.Length)
{
continue;
}
Expand Down Expand Up @@ -2507,12 +2507,17 @@ internal MemberResolutionResult<TMember> IsMemberApplicableInNormalForm<TMember>
var argumentAnalysis = AnalyzeArguments(member, arguments, isMethodGroupConversion, expanded: false);
if (!argumentAnalysis.IsValid)
{
// When we are producing more complete results, and a required parameter is missing, we push on
// to type inference so that lambda arguments can be bound to their delegate-typed parameters,
// thus improving the API and intellisense experience.
if (!completeResults || argumentAnalysis.Kind != ArgumentAnalysisResultKind.RequiredParameterMissing)
switch (argumentAnalysis.Kind)
{
return new MemberResolutionResult<TMember>(member, leastOverriddenMember, MemberAnalysisResult.ArgumentParameterMismatch(argumentAnalysis));
case ArgumentAnalysisResultKind.RequiredParameterMissing:
case ArgumentAnalysisResultKind.NoCorrespondingParameter:
if (!completeResults) goto default;
// When we are producing more complete results, and we have the wrong number of arguments, we push on
// through type inference so that lambda arguments can be bound to their delegate-typed parameters,
// thus improving the API and intellisense experience.
break;
default:
return new MemberResolutionResult<TMember>(member, leastOverriddenMember, MemberAnalysisResult.ArgumentParameterMismatch(argumentAnalysis));
}
}

Expand Down Expand Up @@ -2812,10 +2817,6 @@ private MemberAnalysisResult IsApplicable(
// treating the method as inapplicable.
paramCount = arguments.Arguments.Count;
}
else
{
Debug.Assert(paramCount == arguments.Arguments.Count);
}

// For each argument in A, the parameter passing mode of the argument (i.e., value, ref, or out) is
// identical to the parameter passing mode of the corresponding parameter, and
Expand Down
113 changes: 113 additions & 0 deletions src/Compilers/CSharp/Test/Semantic/Semantics/LambdaTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1963,5 +1963,118 @@ public TSource FirstOrDefault(Func<TSource, bool> predicate, params TSource[] de
Assert.Equal("String", typeInfo.Type.Name);
Assert.NotEmpty(typeInfo.Type.GetMembers("Replace"));
}

[Fact]
[WorkItem(557, "https://github.com/dotnet/roslyn/issues/557")]
public void TestLambdaWithError11()
{
var source =
@"using System.Linq;
public static class Program
{
public static void Main()
{
var x = new {
X = """".Select(c => c.
Y = 0,
};
}
}
";
var compilation = CreateCompilationWithMscorlibAndSystemCore(source);
var tree = compilation.SyntaxTrees[0];
var sm = compilation.GetSemanticModel(tree);
var lambda = tree.GetCompilationUnitRoot().DescendantNodes().OfType<LambdaExpressionSyntax>().Single();
var eReference = lambda.Body.DescendantNodes().OfType<IdentifierNameSyntax>().First();
Assert.Equal("c", eReference.ToString());
var typeInfo = sm.GetTypeInfo(eReference);
Assert.Equal(TypeKind.Struct, typeInfo.Type.TypeKind);
Assert.Equal("Char", typeInfo.Type.Name);
Assert.NotEmpty(typeInfo.Type.GetMembers("IsHighSurrogate")); // check it is the char we know and love
}

[Fact]
[WorkItem(5498, "https://github.com/dotnet/roslyn/issues/5498")]
public void TestLambdaWithError12()
{
var source =
@"using System.Linq;
class Program
{
static void Main(string[] args)
{
var z = args.Select(a => a.
var foo =
}
}";
var compilation = CreateCompilationWithMscorlibAndSystemCore(source);
var tree = compilation.SyntaxTrees[0];
var sm = compilation.GetSemanticModel(tree);
var lambda = tree.GetCompilationUnitRoot().DescendantNodes().OfType<LambdaExpressionSyntax>().Single();
var eReference = lambda.Body.DescendantNodes().OfType<IdentifierNameSyntax>().First();
Assert.Equal("a", eReference.ToString());
var typeInfo = sm.GetTypeInfo(eReference);
Assert.Equal(TypeKind.Class, typeInfo.Type.TypeKind);
Assert.Equal("String", typeInfo.Type.Name);
Assert.NotEmpty(typeInfo.Type.GetMembers("Replace"));
}

[WorkItem(5498, "https://github.com/dotnet/roslyn/issues/5498")]
[WorkItem(11358, "https://github.com/dotnet/roslyn/issues/11358")]
[Fact]
public void TestLambdaWithError13()
{
// These tests ensure we attempt to perform type inference and bind a lambda expression
// argument even when there are too many or too few arguments to an invocation, in the
// case when there is more than one method in the method group.
// See https://github.com/dotnet/roslyn/issues/11901 for the case of one method in the group
var source =
@"using System;
class Program
{
static void Main(string[] args)
{
Thing<string> t = null;
t.X1(x => x, 1); // too many args
t.X2(x => x); // too few args
t.M2(string.Empty, x => x, 1); // too many args
t.M3(string.Empty, x => x); // too few args
}
}
public class Thing<T>
{
public void M2<T>(T x, Func<T, T> func) {}
public void M3<T>(T x, Func<T, T> func, T y) {}
// Ensure we have more than one method in the method group
public void M2() {}
public void M3() {}
}
public static class XThing
{
public static Thing<T> X1<T>(this Thing<T> self, Func<T, T> func) => null;
public static Thing<T> X2<T>(this Thing<T> self, Func<T, T> func, int i) => null;
// Ensure we have more than one method in the method group
public static void X1(this object self) {}
public static void X2(this object self) {}
}
";
var compilation = CreateCompilationWithMscorlibAndSystemCore(source);
var tree = compilation.SyntaxTrees[0];
var sm = compilation.GetSemanticModel(tree);
foreach (var lambda in tree.GetRoot().DescendantNodes().OfType<LambdaExpressionSyntax>())
{
var reference = lambda.Body.DescendantNodesAndSelf().OfType<IdentifierNameSyntax>().First();
Assert.Equal("x", reference.ToString());
var typeInfo = sm.GetTypeInfo(reference);
Assert.Equal(TypeKind.Class, typeInfo.Type.TypeKind);
Assert.Equal("String", typeInfo.Type.Name);
Assert.NotEmpty(typeInfo.Type.GetMembers("Replace"));
}
}
}
}

0 comments on commit d19ec6f

Please sign in to comment.