Skip to content

Commit 9582ecc

Browse files
authored
Optimize typeof(T1).IsAssignableFrom(typeof(T2)) (#1195)
* Optimize Type.IsAssignableFrom * Add TypeIntrinsics.IsAssignableFrom to TypeIntrinsics.csproj * Formatting * Add License header * Remove assert * fix "mustexpand assert" * Add tests for Arrays of vt
2 parents ea5e953 + 78a0a3b commit 9582ecc

File tree

7 files changed

+313
-12
lines changed

7 files changed

+313
-12
lines changed

src/coreclr/src/jit/importer.cpp

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3447,8 +3447,8 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis,
34473447

34483448
if ((methodFlags & CORINFO_FLG_JIT_INTRINSIC) != 0)
34493449
{
3450-
// The recursive calls to Jit intrinsics are must-expand by convention.
3451-
mustExpand = mustExpand || gtIsRecursiveCall(method);
3450+
// The recursive non-virtual calls to Jit intrinsics are must-expand by convention.
3451+
mustExpand = mustExpand || (gtIsRecursiveCall(method) && !(methodFlags & CORINFO_FLG_VIRTUAL));
34523452

34533453
if (intrinsicID == CORINFO_INTRINSIC_Illegal)
34543454
{
@@ -4014,6 +4014,49 @@ GenTree* Compiler::impIntrinsic(GenTree* newobjThis,
40144014
break;
40154015
}
40164016

4017+
case NI_System_Type_IsAssignableFrom:
4018+
{
4019+
// Optimize patterns like:
4020+
//
4021+
// typeof(TTo).IsAssignableFrom(typeof(TTFrom))
4022+
// valueTypeVar.GetType().IsAssignableFrom(typeof(TTFrom))
4023+
//
4024+
// to true/false
4025+
GenTree* typeTo = impStackTop(1).val;
4026+
GenTree* typeFrom = impStackTop(0).val;
4027+
4028+
if (typeTo->IsCall() && typeFrom->IsCall())
4029+
{
4030+
// make sure both arguments are `typeof()`
4031+
CORINFO_METHOD_HANDLE hTypeof = eeFindHelper(CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE);
4032+
if ((typeTo->AsCall()->gtCallMethHnd == hTypeof) && (typeFrom->AsCall()->gtCallMethHnd == hTypeof))
4033+
{
4034+
CORINFO_CLASS_HANDLE hClassTo =
4035+
gtGetHelperArgClassHandle(typeTo->AsCall()->gtCallArgs->GetNode());
4036+
CORINFO_CLASS_HANDLE hClassFrom =
4037+
gtGetHelperArgClassHandle(typeFrom->AsCall()->gtCallArgs->GetNode());
4038+
4039+
if (hClassTo == NO_CLASS_HANDLE || hClassFrom == NO_CLASS_HANDLE)
4040+
{
4041+
break;
4042+
}
4043+
4044+
TypeCompareState castResult = info.compCompHnd->compareTypesForCast(hClassFrom, hClassTo);
4045+
if (castResult == TypeCompareState::May)
4046+
{
4047+
// requires runtime check
4048+
// e.g. __Canon, COMObjects, Nullable
4049+
break;
4050+
}
4051+
4052+
retNode = gtNewIconNode((castResult == TypeCompareState::Must) ? 1 : 0);
4053+
impPopStack(); // drop both CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE calls
4054+
impPopStack();
4055+
}
4056+
}
4057+
break;
4058+
}
4059+
40174060
case NI_System_Type_get_IsValueType:
40184061
{
40194062
// Optimize
@@ -4341,6 +4384,10 @@ NamedIntrinsic Compiler::lookupNamedIntrinsic(CORINFO_METHOD_HANDLE method)
43414384
{
43424385
result = NI_System_Type_get_IsValueType;
43434386
}
4387+
else if (strcmp(methodName, "IsAssignableFrom") == 0)
4388+
{
4389+
result = NI_System_Type_IsAssignableFrom;
4390+
}
43444391
}
43454392
}
43464393
#if defined(TARGET_XARCH) // We currently only support BSWAP on x86
@@ -7603,9 +7650,6 @@ var_types Compiler::impImportCall(OPCODE opcode,
76037650

76047651
if (call != nullptr)
76057652
{
7606-
assert(!(mflags & CORINFO_FLG_VIRTUAL) || (mflags & CORINFO_FLG_FINAL) ||
7607-
(clsFlags & CORINFO_FLG_FINAL));
7608-
76097653
#ifdef FEATURE_READYTORUN_COMPILER
76107654
if (call->OperGet() == GT_INTRINSIC)
76117655
{

src/coreclr/src/jit/namedintrinsiclist.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ enum NamedIntrinsic : unsigned short
2020
NI_System_Buffers_Binary_BinaryPrimitives_ReverseEndianness,
2121
NI_System_GC_KeepAlive,
2222
NI_System_Type_get_IsValueType,
23+
NI_System_Type_IsAssignableFrom,
2324

2425
#ifdef FEATURE_HW_INTRINSICS
2526
NI_IsSupported_True,
Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Collections;
7+
using System.Collections.Generic;
8+
using System.Numerics;
9+
using System.Runtime.CompilerServices;
10+
using System.Runtime.Intrinsics;
11+
12+
public partial class Program
13+
{
14+
public static void TestIsAssignableFrom()
15+
{
16+
// Primitive types
17+
IsTrue (typeof(void).IsAssignableFrom(typeof(void)));
18+
IsTrue (typeof(byte).IsAssignableFrom(typeof(byte)));
19+
IsTrue (typeof(int).IsAssignableFrom(typeof(int)));
20+
IsTrue (typeof(float).IsAssignableFrom(typeof(float)));
21+
IsTrue (typeof(double).IsAssignableFrom(typeof(double)));
22+
IsTrue (typeof(byte*).IsAssignableFrom(typeof(byte*)));
23+
IsTrue (typeof(sbyte*).IsAssignableFrom(typeof(byte*)));
24+
IsTrue (typeof(void*).IsAssignableFrom(typeof(void*)));
25+
IsTrue (typeof(byte**).IsAssignableFrom(typeof(byte**)));
26+
IsFalse(typeof(byte).IsAssignableFrom(typeof(sbyte)));
27+
IsFalse(typeof(sbyte).IsAssignableFrom(typeof(byte)));
28+
IsFalse(typeof(int).IsAssignableFrom(typeof(long)));
29+
IsFalse(typeof(int).IsAssignableFrom(typeof(void)));
30+
IsFalse(typeof(void).IsAssignableFrom(typeof(long)));
31+
IsFalse(typeof(long).IsAssignableFrom(typeof(int)));
32+
IsFalse(typeof(float).IsAssignableFrom(typeof(double)));
33+
IsFalse(typeof(double).IsAssignableFrom(typeof(float)));
34+
IsFalse(typeof(double).IsAssignableFrom(typeof(long)));
35+
IsFalse(typeof(int).IsAssignableFrom(typeof(float)));
36+
IsFalse(typeof(sbyte*).IsAssignableFrom(typeof(ulong*)));
37+
IsFalse(typeof(sbyte*).IsAssignableFrom(typeof(void*)));
38+
IsFalse(typeof(void*).IsAssignableFrom(typeof(ulong*)));
39+
IsFalse(typeof(sbyte*).IsAssignableFrom(typeof(IntPtr)));
40+
IsFalse(typeof(IntPtr).IsAssignableFrom(typeof(sbyte*)));
41+
IsFalse(typeof(byte**).IsAssignableFrom(typeof(byte*)));
42+
IsFalse(typeof(byte*).IsAssignableFrom(typeof(byte**)));
43+
44+
// Nullable
45+
IsTrue (typeof(int?).IsAssignableFrom(typeof(int)));
46+
IsTrue (typeof(int?).IsAssignableFrom(typeof(int?)));
47+
IsTrue (typeof(GenericStruct1<int>?).IsAssignableFrom(typeof(GenericStruct1<int>?)));
48+
IsTrue (typeof(GenericStruct1<string>?).IsAssignableFrom(typeof(GenericStruct1<string>?)));
49+
IsTrue (typeof(SimpleEnum_int?).IsAssignableFrom(typeof(SimpleEnum_int?)));
50+
IsFalse(typeof(int).IsAssignableFrom(typeof(int?)));
51+
IsFalse(typeof(uint?).IsAssignableFrom(typeof(int?)));
52+
IsFalse(typeof(int?).IsAssignableFrom(typeof(uint?)));
53+
IsFalse(typeof(SimpleEnum_uint?).IsAssignableFrom(typeof(SimpleEnum_int?)));
54+
IsFalse(typeof(SimpleEnum_int?).IsAssignableFrom(typeof(SimpleEnum_uint?)));
55+
IsFalse(typeof(GenericStruct1<int>?).IsAssignableFrom(typeof(GenericStruct1<uint>?)));
56+
57+
// Enums
58+
IsTrue (typeof(SimpleEnum_int).IsAssignableFrom(typeof(SimpleEnum_int)));
59+
IsTrue (typeof(SimpleEnum_int?).IsAssignableFrom(typeof(SimpleEnum_int?)));
60+
IsTrue (typeof(SimpleEnum_uint).IsAssignableFrom(typeof(SimpleEnum_uint)));
61+
IsTrue (typeof(SimpleEnum_byte).IsAssignableFrom(typeof(SimpleEnum_byte)));
62+
IsTrue (typeof(ValueType).IsAssignableFrom(typeof(SimpleEnum_uint)));
63+
IsFalse(typeof(SimpleEnum_int).IsAssignableFrom(typeof(SimpleEnum_uint)));
64+
IsFalse(typeof(SimpleEnum_uint).IsAssignableFrom(typeof(SimpleEnum_int)));
65+
IsFalse(typeof(SimpleEnum_uint).IsAssignableFrom(typeof(SimpleEnum_byte)));
66+
IsFalse(typeof(SimpleEnum_byte).IsAssignableFrom(typeof(SimpleEnum_uint)));
67+
IsFalse(typeof(SimpleEnum_byte).IsAssignableFrom(typeof(byte)));
68+
IsFalse(typeof(byte).IsAssignableFrom(typeof(SimpleEnum_byte)));
69+
IsFalse(typeof(SimpleEnum_int).IsAssignableFrom(typeof(int)));
70+
IsFalse(typeof(int).IsAssignableFrom(typeof(SimpleEnum_int)));
71+
IsFalse(typeof(SimpleEnum_uint).IsAssignableFrom(typeof(float)));
72+
IsFalse(typeof(float).IsAssignableFrom(typeof(SimpleEnum_uint)));
73+
IsFalse(typeof(SimpleEnum_uint).IsAssignableFrom(typeof(ValueType)));
74+
75+
// Covariance/Contravariance
76+
IsTrue (typeof(IEnumerable<object>).IsAssignableFrom(typeof(List<string>)));
77+
IsTrue (typeof(IEnumerable<ClassA>).IsAssignableFrom(typeof(List<ClassB>)));
78+
IsTrue (typeof(IEnumerable<ClassA>).IsAssignableFrom(typeof(IList<ClassB>)));
79+
IsTrue (typeof(IEnumerable<ClassA>).IsAssignableFrom(typeof(IList<ClassD>)));
80+
IsTrue (typeof(IEnumerable<ClassA>).IsAssignableFrom(typeof(IList<ClassA>)));
81+
IsTrue (typeof(Action<string>).IsAssignableFrom(typeof(Action<object>)));
82+
IsTrue (typeof(object[]).IsAssignableFrom(typeof(string[])));
83+
IsTrue (typeof(object[,]).IsAssignableFrom(typeof(string[,])));
84+
IsTrue (typeof(SimpleEnum_int[,]).IsAssignableFrom(typeof(SimpleEnum_uint[,])));
85+
IsFalse(typeof(string[,]).IsAssignableFrom(typeof(object[,])));
86+
IsFalse(typeof(object[,]).IsAssignableFrom(typeof(string[,,])));
87+
IsFalse(typeof(IDictionary<ClassA, int>).IsAssignableFrom(typeof(IDictionary<ClassB, int>)));
88+
IsFalse(typeof(IDictionary<ClassA, int>).IsAssignableFrom(typeof(Dictionary<ClassB, int>)));
89+
IsFalse(typeof(Action<object>).IsAssignableFrom(typeof(Action<string>)));
90+
IsFalse(typeof(Action<object>).IsAssignableFrom(typeof(Action<Guid>)));
91+
IsFalse(typeof(List<string>).IsAssignableFrom(typeof(IEnumerable<object>)));
92+
IsFalse(typeof(Action<Guid>).IsAssignableFrom(typeof(Action<object>)));
93+
IsFalse(typeof(List<ClassB>).IsAssignableFrom(typeof(IEnumerable<ClassA>)));
94+
IsFalse(typeof(IList<ClassB>).IsAssignableFrom(typeof(IEnumerable<ClassA>)));
95+
IsFalse(typeof(IList<ClassD>).IsAssignableFrom(typeof(IEnumerable<ClassA>)));
96+
IsFalse(typeof(IList<ClassA>).IsAssignableFrom(typeof(IEnumerable<ClassA>)));
97+
98+
// Arrays
99+
IsTrue(typeof(byte[]).IsAssignableFrom(typeof(sbyte[])));
100+
IsTrue(typeof(sbyte[]).IsAssignableFrom(typeof(byte[])));
101+
IsTrue(typeof(short[]).IsAssignableFrom(typeof(ushort[])));
102+
IsTrue(typeof(ushort[]).IsAssignableFrom(typeof(short[])));
103+
IsTrue(typeof(int[]).IsAssignableFrom(typeof(uint[])));
104+
IsTrue(typeof(uint[]).IsAssignableFrom(typeof(int[])));
105+
IsTrue(typeof(long[]).IsAssignableFrom(typeof(ulong[])));
106+
IsTrue(typeof(ulong[]).IsAssignableFrom(typeof(long[])));
107+
IsTrue(typeof(long[,]).IsAssignableFrom(typeof(ulong[,])));
108+
IsTrue(typeof(ulong[,,]).IsAssignableFrom(typeof(long[,,])));
109+
IsTrue(typeof(Struct1[]).IsAssignableFrom(typeof(Struct1[])));
110+
IsFalse(typeof(int[]).IsAssignableFrom(typeof(byte[])));
111+
IsFalse(typeof(int[]).IsAssignableFrom(typeof(sbyte[])));
112+
IsFalse(typeof(int[]).IsAssignableFrom(typeof(short[])));
113+
IsFalse(typeof(int[]).IsAssignableFrom(typeof(ushort[])));
114+
IsFalse(typeof(int[]).IsAssignableFrom(typeof(float[])));
115+
IsFalse(typeof(int[]).IsAssignableFrom(typeof(double[])));
116+
IsFalse(typeof(long[]).IsAssignableFrom(typeof(double[])));
117+
IsFalse(typeof(Struct1[]).IsAssignableFrom(typeof(Struct2[])));
118+
IsFalse(typeof(Struct1[]).IsAssignableFrom(typeof(GenericStruct1<int>[])));
119+
IsFalse(typeof(GenericStruct1<uint>[]).IsAssignableFrom(typeof(GenericStruct1<int>[])));
120+
121+
// Misc
122+
IsTrue (typeof(object).IsAssignableFrom(typeof(byte)));
123+
IsTrue (typeof(object).IsAssignableFrom(typeof(int)));
124+
IsTrue (typeof(object).IsAssignableFrom(typeof(float)));
125+
IsTrue (typeof(object).IsAssignableFrom(typeof(SimpleEnum_uint)));
126+
IsTrue (typeof(object).IsAssignableFrom(typeof(IDisposable)));
127+
IsTrue (typeof(object).IsAssignableFrom(typeof(IDictionary<string, string>)));
128+
IsTrue (typeof(object).IsAssignableFrom(typeof(List<int>)));
129+
IsTrue (typeof(object).IsAssignableFrom(typeof(List<>)));
130+
IsTrue (typeof(object).IsAssignableFrom(typeof(Action<>)));
131+
IsTrue (typeof(object).IsAssignableFrom(typeof(Action<int>)));
132+
IsTrue (typeof(object).IsAssignableFrom(typeof(Vector128<float>)));
133+
IsTrue (typeof(object).IsAssignableFrom(typeof(Vector256<int>)));
134+
IsTrue (typeof(ClassA).IsAssignableFrom(typeof(ClassA)));
135+
IsTrue (typeof(ClassA).IsAssignableFrom(typeof(ClassB)));
136+
IsTrue (typeof(ClassA).IsAssignableFrom(typeof(ClassC)));
137+
IsTrue (typeof(decimal).IsAssignableFrom(typeof(decimal)));
138+
IsTrue (typeof(Struct1).IsAssignableFrom(typeof(Struct1)));
139+
IsTrue (typeof(IDisposable).IsAssignableFrom(typeof(Struct3)));
140+
IsTrue (typeof(Dictionary<,>).IsAssignableFrom(typeof(Dictionary<,>)));
141+
IsTrue (typeof(IDictionary<,>).IsAssignableFrom(typeof(IDictionary<,>)));
142+
IsTrue (typeof(GenericStruct1<>).IsAssignableFrom(typeof(GenericStruct1<>)));
143+
IsTrue (typeof(GenericStruct1<int>).IsAssignableFrom(typeof(GenericStruct1<int>)));
144+
IsTrue (typeof(GenericStruct1<string>).IsAssignableFrom(typeof(GenericStruct1<string>)));
145+
IsFalse(typeof(byte).IsAssignableFrom(typeof(IDisposable)));
146+
IsFalse(typeof(IDisposable).IsAssignableFrom(typeof(IEnumerable)));
147+
IsFalse(typeof(IDictionary<string, string>).IsAssignableFrom(typeof(IDictionary<string, int>)));
148+
IsFalse(typeof(List<int>).IsAssignableFrom(typeof(IList<int>)));
149+
IsFalse(typeof(List<>).IsAssignableFrom(typeof(List<IDisposable>)));
150+
IsFalse(typeof(Action<>).IsAssignableFrom(typeof(Action<int>)));
151+
IsFalse(typeof(Action<>).IsAssignableFrom(typeof(Func<int>)));
152+
IsFalse(typeof(Action).IsAssignableFrom(typeof(CustomAction)));
153+
IsFalse(typeof(Action<int>).IsAssignableFrom(typeof(void)));
154+
IsFalse(typeof(ClassB).IsAssignableFrom(typeof(ClassD)));
155+
IsFalse(typeof(Dictionary<,>).IsAssignableFrom(typeof(Dictionary<int,int>)));
156+
IsFalse(typeof(GenericStruct1<ClassA>).IsAssignableFrom(typeof(GenericStruct1<ClassB>)));
157+
IsFalse(typeof(Struct1).IsAssignableFrom(typeof(Struct2)));
158+
IsFalse(typeof(GenericStruct1<>).IsAssignableFrom(typeof(GenericStruct2<>)));
159+
IsFalse(typeof(GenericStruct1<int>).IsAssignableFrom(typeof(GenericStruct2<int>)));
160+
IsFalse(typeof(object).IsAssignableFrom(typeof(byte*)));
161+
IsFalse(typeof(object).IsAssignableFrom(typeof(byte**)));
162+
IsFalse(typeof(Vector128<double>).IsAssignableFrom(typeof(Vector128<float>)));
163+
IsFalse(typeof(Vector128<float>).IsAssignableFrom(typeof(Vector128<int>)));
164+
IsFalse(typeof(Vector128<int>).IsAssignableFrom(typeof(Vector128<float>)));
165+
IsFalse(typeof(Vector4).IsAssignableFrom(typeof(Vector128<float>)));
166+
IsFalse(typeof(Vector128<float>).IsAssignableFrom(typeof(Vector4)));
167+
IsFalse(typeof(Vector128<float>).IsAssignableFrom(typeof(Vector<float>)));
168+
IsFalse(typeof(Vector256<float>).IsAssignableFrom(typeof(Vector<float>)));
169+
170+
// System.__Canon
171+
IsTrue (IsAssignableFrom<KeyValuePair<IDisposable, IDisposable>, KeyValuePair<IDisposable, IDisposable>>());
172+
IsTrue (IsAssignableFrom<KeyValuePair<IDisposable, object>, KeyValuePair<IDisposable, object>>());
173+
IsTrue (IsAssignableFrom<IDictionary<IDisposable, IDisposable>, IDictionary<IDisposable, IDisposable>>());
174+
IsTrue (IsAssignableFrom<IDictionary<IDisposable, object>, IDictionary<IDisposable, object>>());
175+
IsTrue (IsAssignableFrom<Dictionary<IDisposable, IDisposable>, Dictionary<IDisposable, IDisposable>>());
176+
IsTrue (IsAssignableFrom<Dictionary<IDisposable, object>, Dictionary<IDisposable, object>>());
177+
IsTrue (IsAssignableFrom<KeyValuePair<int, int>, KeyValuePair<int, int>>());
178+
IsTrue (IsAssignableFrom<KeyValuePair<IEnumerable<int>, IEnumerable<int>>, KeyValuePair<IEnumerable<int>, IEnumerable<int>>>());
179+
IsFalse(IsAssignableFrom<KeyValuePair<IDisposable, IDisposable>, KeyValuePair<IDisposable, object>>());
180+
IsFalse(IsAssignableFrom<KeyValuePair<IDisposable, int>, KeyValuePair<IDisposable, object>>());
181+
IsFalse(IsAssignableFrom<IDictionary<IDisposable, IDisposable>, IDictionary<IDisposable, object>>());
182+
IsFalse(IsAssignableFrom<IDictionary<IDisposable, int>, IDictionary<IDisposable, object>>());
183+
IsFalse(IsAssignableFrom<Dictionary<IDisposable, IDisposable>, Dictionary<IDisposable, object>>());
184+
IsFalse(IsAssignableFrom<Dictionary<IDisposable, int>, Dictionary<IDisposable, object>>());
185+
IsFalse(IsAssignableFrom<KeyValuePair<int, int>, KeyValuePair<int, object>>());
186+
IsFalse(IsAssignableFrom<KeyValuePair<IEnumerable<int>, IEnumerable<int>>, KeyValuePair<IEnumerable<int>, IEnumerable<uint>>>());
187+
}
188+
189+
[MethodImpl(MethodImplOptions.NoInlining)]
190+
static bool IsAssignableFrom<TTo, TTFrom>() => typeof(TTo).IsAssignableFrom(typeof(TTFrom));
191+
}
192+
193+
public struct Struct1
194+
{
195+
public int field1;
196+
}
197+
198+
public struct Struct2
199+
{
200+
public int field1;
201+
}
202+
203+
public struct Struct3 : IDisposable
204+
{
205+
public int field1;
206+
public void Dispose(){}
207+
}
208+
209+
public struct GenericStruct1<T>
210+
{
211+
public T field;
212+
}
213+
214+
public struct GenericStruct2<T>
215+
{
216+
public T field;
217+
}
218+
219+
public enum SimpleEnum_byte : byte
220+
{
221+
A, B, C
222+
}
223+
224+
public enum SimpleEnum_int : int
225+
{
226+
A,B,C
227+
}
228+
229+
public enum SimpleEnum_uint : uint
230+
{
231+
D,E
232+
}
233+
234+
public class ClassA
235+
{
236+
}
237+
238+
public class ClassB : ClassA
239+
{
240+
}
241+
242+
public class ClassC : ClassB
243+
{
244+
}
245+
246+
public class ClassD : ClassA
247+
{
248+
}
249+
250+
public delegate void CustomAction();

src/coreclr/tests/src/JIT/Intrinsics/TypeIntrinsics.cs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
using System.Runtime.CompilerServices;
1010
using System.Runtime.Intrinsics;
1111

12-
public class Program
12+
public partial class Program
1313
{
1414
private static int _errors = 0;
1515

@@ -98,6 +98,8 @@ public static int Main(string[] args)
9898
ThrowsNRE(() => { IsValueTypeRef(ref _varNullableIntNull); });
9999
ThrowsNRE(() => { IsValueTypeRef(ref _varStringNull); });
100100

101+
TestIsAssignableFrom();
102+
101103
return 100 + _errors;
102104
}
103105

@@ -135,25 +137,25 @@ public static int Main(string[] args)
135137
private static dynamic CreateDynamic2() => new { Name = "Test" };
136138

137139

138-
static void IsTrue(bool expression, [CallerLineNumber] int line = 0)
140+
static void IsTrue(bool expression, [CallerLineNumber] int line = 0, [CallerFilePath] string file = "")
139141
{
140142
if (!expression)
141143
{
142-
Console.WriteLine($"Line {line}: test failed (expected: true).");
144+
Console.WriteLine($"{file}:L{line} test failed (expected: true).");
143145
_errors++;
144146
}
145147
}
146148

147-
static void IsFalse(bool expression, [CallerLineNumber] int line = 0)
149+
static void IsFalse(bool expression, [CallerLineNumber] int line = 0, [CallerFilePath] string file = "")
148150
{
149151
if (expression)
150152
{
151-
Console.WriteLine($"Line {line}: test failed (expected: false).");
153+
Console.WriteLine($"{file}:L{line} test failed (expected: false).");
152154
_errors++;
153155
}
154156
}
155157

156-
static void ThrowsNRE(Action action, [CallerLineNumber] int line = 0)
158+
static void ThrowsNRE(Action action, [CallerLineNumber] int line = 0, [CallerFilePath] string file = "")
157159
{
158160
try
159161
{
@@ -165,7 +167,7 @@ static void ThrowsNRE(Action action, [CallerLineNumber] int line = 0)
165167
}
166168
catch (Exception exc)
167169
{
168-
Console.WriteLine($"Line {line}: {exc}");
170+
Console.WriteLine($"{file}:L{line} {exc}");
169171
}
170172
Console.WriteLine($"Line {line}: test failed (expected: NullReferenceException)");
171173
}

0 commit comments

Comments
 (0)