1
ļ»æ// Copyright (c) 2010-2013 AlphaSierraPapa for the SharpDevelop Team
3
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
4
// software and associated documentation files (the "Software"), to deal in the Software
5
// without restriction, including without limitation the rights to use, copy, modify, merge,
6
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
7
// to whom the Software is furnished to do so, subject to the following conditions:
9
// The above copyright notice and this permission notice shall be included in all copies or
10
// substantial portions of the Software.
12
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
13
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
15
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
17
// DEALINGS IN THE SOFTWARE.
20
using System.Collections;
21
using System.Collections.Generic;
22
using System.Collections.ObjectModel;
24
using System.Runtime.CompilerServices;
25
using ICSharpCode.NRefactory.Semantics;
26
using ICSharpCode.NRefactory.TypeSystem;
27
using ICSharpCode.NRefactory.TypeSystem.Implementation;
28
using NUnit.Framework;
30
namespace ICSharpCode.NRefactory.CSharp.Resolver
33
public class TypeInferenceTests : ResolverTestBase
40
ti = new TypeInference(compilation);
43
#region Type Inference
44
IType[] Resolve(params Type[] types)
46
IType[] r = new IType[types.Length];
47
for (int i = 0; i < types.Length; i++) {
48
r[i] = compilation.FindType(types[i]);
49
Assert.AreNotSame(r[i], SpecialType.UnknownType);
51
Array.Sort(r, (a,b)=>a.ReflectionName.CompareTo(b.ReflectionName));
56
public void ArrayToEnumerable()
58
ITypeParameter tp = new DefaultTypeParameter(compilation, EntityType.Method, 0, "T");
59
IType stringType = compilation.FindType(KnownTypeCode.String);
60
ITypeDefinition enumerableType = compilation.FindType(KnownTypeCode.IEnumerableOfT).GetDefinition();
64
new [] { stringType },
65
ti.InferTypeArguments(new [] { tp },
66
new [] { new ResolveResult(new ArrayType(compilation, stringType)) },
67
new [] { new ParameterizedType(enumerableType, new [] { tp }) },
69
Assert.IsTrue(success);
73
public void ArrayToReadOnlyList()
75
ITypeParameter tp = new DefaultTypeParameter(compilation, EntityType.Method, 0, "T");
76
IType stringType = compilation.FindType(KnownTypeCode.String);
77
ITypeDefinition readOnlyListType = compilation.FindType(KnownTypeCode.IReadOnlyListOfT).GetDefinition();
78
if (readOnlyListType == null)
79
Assert.Ignore(".NET 4.5 IReadOnlyList not available");
83
new [] { stringType },
84
ti.InferTypeArguments(new [] { tp },
85
new [] { new ResolveResult(new ArrayType(compilation, stringType)) },
86
new [] { new ParameterizedType(readOnlyListType, new [] { tp }) },
88
Assert.IsTrue(success);
92
public void EnumerableToArrayInContravariantType()
94
ITypeParameter tp = new DefaultTypeParameter(compilation, EntityType.Method, 0, "T");
95
IType stringType = compilation.FindType(KnownTypeCode.String);
96
ITypeDefinition enumerableType = compilation.FindType(typeof(IEnumerable<>)).GetDefinition();
97
ITypeDefinition comparerType = compilation.FindType(typeof(IComparer<>)).GetDefinition();
99
var comparerOfIEnumerableOfString = new ParameterizedType(comparerType, new [] { new ParameterizedType(enumerableType, new [] { stringType } ) });
100
var comparerOfTpArray = new ParameterizedType(comparerType, new [] { new ArrayType(compilation, tp) });
104
new [] { stringType },
105
ti.InferTypeArguments(new [] { tp },
106
new [] { new ResolveResult(comparerOfIEnumerableOfString) },
107
new [] { comparerOfTpArray },
109
Assert.IsTrue(success);
113
public void InferFromObjectAndFromNullLiteral()
116
ITypeParameter tp = new DefaultTypeParameter(compilation, EntityType.Method, 0, "T");
118
// M(new object(), null);
121
new [] { compilation.FindType(KnownTypeCode.Object) },
122
ti.InferTypeArguments(new [] { tp },
123
new [] { new ResolveResult(compilation.FindType(KnownTypeCode.Object)), new ResolveResult(SpecialType.NullType) },
126
Assert.IsTrue(success);
130
public void ArrayToListWithArrayCovariance()
132
ITypeParameter tp = new DefaultTypeParameter(compilation, EntityType.Method, 0, "T");
133
IType objectType = compilation.FindType(KnownTypeCode.Object);
134
IType stringType = compilation.FindType(KnownTypeCode.String);
135
ITypeDefinition listType = compilation.FindType(KnownTypeCode.IListOfT).GetDefinition();
137
// void M<T>(IList<T> a, T b);
138
// M(new string[0], new object());
142
new [] { objectType },
143
ti.InferTypeArguments(
145
new [] { new ResolveResult(new ArrayType(compilation, stringType)), new ResolveResult(objectType) },
146
new [] { new ParameterizedType(listType, new [] { tp }), (IType)tp },
148
Assert.IsTrue(success);
152
public void IEnumerableCovarianceWithDynamic()
154
ITypeParameter tp = new DefaultTypeParameter(compilation, EntityType.Method, 0, "T");
155
var ienumerableOfT = new ParameterizedType(compilation.FindType(typeof(IEnumerable<>)).GetDefinition(), new[] { tp });
156
var ienumerableOfString = compilation.FindType(typeof(IEnumerable<string>));
157
var ienumerableOfDynamic = compilation.FindType(typeof(IEnumerable<ReflectionHelper.Dynamic>));
159
// static T M<T>(IEnumerable<T> x, IEnumerable<T> y) {}
160
// M(IEnumerable<dynamic>, IEnumerable<string>); -> should infer T=dynamic, no ambiguity
161
// See http://blogs.msdn.com/b/cburrows/archive/2010/04/01/errata-dynamic-conversions-and-overload-resolution.aspx
166
new [] { SpecialType.Dynamic },
167
ti.InferTypeArguments(
169
new [] { new ResolveResult(ienumerableOfDynamic), new ResolveResult(ienumerableOfString) },
170
new [] { ienumerableOfT, ienumerableOfT },
172
Assert.IsTrue(success);
176
#region Inference with Method Groups
178
public void CannotInferFromMethodParameterTypes()
180
// static void M<A, B>(Func<A, B> f) {}
181
// M(int.Parse); // type inference fails
182
var A = new DefaultTypeParameter(compilation, EntityType.Method, 0, "A");
183
var B = new DefaultTypeParameter(compilation, EntityType.Method, 1, "B");
185
IType declType = compilation.FindType(typeof(int));
186
var methods = new MethodListWithDeclaringType(declType, declType.GetMethods(m => m.Name == "Parse"));
187
var argument = new MethodGroupResolveResult(new TypeResolveResult(declType), "Parse", new[] { methods }, new IType[0]);
190
ti.InferTypeArguments(new [] { A, B }, new [] { argument },
191
new [] { new ParameterizedType(compilation.FindType(typeof(Func<,>)).GetDefinition(), new[] { A, B }) },
193
Assert.IsFalse(success);
197
public void InferFromMethodReturnType()
199
// static void M<T>(Func<T> f) {}
200
// M(Console.ReadKey); // type inference produces ConsoleKeyInfo
202
var T = new DefaultTypeParameter(compilation, EntityType.Method, 0, "T");
204
IType declType = compilation.FindType(typeof(Console));
205
var methods = new MethodListWithDeclaringType(declType, declType.GetMethods(m => m.Name == "ReadKey"));
206
var argument = new MethodGroupResolveResult(new TypeResolveResult(declType), "ReadKey", new[] { methods }, new IType[0]);
210
new [] { compilation.FindType(typeof(ConsoleKeyInfo)) },
211
ti.InferTypeArguments(new [] { T }, new [] { argument },
212
new [] { new ParameterizedType(compilation.FindType(typeof(Func<>)).GetDefinition(), new[] { T }) },
214
Assert.IsTrue(success);
218
#region Inference with Lambda
219
#region MockImplicitLambda
220
sealed class MockImplicitLambda : LambdaResolveResult
222
IType[] expectedParameterTypes;
223
IType inferredReturnType;
224
IParameter[] parameters;
226
public MockImplicitLambda(IType[] expectedParameterTypes, IType inferredReturnType)
228
this.expectedParameterTypes = expectedParameterTypes;
229
this.inferredReturnType = inferredReturnType;
230
this.parameters = new IParameter[expectedParameterTypes.Length];
231
for (int i = 0; i < parameters.Length; i++) {
232
// UnknownType because this lambda is implicitly typed
233
parameters[i] = new DefaultParameter(SpecialType.UnknownType, "X" + i);
237
public override IList<IParameter> Parameters {
238
get { return parameters; }
241
public override Conversion IsValid(IType[] parameterTypes, IType returnType, CSharpConversions conversions)
243
Assert.AreEqual(expectedParameterTypes, parameterTypes);
244
return conversions.ImplicitConversion(inferredReturnType, returnType);
247
public override bool IsImplicitlyTyped {
251
public override bool IsAnonymousMethod {
252
get { return false; }
255
public override bool HasParameterList {
259
public override bool IsAsync {
260
get { return false; }
263
public override ResolveResult Body {
264
get { throw new NotImplementedException(); }
267
public override IType GetInferredReturnType(IType[] parameterTypes)
269
Assert.AreEqual(expectedParameterTypes, parameterTypes, "Parameters types passed to " + this);
270
return inferredReturnType;
273
public override string ToString()
275
return "[MockImplicitLambda (" + string.Join<IType>(", ", expectedParameterTypes) + ") => " + inferredReturnType + "]";
281
public void TestLambdaInference()
283
ITypeParameter[] typeParameters = {
284
new DefaultTypeParameter(compilation, EntityType.Method, 0, "X"),
285
new DefaultTypeParameter(compilation, EntityType.Method, 1, "Y"),
286
new DefaultTypeParameter(compilation, EntityType.Method, 2, "Z")
288
IType[] parameterTypes = {
290
new ParameterizedType(compilation.FindType(typeof(Func<,>)).GetDefinition(), new[] { typeParameters[0], typeParameters[1] }),
291
new ParameterizedType(compilation.FindType(typeof(Func<,>)).GetDefinition(), new[] { typeParameters[1], typeParameters[2] })
293
// Signature: M<X,Y,Z>(X x, Func<X,Y> y, Func<Y,Z> z) {}
294
// Invocation: M(default(string), s => default(int), t => default(float));
295
ResolveResult[] arguments = {
296
new ResolveResult(compilation.FindType(KnownTypeCode.String)),
297
new MockImplicitLambda(new[] { compilation.FindType(KnownTypeCode.String) }, compilation.FindType(KnownTypeCode.Int32)),
298
new MockImplicitLambda(new[] { compilation.FindType(KnownTypeCode.Int32) }, compilation.FindType(KnownTypeCode.Single))
303
compilation.FindType(KnownTypeCode.String),
304
compilation.FindType(KnownTypeCode.Int32),
305
compilation.FindType(KnownTypeCode.Single)
307
ti.InferTypeArguments(typeParameters, arguments, parameterTypes, out success));
308
Assert.IsTrue(success);
312
public void ConvertAllLambdaInference()
314
ITypeParameter[] classTypeParameters = { new DefaultTypeParameter(compilation, EntityType.TypeDefinition, 0, "T") };
315
ITypeParameter[] methodTypeParameters = { new DefaultTypeParameter(compilation, EntityType.Method, 0, "R") };
317
IType[] parameterTypes = {
318
new ParameterizedType(compilation.FindType(typeof(Converter<,>)).GetDefinition(),
319
new[] { classTypeParameters[0], methodTypeParameters[0] })
322
// Signature: List<T>.ConvertAll<R>(Converter<T, R> converter);
323
// Invocation: listOfString.ConvertAll(s => default(int));
324
ResolveResult[] arguments = {
325
new MockImplicitLambda(new[] { compilation.FindType(KnownTypeCode.String) }, compilation.FindType(KnownTypeCode.Int32))
327
IType[] classTypeArguments = {
328
compilation.FindType(KnownTypeCode.String)
333
new [] { compilation.FindType(KnownTypeCode.Int32) },
334
ti.InferTypeArguments(methodTypeParameters, arguments, parameterTypes, out success, classTypeArguments));
338
public void InferFromImplicitAsyncLambda()
340
string program = @"using System;
341
using System.Threading.Tasks;
345
static T M<T>(Func<int, Task<T>> f)
349
public static void Test()
351
$M(async x => x + 1)$;
355
var rr = Resolve<CSharpInvocationResolveResult>(program);
356
Assert.IsFalse(rr.IsError);
357
Assert.AreEqual("System.Int32", ((IMethod)rr.Member).TypeArguments[0].FullName);
361
public void InferFromExplicitAsyncLambda()
363
string program = @"using System;
364
using System.Threading.Tasks;
368
static T M<T>(Func<int, Task<T>> f)
372
public static void Test()
374
$M(async (int x) => x + 1)$;
378
var rr = Resolve<CSharpInvocationResolveResult>(program);
379
Assert.IsFalse(rr.IsError);
380
Assert.AreEqual("System.Int32", ((IMethod)rr.Member).TypeArguments[0].FullName);
384
#region FindTypeInBounds
385
IType[] FindAllTypesInBounds(IList<IType> lowerBounds, IList<IType> upperBounds = null)
387
ti.Algorithm = TypeInferenceAlgorithm.ImprovedReturnAllResults;
388
IType type = ti.FindTypeInBounds(lowerBounds, upperBounds ?? new IType[0]);
389
return ExpandIntersections(type).OrderBy(t => t.ReflectionName).ToArray();
392
static IEnumerable<IType> ExpandIntersections(IType type)
394
IntersectionType it = type as IntersectionType;
396
return it.Types.SelectMany(t => ExpandIntersections(t));
398
ParameterizedType pt = type as ParameterizedType;
400
IType[][] typeArguments = new IType[pt.TypeArguments.Count][];
401
for (int i = 0; i < typeArguments.Length; i++) {
402
typeArguments[i] = ExpandIntersections(pt.TypeArguments[i]).ToArray();
404
return AllCombinations(typeArguments).Select(ta => new ParameterizedType(pt.GetDefinition(), ta));
406
return new [] { type };
410
/// Performs the combinatorial explosion.
412
static IEnumerable<IType[]> AllCombinations(IType[][] typeArguments)
414
int[] index = new int[typeArguments.Length];
415
index[typeArguments.Length - 1] = -1;
418
for (i = index.Length - 1; i >= 0; i--) {
419
if (++index[i] == typeArguments[i].Length)
426
IType[] r = new IType[typeArguments.Length];
427
for (i = 0; i < r.Length; i++) {
428
r[i] = typeArguments[i][index[i]];
435
public void ListOfShortAndInt()
438
Resolve(typeof(IList)),
439
FindAllTypesInBounds(Resolve(typeof(List<short>), typeof(List<int>))));
443
[Ignore("Produces different results in .NET 4.5 due to new read-only interfaces")]
444
public void ListOfStringAndObject()
447
Resolve(typeof(IList), typeof(IEnumerable<object>)),
448
FindAllTypesInBounds(Resolve(typeof(List<string>), typeof(List<object>))));
452
[Ignore("Produces different results in .NET 4.5 due to new read-only interfaces")]
453
public void ListOfListOfStringAndObject()
456
Resolve(typeof(IList), typeof(IEnumerable<IList>), typeof(IEnumerable<IEnumerable<object>>)),
457
FindAllTypesInBounds(Resolve(typeof(List<List<string>>), typeof(List<List<object>>))));
461
public void ShortAndInt()
464
Resolve(typeof(int)),
465
FindAllTypesInBounds(Resolve(typeof(short), typeof(int))));
469
public void StringAndVersion()
472
Resolve(typeof(ICloneable), typeof(IComparable)),
473
FindAllTypesInBounds(Resolve(typeof(string), typeof(Version))));
477
public void CommonSubTypeClonableComparable()
480
Resolve(typeof(string), typeof(Version)),
481
FindAllTypesInBounds(Resolve(), Resolve(typeof(ICloneable), typeof(IComparable))));
485
public void EnumerableOfStringAndVersion()
488
Resolve(typeof(IEnumerable<ICloneable>), typeof(IEnumerable<IComparable>)),
489
FindAllTypesInBounds(Resolve(typeof(IList<string>), typeof(IList<Version>))));
493
public void CommonSubTypeIEnumerableClonableIEnumerableComparable()
496
Resolve(typeof(IEnumerable<string>), typeof(IEnumerable<Version>)),
497
FindAllTypesInBounds(Resolve(), Resolve(typeof(IEnumerable<ICloneable>), typeof(IEnumerable<IComparable>))));
501
public void CommonSubTypeIEnumerableClonableIEnumerableComparableList()
504
Resolve(typeof(List<string>), typeof(List<Version>), typeof(Collection<string>), typeof(Collection<Version>),
505
typeof(ReadOnlyCollectionBuilder<string>), typeof(ReadOnlyCollectionBuilder<Version>),
506
typeof(ReadOnlyCollection<string>), typeof(ReadOnlyCollection<Version>)),
507
FindAllTypesInBounds(Resolve(), Resolve(typeof(IEnumerable<ICloneable>), typeof(IEnumerable<IComparable>), typeof(IList))));
512
public void NullablePick()
515
interface ICo<out T> {}
516
interface IContra<in T> {}
519
static T Pick<T> (T? a, T? b)
523
public static void Test(int? i, long? l)
529
var mrr = Resolve<CSharpInvocationResolveResult>(program);
530
Assert.AreEqual("System.Int64", mrr.Type.FullName);
531
Assert.IsFalse(mrr.IsError);
535
public void CoContraPick()
538
interface ICo<out T> {}
539
interface IContra<in T> {}
542
static T Pick<T> (ICo<T> a, IContra<T> b)
546
public static void Test(ICo<string> i, IContra<object> l)
552
// String and Object are both valid choices; and csc ends up picking object,
553
// even though the C# specification says it should pick string:
554
// 7.5.2.11 Fixing - both string and object are in the candidate set;
555
// string has a conversion to object (the other candidate),
556
// object doesn't have that; so string should be chosen as the result.
558
// We follow the csc behavior.
559
var mrr = Resolve<CSharpInvocationResolveResult>(program);
560
Assert.AreEqual("System.Object", mrr.Type.FullName);
561
Assert.IsFalse(mrr.IsError);
565
/// Bug 9300 - Unknown Resolve Error
568
public void TestBug9300()
570
string program = @"struct S
572
public static implicit operator string (S s)
584
static T Foo<T> (T a, I<T> b)
589
public static void Main ()
592
I<string> i = new C ();
593
var result = $Foo (s, i)$;
597
var mrr = Resolve<CSharpInvocationResolveResult>(program);
598
Assert.AreEqual("System.String", mrr.Type.FullName);
599
Assert.IsFalse(mrr.IsError);