mirror of
https://github.com/dart-lang/sdk
synced 2024-09-18 20:51:19 +00:00
Support checking of malbounded types.
BUG= R=karlklose@google.com Review URL: https://codereview.chromium.org//48383003 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@29531 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
parent
0354366832
commit
70e0cd4207
|
@ -174,6 +174,14 @@ abstract class Backend {
|
|||
Enqueuer enqueuer,
|
||||
TreeElements elements) {}
|
||||
|
||||
/// Register a runtime type variable bound tests between [typeArgument] and
|
||||
/// [bound].
|
||||
void registerTypeVariableBoundsSubtypeCheck(DartType typeArgument,
|
||||
DartType bound) {}
|
||||
|
||||
/// Registers that a type variable bounds check might occur at runtime.
|
||||
void registerTypeVariableBoundCheck(TreeElements elements) {}
|
||||
|
||||
/// Register that the application may throw a [NoSuchMethodError].
|
||||
void registerThrowNoSuchMethod(TreeElements elements) {}
|
||||
|
||||
|
|
|
@ -1207,6 +1207,16 @@ class SubtypeVisitor extends MoreSpecificVisitor {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback used to check whether the [typeArgument] of [type] is a valid
|
||||
* substitute for the bound of [typeVariable]. [bound] holds the bound against
|
||||
* which [typeArgument] should be checked.
|
||||
*/
|
||||
typedef void CheckTypeVariableBound(GenericType type,
|
||||
DartType typeArgument,
|
||||
TypeVariableType typeVariable,
|
||||
DartType bound);
|
||||
|
||||
class Types {
|
||||
final Compiler compiler;
|
||||
// TODO(karlklose): should we have a class Void?
|
||||
|
@ -1250,12 +1260,44 @@ class Types {
|
|||
return subtypeVisitor.isAssignable(r, s);
|
||||
}
|
||||
|
||||
static const int IS_SUBTYPE = 1;
|
||||
static const int MAYBE_SUBTYPE = 0;
|
||||
static const int NOT_SUBTYPE = -1;
|
||||
|
||||
int computeSubtypeRelation(DartType t, DartType s) {
|
||||
// TODO(johnniwinther): Compute this directly in [isPotentialSubtype].
|
||||
if (isSubtype(t, s)) return IS_SUBTYPE;
|
||||
return isPotentialSubtype(t, s) ? MAYBE_SUBTYPE : NOT_SUBTYPE;
|
||||
}
|
||||
|
||||
bool isPotentialSubtype(DartType t, DartType s) {
|
||||
// TODO(johnniwinther): Return a set of variable points in the positive
|
||||
// cases.
|
||||
return potentialSubtypeVisitor.isSubtype(t, s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the type arguments of [type] against the type variable bounds
|
||||
* declared on [element]. Calls [checkTypeVariableBound] on each type
|
||||
* argument and bound.
|
||||
*/
|
||||
void checkTypeVariableBounds(GenericType type,
|
||||
CheckTypeVariableBound checkTypeVariableBound) {
|
||||
TypeDeclarationElement element = type.element;
|
||||
Link<DartType> typeArguments = type.typeArguments;
|
||||
Link<DartType> typeVariables = element.typeVariables;
|
||||
while (!typeVariables.isEmpty && !typeArguments.isEmpty) {
|
||||
TypeVariableType typeVariable = typeVariables.head;
|
||||
DartType bound = typeVariable.element.bound.subst(
|
||||
type.typeArguments, element.typeVariables);
|
||||
DartType typeArgument = typeArguments.head;
|
||||
checkTypeVariableBound(type, typeArgument, typeVariable, bound);
|
||||
typeVariables = typeVariables.tail;
|
||||
typeArguments = typeArguments.tail;
|
||||
}
|
||||
assert(typeVariables.isEmpty && typeArguments.isEmpty);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for performing substitution of a linked list of types.
|
||||
*
|
||||
|
|
|
@ -1078,6 +1078,16 @@ class JavaScriptBackend extends Backend {
|
|||
enqueueClass(compiler.enqueuer.resolution, compiler.stringClass, elements);
|
||||
}
|
||||
|
||||
void registerTypeVariableBoundsSubtypeCheck(DartType typeArgument,
|
||||
DartType bound) {
|
||||
rti.registerTypeVariableBoundsSubtypeCheck(typeArgument, bound);
|
||||
}
|
||||
|
||||
void registerTypeVariableBoundCheck(TreeElements elements) {
|
||||
enqueueInResolution(getThrowTypeError(), elements);
|
||||
enqueueInResolution(getAssertIsSubtype(), elements);
|
||||
}
|
||||
|
||||
void registerAbstractClassInstantiation(TreeElements elements) {
|
||||
enqueueInResolution(getThrowAbstractClassInstantiationError(), elements);
|
||||
// Also register the types of the arguments passed to this method.
|
||||
|
@ -1483,6 +1493,10 @@ class JavaScriptBackend extends Backend {
|
|||
return compiler.findHelper('throwRuntimeError');
|
||||
}
|
||||
|
||||
Element getThrowTypeError() {
|
||||
return compiler.findHelper('throwTypeError');
|
||||
}
|
||||
|
||||
Element getThrowAbstractClassInstantiationError() {
|
||||
return compiler.findHelper('throwAbstractClassInstantiationError');
|
||||
}
|
||||
|
@ -1535,6 +1549,10 @@ class JavaScriptBackend extends Backend {
|
|||
return compiler.findHelper('runtimeTypeToString');
|
||||
}
|
||||
|
||||
Element getAssertIsSubtype() {
|
||||
return compiler.findHelper('assertIsSubtype');
|
||||
}
|
||||
|
||||
Element getCheckSubtype() {
|
||||
return compiler.findHelper('checkSubtype');
|
||||
}
|
||||
|
|
|
@ -24,6 +24,10 @@ class RuntimeTypes {
|
|||
// The set of classes that use one of their type variables as expressions
|
||||
// to get the runtime type.
|
||||
final Set<ClassElement> classesUsingTypeVariableExpression;
|
||||
// The set of type arguments tested against type variable bounds.
|
||||
final Set<DartType> checkedTypeArguments;
|
||||
// The set of tested type variable bounds.
|
||||
final Set<DartType> checkedBounds;
|
||||
|
||||
JavaScriptBackend get backend => compiler.backend;
|
||||
|
||||
|
@ -33,7 +37,9 @@ class RuntimeTypes {
|
|||
classesNeedingRti = new Set<ClassElement>(),
|
||||
methodsNeedingRti = new Set<Element>(),
|
||||
rtiDependencies = new Map<ClassElement, Set<ClassElement>>(),
|
||||
classesUsingTypeVariableExpression = new Set<ClassElement>();
|
||||
classesUsingTypeVariableExpression = new Set<ClassElement>(),
|
||||
checkedTypeArguments = new Set<DartType>(),
|
||||
checkedBounds = new Set<DartType>();
|
||||
|
||||
Set<ClassElement> directlyInstantiatedArguments;
|
||||
Set<ClassElement> allInstantiatedArguments;
|
||||
|
@ -56,6 +62,12 @@ class RuntimeTypes {
|
|||
classes.add(dependency);
|
||||
}
|
||||
|
||||
void registerTypeVariableBoundsSubtypeCheck(DartType typeArgument,
|
||||
DartType bound) {
|
||||
checkedTypeArguments.add(typeArgument);
|
||||
checkedBounds.add(bound);
|
||||
}
|
||||
|
||||
bool usingFactoryWithTypeArguments = false;
|
||||
|
||||
/**
|
||||
|
@ -298,19 +310,29 @@ class RuntimeTypes {
|
|||
|
||||
// We need to add classes occuring in function type arguments, like for
|
||||
// instance 'I' for [: o is C<f> :] where f is [: typedef I f(); :].
|
||||
for (DartType type in isChecks) {
|
||||
functionArgumentCollector.collect(type);
|
||||
void collectFunctionTypeArguments(Iterable<DartType> types) {
|
||||
for (DartType type in types) {
|
||||
functionArgumentCollector.collect(type);
|
||||
}
|
||||
}
|
||||
collectFunctionTypeArguments(isChecks);
|
||||
collectFunctionTypeArguments(checkedBounds);
|
||||
|
||||
for (DartType type in instantiatedTypes) {
|
||||
directCollector.collect(type);
|
||||
if (type.kind == TypeKind.INTERFACE) {
|
||||
ClassElement cls = type.element;
|
||||
for (DartType supertype in cls.allSupertypes) {
|
||||
superCollector.collect(supertype);
|
||||
void collectTypeArguments(Iterable<DartType> types,
|
||||
{bool isTypeArgument: false}) {
|
||||
for (DartType type in types) {
|
||||
directCollector.collect(type, isTypeArgument: isTypeArgument);
|
||||
if (type.kind == TypeKind.INTERFACE) {
|
||||
ClassElement cls = type.element;
|
||||
for (DartType supertype in cls.allSupertypes) {
|
||||
superCollector.collect(supertype, isTypeArgument: isTypeArgument);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
collectTypeArguments(instantiatedTypes);
|
||||
collectTypeArguments(checkedTypeArguments, isTypeArgument: true);
|
||||
|
||||
for (ClassElement cls in superCollector.classes.toList()) {
|
||||
for (DartType supertype in cls.allSupertypes) {
|
||||
superCollector.collect(supertype);
|
||||
|
@ -333,13 +355,22 @@ class RuntimeTypes {
|
|||
// We need to add types occuring in function type arguments, like for
|
||||
// instance 'J' for [: (J j) {} is f :] where f is
|
||||
// [: typedef void f(I i); :] and 'J' is a subtype of 'I'.
|
||||
for (DartType type in instantiatedTypes) {
|
||||
functionArgumentCollector.collect(type);
|
||||
void collectFunctionTypeArguments(Iterable<DartType> types) {
|
||||
for (DartType type in types) {
|
||||
functionArgumentCollector.collect(type);
|
||||
}
|
||||
}
|
||||
collectFunctionTypeArguments(instantiatedTypes);
|
||||
collectFunctionTypeArguments(checkedTypeArguments);
|
||||
|
||||
for (DartType type in isChecks) {
|
||||
collector.collect(type);
|
||||
void collectTypeArguments(Iterable<DartType> types,
|
||||
{bool isTypeArgument: false}) {
|
||||
for (DartType type in types) {
|
||||
collector.collect(type, isTypeArgument: isTypeArgument);
|
||||
}
|
||||
}
|
||||
collectTypeArguments(isChecks);
|
||||
collectTypeArguments(checkedBounds, isTypeArgument: true);
|
||||
|
||||
checkedArguments =
|
||||
collector.classes..addAll(functionArgumentCollector.classes);
|
||||
|
@ -718,8 +749,8 @@ class ArgumentCollector extends DartTypeVisitor {
|
|||
|
||||
ArgumentCollector(this.backend);
|
||||
|
||||
collect(DartType type) {
|
||||
type.accept(this, false);
|
||||
collect(DartType type, {bool isTypeArgument: false}) {
|
||||
type.accept(this, isTypeArgument);
|
||||
}
|
||||
|
||||
/// Collect all types in the list as if they were arguments of an
|
||||
|
|
|
@ -235,9 +235,10 @@ class TypeTestEmitter extends CodeEmitterHelper {
|
|||
Map<FunctionType, bool> getFunctionTypeChecksOn(DartType type) {
|
||||
Map<FunctionType, bool> functionTypeMap = new Map<FunctionType, bool>();
|
||||
for (FunctionType functionType in checkedFunctionTypes) {
|
||||
if (compiler.types.isSubtype(type, functionType)) {
|
||||
int maybeSubtype = compiler.types.computeSubtypeRelation(type, functionType);
|
||||
if (maybeSubtype == Types.IS_SUBTYPE) {
|
||||
functionTypeMap[functionType] = true;
|
||||
} else if (compiler.types.isPotentialSubtype(type, functionType)) {
|
||||
} else if (maybeSubtype == Types.MAYBE_SUBTYPE) {
|
||||
functionTypeMap[functionType] = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1779,7 +1779,7 @@ class TypeResolver {
|
|||
addTypeVariableBoundsCheck) {
|
||||
visitor.addDeferredAction(
|
||||
visitor.enclosingElement,
|
||||
() => checkTypeVariableBounds(node, type));
|
||||
() => checkTypeVariableBounds(visitor.mapping, node, type));
|
||||
}
|
||||
}
|
||||
visitor.useType(node, type);
|
||||
|
@ -1787,26 +1787,23 @@ class TypeResolver {
|
|||
}
|
||||
|
||||
/// Checks the type arguments of [type] against the type variable bounds.
|
||||
void checkTypeVariableBounds(TypeAnnotation node, GenericType type) {
|
||||
TypeDeclarationElement element = type.element;
|
||||
Link<DartType> typeArguments = type.typeArguments;
|
||||
Link<DartType> typeVariables = element.typeVariables;
|
||||
while (!typeVariables.isEmpty && !typeArguments.isEmpty) {
|
||||
TypeVariableType typeVariable = typeVariables.head;
|
||||
DartType bound = typeVariable.element.bound.subst(
|
||||
type.typeArguments, element.typeVariables);
|
||||
DartType typeArgument = typeArguments.head;
|
||||
void checkTypeVariableBounds(TreeElements elements,
|
||||
TypeAnnotation node, GenericType type) {
|
||||
void checkTypeVariableBound(_, DartType typeArgument,
|
||||
TypeVariableType typeVariable,
|
||||
DartType bound) {
|
||||
compiler.backend.registerTypeVariableBoundCheck(elements);
|
||||
if (!compiler.types.isSubtype(typeArgument, bound)) {
|
||||
compiler.reportWarningCode(node,
|
||||
MessageKind.INVALID_TYPE_VARIABLE_BOUND,
|
||||
{'typeVariable': typeVariable,
|
||||
'bound': bound,
|
||||
'typeArgument': typeArgument,
|
||||
'thisType': element.thisType});
|
||||
'thisType': type.element.thisType});
|
||||
}
|
||||
typeVariables = typeVariables.tail;
|
||||
typeArguments = typeArguments.tail;
|
||||
}
|
||||
};
|
||||
|
||||
compiler.types.checkTypeVariableBounds(type, checkTypeVariableBound);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1932,6 +1932,22 @@ class SsaBuilder extends ResolvedVisitor implements Visitor {
|
|||
return other;
|
||||
}
|
||||
|
||||
void assertIsSubtype(Node node, DartType subtype, DartType supertype,
|
||||
String message) {
|
||||
HInstruction subtypeInstruction = analyzeTypeArgument(subtype);
|
||||
HInstruction supertypeInstruction = analyzeTypeArgument(supertype);
|
||||
HInstruction messageInstruction =
|
||||
graph.addConstantString(new DartString.literal(message),
|
||||
node, compiler);
|
||||
Element element = backend.getAssertIsSubtype();
|
||||
var inputs = <HInstruction>[subtypeInstruction, supertypeInstruction,
|
||||
messageInstruction];
|
||||
HInstruction assertIsSubtype = new HInvokeStatic(
|
||||
element, inputs, subtypeInstruction.instructionType);
|
||||
compiler.backend.registerTypeVariableBoundsSubtypeCheck(subtype, supertype);
|
||||
add(assertIsSubtype);
|
||||
}
|
||||
|
||||
HGraph closeFunction() {
|
||||
// TODO(kasperl): Make this goto an implicit return.
|
||||
if (!isAborted()) closeAndGotoExit(new HGoto());
|
||||
|
@ -3689,6 +3705,8 @@ class SsaBuilder extends ResolvedVisitor implements Visitor {
|
|||
type = functionElement.computeTargetType(compiler, type);
|
||||
}
|
||||
|
||||
if (checkTypeVariableBounds(node, type)) return;
|
||||
|
||||
var inputs = <HInstruction>[];
|
||||
if (constructor.isGenerativeConstructor() &&
|
||||
Elements.isNativeOrExtendsNative(constructor.getEnclosingClass())) {
|
||||
|
@ -3746,6 +3764,62 @@ class SsaBuilder extends ResolvedVisitor implements Visitor {
|
|||
}
|
||||
}
|
||||
|
||||
/// In checked mode checks the [type] of [node] to be well-bounded. The method
|
||||
/// returns [:true:] if an error can be statically determined.
|
||||
bool checkTypeVariableBounds(NewExpression node, InterfaceType type) {
|
||||
if (!compiler.enableTypeAssertions) return false;
|
||||
|
||||
Map<DartType, Set<DartType>> seenChecksMap =
|
||||
new Map<DartType, Set<DartType>>();
|
||||
bool definitelyFails = false;
|
||||
|
||||
addTypeVariableBoundCheck(GenericType instance,
|
||||
DartType typeArgument,
|
||||
TypeVariableType typeVariable,
|
||||
DartType bound) {
|
||||
if (definitelyFails) return;
|
||||
|
||||
int subtypeRelation = compiler.types.computeSubtypeRelation(typeArgument, bound);
|
||||
if (subtypeRelation == Types.IS_SUBTYPE) return;
|
||||
|
||||
String message =
|
||||
"Can't create an instance of malbounded type '$type': "
|
||||
"'${typeArgument}' is not a subtype of bound '${bound}' for "
|
||||
"type variable '${typeVariable}' of type "
|
||||
"${type == instance
|
||||
? "'${type.element.thisType}'"
|
||||
: "'${instance.element.thisType}' on the supertype "
|
||||
"'${instance}' of '${type}'"
|
||||
}.";
|
||||
if (subtypeRelation == Types.NOT_SUBTYPE) {
|
||||
generateTypeError(node, message);
|
||||
definitelyFails = true;
|
||||
return;
|
||||
} else if (subtypeRelation == Types.MAYBE_SUBTYPE) {
|
||||
Set<DartType> seenChecks =
|
||||
seenChecksMap.putIfAbsent(typeArgument, () => new Set<DartType>());
|
||||
if (!seenChecks.contains(bound)) {
|
||||
seenChecks.add(bound);
|
||||
assertIsSubtype(node, typeArgument, bound, message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compiler.types.checkTypeVariableBounds(type, addTypeVariableBoundCheck);
|
||||
if (definitelyFails) {
|
||||
return true;
|
||||
}
|
||||
for (InterfaceType supertype in type.element.allSupertypes) {
|
||||
DartType instance = type.asInstanceOf(supertype.element);
|
||||
compiler.types.checkTypeVariableBounds(instance,
|
||||
addTypeVariableBoundCheck);
|
||||
if (definitelyFails) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
visitAssert(node) {
|
||||
if (!compiler.enableUserAssertions) {
|
||||
stack.add(graph.addConstantNull(compiler));
|
||||
|
@ -3860,6 +3934,10 @@ class SsaBuilder extends ResolvedVisitor implements Visitor {
|
|||
generateError(node, message, backend.getThrowRuntimeError());
|
||||
}
|
||||
|
||||
void generateTypeError(Node node, String message) {
|
||||
generateError(node, message, backend.getThrowTypeError());
|
||||
}
|
||||
|
||||
void generateAbstractClassInstantiationError(Node node, String message) {
|
||||
generateError(node,
|
||||
message,
|
||||
|
|
|
@ -1943,6 +1943,8 @@ class TypeErrorImplementation extends Error implements TypeError {
|
|||
: message = "type '${Primitives.objectTypeName(value)}' is not a subtype "
|
||||
"of type '$type'";
|
||||
|
||||
TypeErrorImplementation.fromMessage(String this.message);
|
||||
|
||||
String toString() => message;
|
||||
}
|
||||
|
||||
|
|
|
@ -286,6 +286,18 @@ Object assertSubtype(Object object, String isField, List checks,
|
|||
return object;
|
||||
}
|
||||
|
||||
/// Checks that the type represented by [subtype] is a subtype of [supertype].
|
||||
/// If not a type error with [message] is thrown.
|
||||
assertIsSubtype(var subtype, var supertype, String message) {
|
||||
if (!isSubtype(subtype, supertype)) {
|
||||
throwTypeError(message);
|
||||
}
|
||||
}
|
||||
|
||||
throwTypeError(message) {
|
||||
throw new TypeErrorImplementation.fromMessage(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the types in the list [arguments] are subtypes of the types in
|
||||
* list [checks] (at the respective positions), possibly applying [substitution]
|
||||
|
|
|
@ -27,6 +27,9 @@ implicit_setter_test: Fail # Issue 13917
|
|||
[ $compiler == none && $runtime == vm ]
|
||||
class_keyword_test/02: MissingCompileTimeError # Issue 13627
|
||||
|
||||
[ $compiler == none && $checked ]
|
||||
type_variable_bounds4_test/01: Fail # Issue 14006
|
||||
|
||||
[ $compiler == none && $unchecked ]
|
||||
# Only checked mode reports an error on type assignment
|
||||
# problems in compile time constants.
|
||||
|
|
|
@ -450,8 +450,6 @@ try_catch4_test: StaticWarning
|
|||
try_catch5_test: StaticWarning
|
||||
type_argument_in_super_type_test: StaticWarning
|
||||
typed_selector2_test: StaticWarning
|
||||
type_variable_bounds2_test/05: StaticWarning
|
||||
type_variable_bounds3_test/00: StaticWarning
|
||||
type_variable_identifier_expression_test: StaticWarning
|
||||
type_variable_scope2_test: StaticWarning
|
||||
unary_plus_negative_test: CompileTimeError
|
||||
|
|
|
@ -33,13 +33,9 @@ vm/*: Skip # Issue 12699
|
|||
[ $compiler == dart2js && $checked ]
|
||||
checked_setter2_test: Fail # Issue 11273
|
||||
default_factory2_test/01: Fail # Issue 12700
|
||||
type_variable_bounds_test/01: Fail # Issue 12702
|
||||
type_variable_bounds_test/02: Fail # Issue 12702
|
||||
type_variable_bounds_test/04: Fail # Issue 12702
|
||||
type_variable_bounds_test/05: Fail # Issue 12702
|
||||
type_variable_bounds2_test/00: Fail # Issue 12702
|
||||
type_variable_bounds2_test/03: Fail # Issue 12702
|
||||
type_variable_bounds2_test/05: Fail # Issue 12702
|
||||
type_variable_bounds2_test/01: Fail # Issue 12702
|
||||
type_variable_bounds2_test/04: Fail # Issue 12702
|
||||
type_variable_bounds2_test/06: Pass # Issue 12702 (pass for the wrong reasons).
|
||||
type_variable_bounds3_test/00: Fail # Issue 12702
|
||||
f_bounded_quantification_test/01: Fail # Issue 12703
|
||||
|
|
|
@ -41,7 +41,7 @@ main() {
|
|||
// However, while unsuccessfully trying to prove that A<String> is a K,
|
||||
// a malformed type is encountered in checked mode, resulting in a dynamic
|
||||
// type error.
|
||||
Expect.isTrue(a is !K); /// 05: dynamic type error
|
||||
Expect.isTrue(a is !K); /// 05: dynamic type error, static type warning
|
||||
}
|
||||
|
||||
a = new A<int>();
|
||||
|
|
|
@ -9,7 +9,7 @@ class A<K extends int> {
|
|||
|
||||
class B<X, Y> {
|
||||
foo(x) {
|
||||
return x is A<X>; /// 00: dynamic type error
|
||||
return x is A<X>; /// 00: dynamic type error, static type warning
|
||||
}
|
||||
}
|
||||
|
||||
|
|
69
tests/language/type_variable_bounds4_test.dart
Normal file
69
tests/language/type_variable_bounds4_test.dart
Normal file
|
@ -0,0 +1,69 @@
|
|||
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
|
||||
// for details. All rights reserved. Use of this source code is governed by a
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
// Test instantiation of object with malbounded types.
|
||||
|
||||
class A<T
|
||||
extends num /// 01: static type warning
|
||||
> {}
|
||||
class B<T> implements A<T> {}
|
||||
class C<T
|
||||
extends num /// 01: continued
|
||||
> implements B<T> {}
|
||||
|
||||
class Class<T> {
|
||||
newA() {
|
||||
new A<T>(); /// 01: continued
|
||||
}
|
||||
newB() {
|
||||
new B<T>(); /// 01: continued
|
||||
}
|
||||
newC() {
|
||||
new C<T>(); /// 01: continued
|
||||
}
|
||||
}
|
||||
|
||||
bool inCheckedMode() {
|
||||
try {
|
||||
var i = 42;
|
||||
String s = i;
|
||||
} on TypeError catch (e) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void test(bool expectTypeError, f()) {
|
||||
try {
|
||||
var v = f();
|
||||
if (expectTypeError && inCheckedMode()) {
|
||||
throw 'Missing type error instantiating ${v.runtimeType}';
|
||||
}
|
||||
} on TypeError catch (e) {
|
||||
if (!expectTypeError || !inCheckedMode()) {
|
||||
throw 'Unexpected type error: $e';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void main() {
|
||||
test(false, () => new A<int>());
|
||||
test(false, () => new B<int>());
|
||||
test(false, () => new C<int>());
|
||||
|
||||
test(true, () => new A<String>()); /// 01: continued
|
||||
test(true, () => new B<String>()); /// 01: continued
|
||||
test(true, () => new C<String>()); /// 01: continued
|
||||
|
||||
var c = new Class<int>();
|
||||
test(false, () => c.newA());
|
||||
test(false, () => c.newB());
|
||||
test(false, () => c.newC());
|
||||
|
||||
c = new Class<String>();
|
||||
test(true, () => c.newA()); /// 01: continued
|
||||
test(true, () => c.newB()); /// 01: continued
|
||||
test(true, () => c.newC()); /// 01: continued
|
||||
}
|
Loading…
Reference in a new issue