[dart2js] Disallow default clauses on switches over enums and enum-like classes.

In a recent change I added a new value to an enum and had no easy way to statically find all the switches where I might need to handle that new enum value. Instead I had to wait for runtime errors (or worse-yet incorrect generated code) to find what I had missed.

Though it may be add a little inconvience to now allow "default" cases, the static warnings/checks it provides are worth it.

Change-Id: I2347bcec77ee8bc0a5618ea4dbc6e0840bdf2f6c
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/343321
Commit-Queue: Nate Biggs <natebiggs@google.com>
Reviewed-by: Mayank Patke <fishythefish@google.com>
Reviewed-by: Sigmund Cherem <sigmund@google.com>
This commit is contained in:
Nate Biggs 2024-01-03 00:24:09 +00:00 committed by Commit Queue
parent 334ac53602
commit acbbfd60fb
22 changed files with 154 additions and 64 deletions

View file

@ -17,3 +17,5 @@ linter:
- prefer_if_null_operators
- prefer_null_aware_operators
- use_super_parameters
- exhaustive_cases
- no_default_cases

View file

@ -2263,9 +2263,6 @@ abstract class DartTypes {
if (!_isSubtype(sArgs[i], tArgs[i], env) ||
!_isSubtype(tArgs[i], sArgs[i], env)) return false;
break;
default:
throw StateError(
"Invalid variance ${variances[i]} used for subtype check.");
}
}
return true;

View file

@ -492,7 +492,6 @@ class FlatTypeMask extends TypeMask {
return other.withFlags(
isNullable: includeNull, hasLateSentinel: includeLateSentinel);
case SubclassResultKind.SET:
default:
if (result.classes.isEmpty) {
return includeNull
? TypeMask.empty(hasLateSentinel: includeLateSentinel)

View file

@ -69,7 +69,13 @@ String? computeKernelElementNameForSourceMaps(
String enclosingMemberName =
computeElementNameForSourceMaps(enclosingMember, callStructure)!;
return '$enclosingMemberName.$name';
default:
case MemberKind.constructor:
case MemberKind.constructorBody:
case MemberKind.recordGetter:
case MemberKind.signature:
case MemberKind.closureField:
case MemberKind.generatorBody:
case MemberKind.parameterStub:
return computeElementNameForSourceMaps(member, callStructure);
}
}
@ -190,7 +196,7 @@ class KernelSourceInformationBuilder implements SourceInformationBuilder {
case MemberKind.closureField:
case MemberKind.signature:
case MemberKind.generatorBody:
// TODO(sra): Should we target the generator itself for generatorBody
// TODO(sra): Should we target the generator itself for generatorBody?
break;
}
return _buildTreeNode(base ?? definition.node as ir.TreeNode, name: name);
@ -295,7 +301,12 @@ class KernelSourceInformationBuilder implements SourceInformationBuilder {
case MemberKind.closureCall:
final node = definition.node as ir.LocalFunction;
return _buildFunctionExit(node, node.function);
default:
case MemberKind.signature:
case MemberKind.closureField:
case MemberKind.recordGetter:
case MemberKind.generatorBody:
case MemberKind.parameterStub:
break;
}
return _buildTreeNode(definition.node as ir.TreeNode);
}

View file

@ -1568,8 +1568,6 @@ class EvaluationComplexity {
case ComplexityLevel.definitelyLazy:
sb.write('lazy');
break;
default:
throw UnsupportedError("Unexpected complexity level $level");
}
return sb.toString();
}

View file

@ -77,9 +77,6 @@ AsyncMarker getAsyncMarker(ir.FunctionNode node) {
return AsyncMarker.SYNC;
case ir.AsyncMarker.SyncStar:
return AsyncMarker.SYNC_STAR;
default:
throw UnsupportedError(
"Async marker ${node.asyncMarker} is not supported.");
}
}

View file

@ -156,7 +156,24 @@ class CodegenEnqueuer extends Enqueuer {
// TODO(johnniwinther): Should this be tracked with _MemberUsage ?
listener.registerUsedElement(staticUse.element as MemberEntity);
break;
default:
case StaticUseKind.CALL_METHOD:
case StaticUseKind.CLOSURE:
case StaticUseKind.CLOSURE_CALL:
case StaticUseKind.DIRECT_INVOKE:
case StaticUseKind.FIELD_CONSTANT_INIT:
case StaticUseKind.FIELD_INIT:
case StaticUseKind.INSTANCE_FIELD_GET:
case StaticUseKind.INSTANCE_FIELD_SET:
case StaticUseKind.STATIC_GET:
case StaticUseKind.STATIC_INVOKE:
case StaticUseKind.STATIC_SET:
case StaticUseKind.STATIC_TEAR_OFF:
case StaticUseKind.SUPER_FIELD_SET:
case StaticUseKind.SUPER_GET:
case StaticUseKind.SUPER_INVOKE:
case StaticUseKind.SUPER_SETTER_SET:
case StaticUseKind.SUPER_TEAR_OFF:
case StaticUseKind.WEAK_STATIC_TEAR_OFF:
break;
}
});

View file

@ -104,7 +104,20 @@ class CodegenImpactTransformer {
_closedWorld.outputUnitData.registerConstantDeferredUse(
constantUse.value as DeferredGlobalConstantValue);
break;
default:
case ConstantValueKind.BOOL:
case ConstantValueKind.DOUBLE:
case ConstantValueKind.DUMMY_INTERCEPTOR:
case ConstantValueKind.FUNCTION:
case ConstantValueKind.INT:
case ConstantValueKind.INTERCEPTOR:
case ConstantValueKind.JAVASCRIPT_OBJECT:
case ConstantValueKind.JS_NAME:
case ConstantValueKind.LATE_SENTINEL:
case ConstantValueKind.NULL:
case ConstantValueKind.RECORD:
case ConstantValueKind.STRING:
case ConstantValueKind.TYPE:
case ConstantValueKind.UNREACHABLE:
break;
}
}
@ -175,6 +188,9 @@ class CodegenImpactTransformer {
_impacts.asyncStarBody
.registerImpact(transformed, _elementEnvironment);
break;
case AsyncMarker.SYNC:
// No implicit impacts.
break;
}
}

View file

@ -1988,9 +1988,6 @@ abstract class ModularNamer {
return asName(fixedNames.recordShapeTag);
case JsGetName.RECORD_SHAPE_TYPE_PROPERTY:
return asName(fixedNames.recordShapeRecipe);
default:
throw failedAt(spannable ?? CURRENT_ELEMENT_SPANNABLE,
'Error: Namer has no name for "$name".');
}
}
}

View file

@ -10,7 +10,7 @@ import '../js_backend/interceptor_data.dart' show InterceptorData;
import '../js_model/js_world.dart' show JClosedWorld;
import '../universe/class_hierarchy.dart' show ClassHierarchy;
enum _Kind {
enum IsTestSpecializationKind {
isNull,
isNotNull,
isString,
@ -22,27 +22,30 @@ enum _Kind {
}
class IsTestSpecialization {
static const isNull = IsTestSpecialization._(_Kind.isNull);
static const isNotNull = IsTestSpecialization._(_Kind.isNotNull);
static const isString = IsTestSpecialization._(_Kind.isString);
static const isBool = IsTestSpecialization._(_Kind.isBool);
static const isNum = IsTestSpecialization._(_Kind.isNum);
static const isInt = IsTestSpecialization._(_Kind.isInt);
static const isArrayTop = IsTestSpecialization._(_Kind.isArrayTop);
static const isNull = IsTestSpecialization._(IsTestSpecializationKind.isNull);
static const isNotNull =
IsTestSpecialization._(IsTestSpecializationKind.isNotNull);
static const isString =
IsTestSpecialization._(IsTestSpecializationKind.isString);
static const isBool = IsTestSpecialization._(IsTestSpecializationKind.isBool);
static const isNum = IsTestSpecialization._(IsTestSpecializationKind.isNum);
static const isInt = IsTestSpecialization._(IsTestSpecializationKind.isInt);
static const isArrayTop =
IsTestSpecialization._(IsTestSpecializationKind.isArrayTop);
final _Kind _kind;
final IsTestSpecializationKind kind;
final InterfaceType? _type;
const IsTestSpecialization._(this._kind) : _type = null;
const IsTestSpecialization._(this.kind) : _type = null;
const IsTestSpecialization._instanceof(InterfaceType type)
: _kind = _Kind.isInstanceof,
: kind = IsTestSpecializationKind.isInstanceof,
_type = type;
bool get isInstanceof => _kind == _Kind.isInstanceof;
bool get isInstanceof => kind == IsTestSpecializationKind.isInstanceof;
InterfaceType get interfaceType {
assert(_kind == _Kind.isInstanceof);
assert(kind == IsTestSpecializationKind.isInstanceof);
return _type!;
}
}

View file

@ -167,7 +167,9 @@ class ClosureDataImpl implements ClosureData {
const CapturedScope();
case MemberKind.parameterStub:
return const CapturedScope();
default:
case MemberKind.closureField:
case MemberKind.generatorBody:
case MemberKind.recordGetter:
throw failedAt(entity, "Unexpected member definition $definition");
}
}

View file

@ -642,7 +642,12 @@ void forEachOrderedParameter(JsToElementMap elementMap, FunctionEntity function,
forEachOrderedParameterByFunctionNode(
node.function, parameterStructure, handleParameter);
return;
default:
case MemberKind.closureField:
case MemberKind.generatorBody:
case MemberKind.recordGetter:
case MemberKind.signature:
case MemberKind.parameterStub:
break;
}
failedAt(function, "Unexpected function definition $definition.");
}

View file

@ -301,7 +301,14 @@ class JsKernelToElementMap implements JsToElementMap, IrToElementMap {
methodMap[node as ir.Procedure] = member as JFunction;
}
break;
default:
case MemberKind.closureCall:
case MemberKind.closureField:
case MemberKind.constructorBody:
case MemberKind.generatorBody:
case MemberKind.recordGetter:
case MemberKind.signature:
case MemberKind.parameterStub:
break;
}
}
source.end(memberTag);

View file

@ -123,7 +123,8 @@ void reportFrontEndMessage(
case fe.Severity.info:
reporter.reportInfo(mainMessage, infos);
break;
default:
case fe.Severity.context:
case fe.Severity.ignored:
throw UnimplementedError('unhandled severity ${message.severity}');
}
}

View file

@ -158,7 +158,25 @@ class ResolutionEnqueuer extends Enqueuer {
constructor: staticUse.element as ConstructorEntity?,
globalDependency: false);
break;
default:
case StaticUseKind.STATIC_TEAR_OFF:
case StaticUseKind.SUPER_TEAR_OFF:
case StaticUseKind.SUPER_FIELD_SET:
case StaticUseKind.SUPER_GET:
case StaticUseKind.SUPER_SETTER_SET:
case StaticUseKind.SUPER_INVOKE:
case StaticUseKind.INSTANCE_FIELD_GET:
case StaticUseKind.INSTANCE_FIELD_SET:
case StaticUseKind.CLOSURE:
case StaticUseKind.CLOSURE_CALL:
case StaticUseKind.CALL_METHOD:
case StaticUseKind.DIRECT_INVOKE:
case StaticUseKind.INLINING:
case StaticUseKind.STATIC_INVOKE:
case StaticUseKind.STATIC_GET:
case StaticUseKind.STATIC_SET:
case StaticUseKind.FIELD_INIT:
case StaticUseKind.FIELD_CONSTANT_INIT:
case StaticUseKind.WEAK_STATIC_TEAR_OFF:
break;
}
});

View file

@ -380,9 +380,6 @@ class RandomAccessFileOutputProvider implements api.CompilerOutput {
}
uri = out!.resolve('$name.$extension');
break;
default:
onFailure('Unknown output type: $type');
throw StateError('unreachable');
}
return uri;
}

View file

@ -971,12 +971,19 @@ class KernelSsaGraphBuilder extends ir.VisitorDefault<void>
constructorData, field.enclosingClass!);
MemberDefinition definition = _elementMap.getMemberDefinition(field);
late final ir.Field node;
ir.Field node;
switch (definition.kind) {
case MemberKind.regular:
node = definition.node as ir.Field;
break;
default:
case MemberKind.constructor:
case MemberKind.constructorBody:
case MemberKind.closureCall:
case MemberKind.closureField:
case MemberKind.signature:
case MemberKind.generatorBody:
case MemberKind.recordGetter:
case MemberKind.parameterStub:
failedAt(field, "Unexpected member definition $definition.");
}
@ -5118,7 +5125,7 @@ class KernelSsaGraphBuilder extends ir.VisitorDefault<void>
String typesAccess = _emitter.generateEmbeddedGlobalAccessString(TYPES);
return js.js.expressionTemplateFor("$typesAccess[#]");
default:
case JsBuiltin.isJsInteropTypeArgument:
reporter.internalError(
NO_LOCATION_SPANNABLE, "Unhandled Builtin: $builtin");
return null;
@ -6857,8 +6864,11 @@ class KernelSsaGraphBuilder extends ir.VisitorDefault<void>
final node = definition.node as ir.LocalFunction;
node.function.body!.accept(this);
return;
default:
break;
case MemberKind.closureField:
case MemberKind.generatorBody:
case MemberKind.recordGetter:
case MemberKind.signature:
case MemberKind.parameterStub:
}
failedAt(function, "Unexpected inlined function: $definition");
}

View file

@ -28,7 +28,7 @@ import '../js_backend/namer.dart' show ModularNamer;
import '../js_backend/runtime_types_codegen.dart';
import '../js_backend/runtime_types_new.dart'
show RecipeEncoder, RecipeEncoding, indexTypeVariable;
import '../js_backend/specialized_checks.dart' show IsTestSpecialization;
import '../js_backend/specialized_checks.dart' show IsTestSpecializationKind;
import '../js_backend/type_reference.dart' show TypeReference;
import '../js_emitter/js_emitter.dart' show ModularEmitter;
import '../js_model/elements.dart' show JGeneratorBody;
@ -3192,41 +3192,37 @@ class SsaCodeGenerator implements HVisitor, HBlockInformationVisitor {
}
late js.Expression test;
switch (node.specialization) {
case IsTestSpecialization.isNull:
case IsTestSpecialization.isNotNull:
switch (node.specialization.kind) {
case IsTestSpecializationKind.isNull:
case IsTestSpecializationKind.isNotNull:
// These cases should be lowered using [HIdentity] during optimization.
failedAt(node, 'Missing lowering');
case IsTestSpecialization.isString:
case IsTestSpecializationKind.isString:
test = typeof("string");
break;
case IsTestSpecialization.isBool:
case IsTestSpecializationKind.isBool:
test = isTest(_commonElements.specializedIsBool);
break;
case IsTestSpecialization.isNum:
case IsTestSpecializationKind.isNum:
test = typeof("number");
break;
case IsTestSpecialization.isInt:
case IsTestSpecializationKind.isInt:
test = isTest(_commonElements.specializedIsInt);
break;
case IsTestSpecialization.isArrayTop:
case IsTestSpecializationKind.isArrayTop:
test = handleNegative(js.js('Array.isArray(#)', [value]));
break;
default:
if (node.specialization.isInstanceof) {
InterfaceType type = node.specialization.interfaceType;
_registry.registerTypeUse(TypeUse.constructorReference(type));
test = handleNegative(js.js('# instanceof #',
[value, _emitter.constructorAccess(type.element)]));
} else {
failedAt(node, 'Unknown specialization: ${node.specialization}');
}
case IsTestSpecializationKind.isInstanceof:
InterfaceType type = node.specialization.interfaceType;
_registry.registerTypeUse(TypeUse.constructorReference(type));
test = handleNegative(js.js('# instanceof #',
[value, _emitter.constructorAccess(type.element)]));
}
push(test.withSourceInformation(node.sourceInformation));
}

View file

@ -190,6 +190,8 @@ class SsaFunctionCompiler implements FunctionCompiler {
asyncTypeParameter,
name);
break;
case AsyncMarker.SYNC:
throw StateError('Cannot rewrite sync method as async.');
}
return rewriter.rewrite(
code as js.Fun, bodySourceInformation, exitSourceInformation);

View file

@ -236,7 +236,7 @@ abstract class TypeBuilder {
case ClassTypeVariableAccess.property:
usesInstanceParameters = true;
return;
default:
case ClassTypeVariableAccess.none:
builder.reporter.internalError(
type.element, 'Unexpected type variable in static context.');
}

View file

@ -147,7 +147,7 @@ class InstantiationInfo {
break;
case Instantiation.UNINSTANTIATED:
break;
default:
case Instantiation.INDIRECTLY_INSTANTIATED:
throw StateError("Instantiation $kind is not allowed.");
}
}

View file

@ -334,7 +334,22 @@ class StaticUse {
case StaticUseKind.CLOSURE:
sb.write('def:');
break;
default:
case StaticUseKind.STATIC_TEAR_OFF:
case StaticUseKind.SUPER_TEAR_OFF:
case StaticUseKind.SUPER_GET:
case StaticUseKind.SUPER_INVOKE:
case StaticUseKind.INSTANCE_FIELD_GET:
case StaticUseKind.CLOSURE_CALL:
case StaticUseKind.CALL_METHOD:
case StaticUseKind.CONSTRUCTOR_INVOKE:
case StaticUseKind.CONST_CONSTRUCTOR_INVOKE:
case StaticUseKind.DIRECT_INVOKE:
case StaticUseKind.INLINING:
case StaticUseKind.STATIC_INVOKE:
case StaticUseKind.STATIC_GET:
case StaticUseKind.FIELD_CONSTANT_INIT:
case StaticUseKind.WEAK_STATIC_TEAR_OFF:
break;
}
final member = element;
if (member is MemberEntity) {