Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(161)

Side by Side Diff: pkg/analyzer/test/src/task/strong/front_end_runtime_check_test.dart

Issue 3010613003: Simplify annotations in runtime check tests. (Closed)
Patch Set: Address code review comment Created 3 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | pkg/front_end/lib/src/fasta/testing/validating_instrumentation.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 import 'package:analyzer/dart/ast/ast.dart'; 5 import 'package:analyzer/dart/ast/ast.dart';
6 import 'package:analyzer/dart/ast/visitor.dart'; 6 import 'package:analyzer/dart/ast/visitor.dart';
7 import 'package:analyzer/dart/element/element.dart'; 7 import 'package:analyzer/dart/element/element.dart';
8 import 'package:analyzer/dart/element/type.dart'; 8 import 'package:analyzer/dart/element/type.dart';
9 import 'package:analyzer/src/dart/element/type.dart'; 9 import 'package:analyzer/src/dart/element/type.dart';
10 import 'package:analyzer/src/generated/resolver.dart'; 10 import 'package:analyzer/src/generated/resolver.dart';
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
58 _InstrumentationVisitor(this._typeSystem, this._instrumentation, this.uri); 58 _InstrumentationVisitor(this._typeSystem, this._instrumentation, this.uri);
59 59
60 @override 60 @override
61 visitAssignmentExpression(AssignmentExpression node) { 61 visitAssignmentExpression(AssignmentExpression node) {
62 super.visitAssignmentExpression(node); 62 super.visitAssignmentExpression(node);
63 var leftHandSide = node.leftHandSide; 63 var leftHandSide = node.leftHandSide;
64 if (leftHandSide is PrefixedIdentifier) { 64 if (leftHandSide is PrefixedIdentifier) {
65 var staticElement = leftHandSide.identifier.staticElement; 65 var staticElement = leftHandSide.identifier.staticElement;
66 if (staticElement is PropertyAccessorElement && staticElement.isSetter) { 66 if (staticElement is PropertyAccessorElement && staticElement.isSetter) {
67 var target = leftHandSide.prefix; 67 var target = leftHandSide.prefix;
68 _annotateCheckCall( 68 _annotateCallKind(
69 staticElement, 69 staticElement,
70 target is ThisExpression, 70 target is ThisExpression,
71 isDynamicInvoke(leftHandSide.identifier), 71 isDynamicInvoke(leftHandSide.identifier),
72 target.staticType, 72 target.staticType,
73 [],
74 [node.rightHandSide],
75 leftHandSide.identifier.offset); 73 leftHandSide.identifier.offset);
76 } 74 }
77 } 75 }
78 } 76 }
79 77
80 @override 78 @override
81 visitClassDeclaration(ClassDeclaration node) { 79 visitClassDeclaration(ClassDeclaration node) {
82 super.visitClassDeclaration(node); 80 super.visitClassDeclaration(node);
83 _emitForwardingStubs(node, node.name.offset); 81 _emitForwardingStubs(node, node.name.offset);
84 } 82 }
85 83
86 @override 84 @override
87 visitClassTypeAlias(ClassTypeAlias node) { 85 visitClassTypeAlias(ClassTypeAlias node) {
88 super.visitClassTypeAlias(node); 86 super.visitClassTypeAlias(node);
89 _emitForwardingStubs(node, node.name.offset); 87 _emitForwardingStubs(node, node.name.offset);
90 } 88 }
91 89
92 @override 90 @override
93 visitFormalParameter(FormalParameter node) { 91 visitFormalParameter(FormalParameter node) {
94 super.visitFormalParameter(node); 92 super.visitFormalParameter(node);
95 if (node is DefaultFormalParameter) { 93 if (node is DefaultFormalParameter) {
96 // Already handled via the contained parameter ast object 94 // Already handled via the contained parameter ast object
97 return; 95 return;
98 } 96 }
99 _annotateFormalParameter(node.element, node.identifier.offset, 97 if (node.element.enclosingElement.enclosingElement is ClassElement) {
100 node.getAncestor((n) => n is ClassDeclaration)); 98 _annotateFormalParameter(node.element, node.identifier.offset,
99 node.getAncestor((n) => n is ClassDeclaration));
100 }
101 } 101 }
102 102
103 @override 103 @override
104 visitMethodInvocation(MethodInvocation node) { 104 visitMethodInvocation(MethodInvocation node) {
105 super.visitMethodInvocation(node); 105 super.visitMethodInvocation(node);
106 var staticElement = node.methodName.staticElement; 106 var staticElement = node.methodName.staticElement;
107 var target = node.target; 107 var target = node.target;
108 var isThis = target is ThisExpression || target == null; 108 var isThis = target is ThisExpression || target == null;
109 if (staticElement is PropertyAccessorElement) { 109 if (staticElement is PropertyAccessorElement) {
110 // Method invocation resolves to a getter; treat it as a get followed by a 110 // Method invocation resolves to a getter; treat it as a get followed by a
111 // function invocation. 111 // function invocation.
112 _annotateCheckReturn( 112 _annotateCheckReturn(
113 getImplicitOperationCast(node), node.methodName.offset); 113 getImplicitOperationCast(node), node.methodName.offset);
114 _annotateCheckCall( 114 _annotateCallKind(null, isThis, isDynamicInvoke(node.methodName), null,
115 null,
116 isThis,
117 isDynamicInvoke(node.methodName),
118 null,
119 node.typeArguments?.arguments,
120 node.argumentList.arguments,
121 node.argumentList.offset); 115 node.argumentList.offset);
122 } else { 116 } else {
123 _annotateCheckReturn(getImplicitCast(node), node.argumentList.offset); 117 _annotateCheckReturn(getImplicitCast(node), node.argumentList.offset);
124 _annotateCheckCall( 118 _annotateCallKind(staticElement, isThis, isDynamicInvoke(node.methodName),
125 staticElement, 119 target?.staticType, node.argumentList.offset);
126 isThis,
127 isDynamicInvoke(node.methodName),
128 target?.staticType,
129 node.typeArguments?.arguments,
130 node.argumentList.arguments,
131 node.argumentList.offset);
132 } 120 }
133 } 121 }
134 122
135 @override 123 @override
136 visitPrefixedIdentifier(PrefixedIdentifier node) { 124 visitPrefixedIdentifier(PrefixedIdentifier node) {
137 super.visitPrefixedIdentifier(node); 125 super.visitPrefixedIdentifier(node);
138 if (node.identifier.staticElement is MethodElement) { 126 if (node.identifier.staticElement is MethodElement) {
139 _annotateTearOff(node, node.identifier.offset); 127 _annotateTearOff(node, node.identifier.offset);
140 } 128 }
141 } 129 }
(...skipping 13 matching lines...) Expand all
155 if (node.parent.parent is FieldDeclaration) { 143 if (node.parent.parent is FieldDeclaration) {
156 FieldElement element = node.element; 144 FieldElement element = node.element;
157 if (!element.isFinal) { 145 if (!element.isFinal) {
158 var setter = element.setter; 146 var setter = element.setter;
159 _annotateFormalParameter(setter.parameters[0], node.name.offset, 147 _annotateFormalParameter(setter.parameters[0], node.name.offset,
160 node.getAncestor((n) => n is ClassDeclaration)); 148 node.getAncestor((n) => n is ClassDeclaration));
161 } 149 }
162 } 150 }
163 } 151 }
164 152
165 /// Generates the appropriate `@checkCall` annotation (if any) for a call 153 /// Generates the appropriate `@callKind` annotation (if any) for a call site.
166 /// site.
167 /// 154 ///
168 /// An annotation of `@checkCall=dynamic` indicates that the call is dynamic 155 /// An annotation of `@callKind=dynamic` indicates that the call is dynamic
169 /// (so it will have to be fully type checked). An annotation of 156 /// (so it will have to be fully type checked). An annotation of
170 /// "@checkCall=interface(args)" indicates that the call statically resolves 157 /// `@callKind=closure` indicates that the receiver of the call is a function
171 /// to a member of an interface, but some of the arguments are "semi-typed" so 158 /// object (so any formals marked as "semiSafe" will have to be type checked).
172 /// they may have to be type checked. `args` lists the positional indices of 159 /// An annotation of `@callKind=this` indicates that the call goes through
173 /// the semi-typed arguments (counting from 0). If any type parameters need 160 /// `super` or `this` (so formals marked as "semiSafe" don't need to be type
174 /// to be checked, they are also listed by index, enclosed in `<>`. For 161 /// checked). No annotation indicates that either the call is static, in
175 /// example, `@checkCall=interface(<0>,1)` means that type parameter 0 and 162 /// which case no parameters need to be type checked, or it goes through an
176 /// regular parameter 1 are semi-typed. 163 /// interface, in which case the set of arguments that have to be type checked
177 /// 164 /// depends on the `@checkInterface` annotations on the static target of the
178 /// [staticElement] is the element being invoked, or `null` if there is no 165 /// call.
179 /// static element (either because this is a dynamic invocation or because the 166 void _annotateCallKind(Element staticElement, bool isThis, bool isDynamic,
180 /// thing being invoked is function-typed). 167 DartType targetType, int offset) {
181 ///
182 /// [isThis] indicates whether the receiver of the invocation is `this`.
183 ///
184 /// [isDynamic] indicates whether analyzer has classified this invocation as a
185 /// dynamic invocation.
186 ///
187 /// [targetType] is the type of the target of the invocation, or `null` if
188 /// there is no target (e.g. because of implicit `this` or because the thing
189 /// being invoked is function-typed).
190 ///
191 /// [typeArguments] and [arguments] are the type arguments and regular
192 /// arguments of the invocation, respectively.
193 ///
194 /// [offset] is the location of the invocation in source code.
195 void _annotateCheckCall(
196 Element staticElement,
197 bool isThis,
198 bool isDynamic,
199 DartType targetType,
200 List<TypeAnnotation> typeArguments,
201 List<Expression> arguments,
202 int offset) {
203 if (staticElement is FunctionElement && 168 if (staticElement is FunctionElement &&
204 staticElement.enclosingElement is CompilationUnitElement) { 169 staticElement.enclosingElement is CompilationUnitElement) {
205 // Invocation of a top level function; no annotation needed. 170 // Invocation of a top level function; no annotation needed.
206 return; 171 return;
207 } 172 }
208 if (isDynamic) { 173 if (isDynamic) {
209 if (targetType == null && 174 if (targetType == null &&
210 staticElement != null && 175 staticElement != null &&
211 staticElement is! MethodElement) { 176 staticElement is! MethodElement) {
212 // Sometimes analyzer annotates invocations of function objects as 177 // Sometimes analyzer annotates invocations of function objects as
213 // dynamic (presumably due to "dynamic is bottom" behavior). Ignore 178 // dynamic (presumably due to "dynamic is bottom" behavior). Ignore
214 // this. 179 // this.
180 _recordCallKind(offset, 'closure');
215 } else { 181 } else {
216 _recordCheckCall(offset, 'dynamic'); 182 _recordCallKind(offset, 'dynamic');
217 return; 183 return;
218 } 184 }
219 } 185 }
220 if (staticElement is MethodElement && isThis) { 186 if (staticElement is MethodElement && !staticElement.isStatic ||
221 // Calls through "this" are always typed because the type parameters match 187 staticElement is PropertyAccessorElement && !staticElement.isStatic) {
222 // up perfectly; no annotation needed. 188 if (isThis) {
223 return; 189 _recordCallKind(offset, 'this');
224 } 190 return;
225 var semiTypedArgs = <String>[]; 191 } else {
226 if (typeArguments != null) { 192 // Interface call; no annotation needed
227 for (int argPosition = 0; 193 return;
228 argPosition < typeArguments.length;
229 argPosition++) {
230 DartType getArgument(FunctionType functionType) {
231 return functionType.typeFormals[argPosition].bound;
232 }
233
234 if (_isArgumentSemiTyped(targetType, staticElement, getArgument)) {
235 semiTypedArgs.add('<$argPosition>');
236 }
237 } 194 }
238 } 195 }
239 int argPosition = 0; 196 _recordCallKind(offset, 'closure');
240 for (var argument in arguments) {
241 assert(argument is! NamedExpression); // TODO(paulberry): handle this
242 DartType getArgument(FunctionType functionType) {
243 // TODO(paulberry): handle named parameters
244 if (argPosition >= functionType.normalParameterTypes.length) {
245 return functionType.optionalParameterTypes[
246 argPosition - functionType.normalParameterTypes.length];
247 } else {
248 return functionType.normalParameterTypes[argPosition];
249 }
250 }
251
252 if (_isArgumentSemiTyped(targetType, staticElement, getArgument)) {
253 semiTypedArgs.add('$argPosition');
254 }
255 ++argPosition;
256 }
257 if (semiTypedArgs.isEmpty) {
258 // We don't annotate invocations where all arguments are typed because
259 // that's the common case.
260 } else {
261 _recordCheckCall(
262 offset, 'interface(semiTyped:${semiTypedArgs.join(',')})');
263 }
264 } 197 }
265 198
266 /// Generates the appropriate `@checkReturn` annotation (if any) for a call 199 /// Generates the appropriate `@checkReturn` annotation (if any) for a call
267 /// site. 200 /// site.
268 /// 201 ///
269 /// An annotation of `@checkReturn=type` indicates that the value returned by 202 /// An annotation of `@checkReturn=type` indicates that the value returned by
270 /// the call will have to be checked to make sure it is an instance of the 203 /// the call will have to be checked to make sure it is an instance of the
271 /// given type. 204 /// given type.
272 void _annotateCheckReturn(DartType castType, int offset) { 205 void _annotateCheckReturn(DartType castType, int offset) {
273 if (castType != null) { 206 if (castType != null) {
274 _recordCheckReturn(offset, castType); 207 _recordCheckReturn(offset, castType);
275 } 208 }
276 } 209 }
277 210
278 /// Generates the appropriate `@checkFormal` annotation (if any) for a method 211 /// Generates the appropriate `@checkFormal` and `@checkInterface` annotations
279 /// formal parameter, method type parameter, or field declaration. 212 /// (if any) for a method formal parameter, method type parameter, or field
213 /// declaration.
280 /// 214 ///
281 /// When this annotation is generated for a field declaration, it implicitly 215 /// When these annotations are generated for a field declaration, they
282 /// refers to the value parameter of the synthetic setter. 216 /// implicitly refer to the value parameter of the synthetic setter.
283 /// 217 ///
284 /// An annotation of `@checkFormal=unsafe` indicates that the parameter needs 218 /// An annotation of `@checkFormal=unsafe` indicates that the parameter needs
285 /// to be type checked regardless of the call site. 219 /// to be type checked regardless of the call site.
286 /// 220 ///
287 /// An annotation of `@checkFormal=semiSafe` indicates that the parameter 221 /// An annotation of `@checkFormal=semiSafe` indicates that the parameter
288 /// needs to be type checked when corresponding argument at the call site is 222 /// needs to be type checked when the call site is annotated
289 /// considered "semi-typed". 223 /// `@callKind=dynamic` or `@callKind=closure`, or the call site is
224 /// unannotated and the corresponding parameter in the interface target is
225 /// annotated `@checkInterface=semiTyped`.
290 /// 226 ///
291 /// No annotation indicates that the parameter only needs to be type checked 227 /// No `@checkFormal` annotation indicates that the parameter only needs to be
292 /// if the call site is a dynamic invocation. 228 /// type checked if the call site is annotated `@callKind=dynamic`.
293 void _annotateFormalParameter( 229 void _annotateFormalParameter(
294 Element element, int offset, ClassDeclaration cls) { 230 Element element, int offset, ClassDeclaration cls) {
295 if (element is ParameterElement && element.isCovariant) { 231 if (element is ParameterElement && element.isCovariant) {
296 _recordCheckFormal(offset, 'unsafe'); 232 _recordCheckFormal(offset, 'unsafe');
297 } else if (cls != null) { 233 } else if (cls != null) {
298 var covariantParams = getClassCovariantParameters(cls); 234 var covariantParams = getClassCovariantParameters(cls);
299 if (covariantParams != null && covariantParams.contains(element)) { 235 if (covariantParams != null && covariantParams.contains(element)) {
300 _recordCheckFormal(offset, 'semiSafe'); 236 _recordCheckFormal(offset, 'semiSafe');
301 } 237 }
302 } 238 }
239 if (cls?.typeParameters != null) {
240 if (element is ParameterElement) {
241 if (_isFormalSemiTyped(
242 cls.typeParameters.typeParameters, element.type)) {
243 _recordCheckInterface(offset, 'semiTyped');
244 }
245 } else if (element is TypeParameterElement && element.bound != null) {
246 if (_isFormalSemiTyped(
247 cls.typeParameters.typeParameters, element.bound)) {
248 _recordCheckInterface(offset, 'semiTyped');
249 }
250 }
251 }
303 } 252 }
304 253
305 /// Generates the appropriate `@checkTearOff` annotation (if any) for a call 254 /// Generates the appropriate `@checkTearOff` annotation (if any) for a call
306 /// site. 255 /// site.
307 /// 256 ///
308 /// An annotation of `@checkTearOff=type` indicates that the torn off function 257 /// An annotation of `@checkTearOff=type` indicates that the torn off function
309 /// will have to be checked to make sure it is an instance of the given type. 258 /// will have to be checked to make sure it is an instance of the given type.
310 void _annotateTearOff(Expression node, int offset) { 259 void _annotateTearOff(Expression node, int offset) {
311 // TODO(paulberry): handle dynamic tear offs 260 // TODO(paulberry): handle dynamic tear offs
312 // Note: we don't annotate that non-dynamic tear offs use "interface" 261 // Note: we don't annotate that non-dynamic tear offs use "interface"
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after
347 var returnTypeDescr = _typeToString(member.returnType); 296 var returnTypeDescr = _typeToString(member.returnType);
348 var stub = '$returnTypeDescr $memberName(${paramDescrs.join(', ')})'; 297 var stub = '$returnTypeDescr $memberName(${paramDescrs.join(', ')})';
349 _recordForwardingStub(offset, stub); 298 _recordForwardingStub(offset, stub);
350 } else { 299 } else {
351 throw new StateError('Unexpected covariant member $member'); 300 throw new StateError('Unexpected covariant member $member');
352 } 301 }
353 } 302 }
354 } 303 }
355 } 304 }
356 305
357 /// Determines whether an argument at a call site should be considered 306 /// Determines whether a method formal parameter should be considered
358 /// "semi-typed". 307 /// "semi-typed".
359 /// 308 ///
360 /// [targetType] indicates the type of the interface being invoked. 309 /// [typeParameters] is the list of type parameters of the enclosing class.
361 /// 310 ///
362 /// [invocationTarget] is the method or getter/setter being invoked. 311 /// [formalType] is the type of the formal parameter (or the type bound, if
363 /// 312 /// we are looking at a type parameter of a generic method).
364 /// [getArgument] is a callback for accessing the corresponding argument type 313 bool _isFormalSemiTyped(
365 /// from a [FunctionType]. 314 List<TypeParameter> typeParameters, DartType formalType) {
366 bool _isArgumentSemiTyped(InterfaceType targetType, Element invocationTarget, 315 // To see if this parameter needs to be semi-typed, we try substituting
367 DartType getArgument(FunctionType functionType)) { 316 // bottom for all the active type parameters. If the resulting parameter
368 bool _checkTypes(DartType originalArgumentType, 317 // static type is a supertype of its current static type, then that means
369 DartType lookupArgumentType(InterfaceType interfaceType)) { 318 // that regardless of what we pass in, it won't fail a type check.
370 // If the target type lacks type parameters, then everything is safe. 319 var substitutedType = formalType.substitute2(
371 if (targetType.typeParameters.isEmpty) return false; 320 new List<DartType>.filled(
372 321 typeParameters.length, BottomTypeImpl.instance),
373 // To see if this argument needs to be semi-typed, we try substituting 322 typeParameters
374 // bottom in for all the active type parameters. If the resulting 323 .map((p) => new TypeParameterTypeImpl(p.element))
375 // argument static type is a supertype of its current static type, then 324 .toList());
376 // that means that regardless of what we pass in, it won't fail a type 325 return !_typeSystem.isSubtypeOf(formalType, substitutedType);
377 // check.
378 var substitutedInterfaceType = targetType.element.type.instantiate(
379 new List<DartType>.filled(
380 targetType.typeParameters.length, BottomTypeImpl.instance));
381 var substitutedArgumentType =
382 lookupArgumentType(substitutedInterfaceType);
383 return !_typeSystem.isSubtypeOf(
384 originalArgumentType, substitutedArgumentType);
385 }
386
387 if (invocationTarget is LocalVariableElement || invocationTarget == null) {
388 // This is an invocation of a closure, so every argument is semi-typed.
389 return true;
390 } else if (invocationTarget is PropertyAccessorElement &&
391 invocationTarget.isSetter) {
392 return _checkTypes(
393 invocationTarget.parameters[0].type,
394 (InterfaceType type) => type
395 .lookUpSetter(invocationTarget.name, invocationTarget.library)
396 .parameters[0]
397 .type);
398 } else if (invocationTarget is MethodElement) {
399 return _checkTypes(
400 getArgument(invocationTarget.type),
401 (InterfaceType type) => getArgument(type
402 .lookUpMethod(invocationTarget.name, invocationTarget.library)
403 .type));
404 } else {
405 throw new UnimplementedError(
406 'Unexpected invocation target type: ${invocationTarget.runtimeType}');
407 }
408 } 326 }
409 327
410 void _recordCheckCall(int offset, String safety) { 328 void _recordCallKind(int offset, String kind) {
411 _instrumentation.record(uri, offset, 'checkCall', 329 _instrumentation.record(
412 new fasta.InstrumentationValueLiteral(safety)); 330 uri, offset, 'callKind', new fasta.InstrumentationValueLiteral(kind));
413 } 331 }
414 332
415 void _recordCheckFormal(int offset, String safety) { 333 void _recordCheckFormal(int offset, String safety) {
416 _instrumentation.record(uri, offset, 'checkFormal', 334 _instrumentation.record(uri, offset, 'checkFormal',
417 new fasta.InstrumentationValueLiteral(safety)); 335 new fasta.InstrumentationValueLiteral(safety));
418 } 336 }
419 337
338 void _recordCheckInterface(int offset, String safety) {
339 _instrumentation.record(uri, offset, 'checkInterface',
340 new fasta.InstrumentationValueLiteral(safety));
341 }
342
420 void _recordCheckReturn(int offset, DartType castType) { 343 void _recordCheckReturn(int offset, DartType castType) {
421 _instrumentation.record(uri, offset, 'checkReturn', 344 _instrumentation.record(uri, offset, 'checkReturn',
422 new InstrumentationValueForType(castType, _elementNamer)); 345 new InstrumentationValueForType(castType, _elementNamer));
423 } 346 }
424 347
425 void _recordCheckTearOff(int offset, DartType castType) { 348 void _recordCheckTearOff(int offset, DartType castType) {
426 _instrumentation.record(uri, offset, 'checkTearOff', 349 _instrumentation.record(uri, offset, 'checkTearOff',
427 new InstrumentationValueForType(castType, _elementNamer)); 350 new InstrumentationValueForType(castType, _elementNamer));
428 } 351 }
429 352
430 void _recordForwardingStub(int offset, String descr) { 353 void _recordForwardingStub(int offset, String descr) {
431 _instrumentation.record(uri, offset, 'forwardingStub', 354 _instrumentation.record(uri, offset, 'forwardingStub',
432 new fasta.InstrumentationValueLiteral(descr)); 355 new fasta.InstrumentationValueLiteral(descr));
433 } 356 }
434 357
435 String _typeToString(DartType type) { 358 String _typeToString(DartType type) {
436 return new InstrumentationValueForType(type, _elementNamer).toString(); 359 return new InstrumentationValueForType(type, _elementNamer).toString();
437 } 360 }
438 } 361 }
OLDNEW
« no previous file with comments | « no previous file | pkg/front_end/lib/src/fasta/testing/validating_instrumentation.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698