[dartdevc] implement set literal and enable-experiments support
Change-Id: Ibc45b1b1792d62a8f176e6c2f2f5c1e3134942f1
Reviewed-on: https://dart-review.googlesource.com/c/88400
Reviewed-by: Lasse R.H. Nielsen <lrn@google.com>
Reviewed-by: Vijay Menon <vsm@google.com>
Commit-Queue: Jenny Messerly <jmesserly@google.com>
diff --git a/pkg/dev_compiler/lib/src/analyzer/code_generator.dart b/pkg/dev_compiler/lib/src/analyzer/code_generator.dart
index fcc9651..15f4159 100644
--- a/pkg/dev_compiler/lib/src/analyzer/code_generator.dart
+++ b/pkg/dev_compiler/lib/src/analyzer/code_generator.dart
@@ -5727,8 +5727,21 @@
}
@override
- JS.Expression visitSetLiteral(SetLiteral node) =>
- throw new UnsupportedError('literal sets are not yet supported');
+ JS.Expression visitSetLiteral(SetLiteral node) {
+ var type = node.staticType as InterfaceType;
+ if (!node.isConst) {
+ var setType = _emitType(type);
+ if (node.elements.isEmpty) {
+ return js.call('#.new()', [setType]);
+ }
+ return js
+ .call('#.from([#])', [setType, _visitExpressionList(node.elements)]);
+ }
+ return _cacheConst(() => runtimeCall('constSet(#, [#])', [
+ _emitType((node.staticType as InterfaceType).typeArguments[0]),
+ _visitExpressionList(node.elements)
+ ]));
+ }
JS.Expression _emitConstList(
DartType elementType, List<JS.Expression> elements) {
diff --git a/pkg/dev_compiler/lib/src/analyzer/command.dart b/pkg/dev_compiler/lib/src/analyzer/command.dart
index 97188e1..2d32c8d 100644
--- a/pkg/dev_compiler/lib/src/analyzer/command.dart
+++ b/pkg/dev_compiler/lib/src/analyzer/command.dart
@@ -128,11 +128,12 @@
ArgResults argResults, AnalyzerOptions analyzerOptions,
{CompilerAnalysisDriver compilerDriver}) {
var compilerOpts = CompilerOptions.fromArguments(argResults);
+
var summaryPaths = compilerOpts.summaryModules.keys.toList();
if (compilerDriver == null ||
!compilerDriver.isCompatibleWith(analyzerOptions, summaryPaths)) {
- compilerDriver =
- CompilerAnalysisDriver(analyzerOptions, summaryPaths: summaryPaths);
+ compilerDriver = CompilerAnalysisDriver(analyzerOptions,
+ summaryPaths: summaryPaths, experiments: compilerOpts.experiments);
}
var outPaths = argResults['out'] as List<String>;
var moduleFormats = compilerOpts.moduleFormats;
diff --git a/pkg/dev_compiler/lib/src/analyzer/driver.dart b/pkg/dev_compiler/lib/src/analyzer/driver.dart
index 5e1e76a..aa5d565 100644
--- a/pkg/dev_compiler/lib/src/analyzer/driver.dart
+++ b/pkg/dev_compiler/lib/src/analyzer/driver.dart
@@ -73,7 +73,9 @@
ExtensionTypeSet get extensionTypes => _extensionTypes;
factory CompilerAnalysisDriver(AnalyzerOptions options,
- {SummaryDataStore summaryData, List<String> summaryPaths = const []}) {
+ {SummaryDataStore summaryData,
+ List<String> summaryPaths = const [],
+ Map<String, bool> experiments = const {}}) {
AnalysisEngine.instance.processRequiredPlugins();
var resourceProvider = options.resourceProvider;
@@ -81,6 +83,10 @@
var analysisOptions =
contextBuilder.getAnalysisOptions(options.analysisRoot);
+
+ (analysisOptions as AnalysisOptionsImpl).enabledExperiments =
+ experiments.entries.where((e) => e.value).map((e) => e.key).toList();
+
var dartSdk = contextBuilder.findSdk(null, analysisOptions);
// Read the summaries.
diff --git a/pkg/dev_compiler/lib/src/analyzer/module_compiler.dart b/pkg/dev_compiler/lib/src/analyzer/module_compiler.dart
index 868d50f..f62c54c 100644
--- a/pkg/dev_compiler/lib/src/analyzer/module_compiler.dart
+++ b/pkg/dev_compiler/lib/src/analyzer/module_compiler.dart
@@ -68,7 +68,6 @@
}
explicitSources.add(sourceUri);
}
-
var driver = compilerDriver.linkLibraries(explicitSources, analyzerOptions);
for (var libraryUri in driver.libraryUris) {
diff --git a/pkg/dev_compiler/lib/src/compiler/shared_command.dart b/pkg/dev_compiler/lib/src/compiler/shared_command.dart
index 4458f59..39c4759 100644
--- a/pkg/dev_compiler/lib/src/compiler/shared_command.dart
+++ b/pkg/dev_compiler/lib/src/compiler/shared_command.dart
@@ -81,6 +81,11 @@
final List<ModuleFormat> moduleFormats;
+ /// Experimental language features that are enabled/disabled, see
+ /// [the spec](https://github.com/dart-lang/sdk/blob/master/docs/process/experimental-flags.md)
+ /// for more details.
+ final Map<String, bool> experiments;
+
/// The name of the module.
///
/// This used when to support file concatenation. The JS module will contain
@@ -97,6 +102,7 @@
this.bazelMapping = const {},
this.summaryModules = const {},
this.moduleFormats = const [],
+ this.experiments = const {},
this.moduleName});
SharedCompilerOptions.fromArguments(ArgResults args,
@@ -106,6 +112,8 @@
summarizeApi: args['summarize'] as bool,
emitMetadata: args['emit-metadata'] as bool,
enableAsserts: args['enable-asserts'] as bool,
+ experiments:
+ _parseExperiments(args['enable-experiment'] as List<String>),
bazelMapping:
_parseBazelMappings(args['bazel-mapping'] as List<String>),
summaryModules: _parseCustomSummaryModules(
@@ -121,6 +129,9 @@
abbr: 's',
help: 'summary file(s) of imported libraries, optionally\n'
'with module import path: -s path.sum=js/import/path')
+ ..addMultiOption('enable-experiment',
+ help: 'used to enable/disable experimental language features',
+ hide: hide)
..addFlag('summarize',
help: 'emit an API summary file', defaultsTo: true, hide: hide)
..addFlag('source-map',
@@ -204,6 +215,20 @@
return pathToModule;
}
+Map<String, bool> _parseExperiments(List<String> arguments) {
+ var result = <String, bool>{};
+ for (var argument in arguments) {
+ for (var feature in argument.split(',')) {
+ if (feature.startsWith('no-')) {
+ result[feature.substring(3)] = false;
+ } else {
+ result[feature] = true;
+ }
+ }
+ }
+ return result;
+}
+
Map<String, String> _parseBazelMappings(List<String> argument) {
var mappings = <String, String>{};
for (var mapping in argument) {
diff --git a/pkg/dev_compiler/lib/src/kernel/command.dart b/pkg/dev_compiler/lib/src/kernel/command.dart
index 60ce102..15dd91c 100644
--- a/pkg/dev_compiler/lib/src/kernel/command.dart
+++ b/pkg/dev_compiler/lib/src/kernel/command.dart
@@ -10,7 +10,7 @@
import 'package:build_integration/file_system/multi_root.dart';
import 'package:cli_util/cli_util.dart' show getSdkPath;
import 'package:front_end/src/api_unstable/ddc.dart' as fe;
-import 'package:kernel/kernel.dart';
+import 'package:kernel/kernel.dart' hide MapEntry;
import 'package:kernel/text/ast_to_text.dart' as kernel show Printer;
import 'package:kernel/binary/ast_to_binary.dart' as kernel show BinaryPrinter;
import 'package:path/path.dart' as path;
@@ -177,6 +177,16 @@
fe.printDiagnosticMessage(message, print);
}
+ var experiments = <fe.ExperimentalFlag, bool>{};
+ for (var name in options.experiments.keys) {
+ var flag = fe.parseExperimentalFlag(name);
+ if (flag != null) {
+ experiments[flag] = options.experiments[name];
+ } else {
+ stderr.writeln("Unknown experiment flag '$name'.");
+ }
+ }
+
var oldCompilerState = compilerState;
compilerState = await fe.initializeCompiler(
oldCompilerState,
@@ -185,7 +195,8 @@
sourcePathToUri(librarySpecPath),
summaryModules.keys.toList(),
DevCompilerTarget(),
- fileSystem: fileSystem);
+ fileSystem: fileSystem,
+ experiments: experiments);
var output = argResults['out'] as String;
// TODO(jmesserly): is there a cleaner way to do this?
diff --git a/pkg/dev_compiler/lib/src/kernel/compiler.dart b/pkg/dev_compiler/lib/src/kernel/compiler.dart
index f034d66..3a2c138 100644
--- a/pkg/dev_compiler/lib/src/kernel/compiler.dart
+++ b/pkg/dev_compiler/lib/src/kernel/compiler.dart
@@ -183,6 +183,7 @@
final Class privateSymbolClass;
final Class linkedHashMapImplClass;
final Class identityHashMapImplClass;
+ final Class linkedHashSetClass;
final Class linkedHashSetImplClass;
final Class identityHashSetImplClass;
final Class syncIterableClass;
@@ -222,6 +223,7 @@
linkedHashMapImplClass = sdk.getClass('dart:_js_helper', 'LinkedMap'),
identityHashMapImplClass =
sdk.getClass('dart:_js_helper', 'IdentityMap'),
+ linkedHashSetClass = sdk.getClass('dart:collection', 'LinkedHashSet'),
linkedHashSetImplClass = sdk.getClass('dart:collection', '_HashSet'),
identityHashSetImplClass =
sdk.getClass('dart:collection', '_IdentityHashSet'),
@@ -4887,7 +4889,19 @@
@override
visitSetLiteral(SetLiteral node) {
- throw UnsupportedError("SetLiteral");
+ if (!node.isConst) {
+ var setType = visitInterfaceType(
+ InterfaceType(linkedHashSetClass, [node.typeArgument]));
+ if (node.expressions.isEmpty) {
+ return js.call('#.new()', [setType]);
+ }
+ return js.call(
+ '#.from([#])', [setType, _visitExpressionList(node.expressions)]);
+ }
+ return _cacheConst(() => runtimeCall('constSet(#, [#])', [
+ _emitType(node.typeArgument),
+ _visitExpressionList(node.expressions)
+ ]));
}
JS.Expression _emitConstList(
diff --git a/pkg/dev_compiler/test/nullable_inference_test.dart b/pkg/dev_compiler/test/nullable_inference_test.dart
index d14a0ee..73981f7 100644
--- a/pkg/dev_compiler/test/nullable_inference_test.dart
+++ b/pkg/dev_compiler/test/nullable_inference_test.dart
@@ -572,9 +572,11 @@
var mainUri = Uri.file('/memory/test.dart');
_fileSystem.entityForUri(mainUri).writeAsStringSync(code);
+ var oldCompilerState = _compilerState;
_compilerState = await fe.initializeCompiler(
- _compilerState, sdkUri, packagesUri, null, [], DevCompilerTarget(),
- fileSystem: _fileSystem);
+ oldCompilerState, sdkUri, packagesUri, null, [], DevCompilerTarget(),
+ fileSystem: _fileSystem, experiments: const {});
+ if (!identical(oldCompilerState, _compilerState)) inference = null;
fe.DdcResult result =
await fe.compile(_compilerState, [mainUri], diagnosticMessageHandler);
expect(succeeded, true);
diff --git a/pkg/dev_compiler/tool/input_sdk/patch/collection_patch.dart b/pkg/dev_compiler/tool/input_sdk/patch/collection_patch.dart
index 5b8d733..ab94a36 100644
--- a/pkg/dev_compiler/tool/input_sdk/patch/collection_patch.dart
+++ b/pkg/dev_compiler/tool/input_sdk/patch/collection_patch.dart
@@ -5,6 +5,7 @@
// Patch file for dart:collection classes.
import 'dart:_foreign_helper' show JS, JSExportName;
import 'dart:_runtime' as dart;
+import 'dart:_interceptors' show JSArray;
import 'dart:_js_helper'
show
NoInline,
@@ -248,7 +249,7 @@
int length = JS('', '#.size', map);
for (E key in objects) {
if (key == null) {
- key = null;
+ key = null; // converts undefined to null, if needed.
} else if (JS('bool', '#[#] !== #', key, dart.extensionSymbol('_equals'),
dart.identityEquals)) {
key = putLinkedMapKey(key, _keyMap);
@@ -302,6 +303,29 @@
}
}
+class ImmutableSet<E> extends _HashSet<E> {
+ ImmutableSet.from(JSArray entries) {
+ var map = _map;
+ for (Object key in entries) {
+ if (key == null) {
+ key = null; // converts undefined to null, if needed.
+ } else if (JS('bool', '#[#] !== #', key, dart.extensionSymbol('_equals'),
+ dart.identityEquals)) {
+ key = putLinkedMapKey(key, _keyMap);
+ }
+ JS('', '#.add(#)', map, key);
+ }
+ }
+
+ bool add(Object other) => throw _unsupported();
+ void addAll(Object other) => throw _unsupported();
+ void clear() => throw _unsupported();
+ bool remove(Object key) => throw _unsupported();
+
+ static Error _unsupported() =>
+ UnsupportedError("Cannot modify unmodifiable map");
+}
+
class _IdentityHashSet<E> extends _InternalSet<E>
implements HashSet<E>, LinkedHashSet<E> {
/// The backing store for this set.
diff --git a/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/operations.dart b/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/operations.dart
index 5aed9a6..b3865c1 100644
--- a/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/operations.dart
+++ b/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/operations.dart
@@ -502,19 +502,24 @@
/// The global constant map table.
final constantMaps = JS('', 'new Map()');
-constMap<K, V>(JSArray elements) {
- Function(Object, Object) lookupNonTerminal = JS('', '''function(map, key) {
- let result = map.get(key);
- if (result != null) return result;
- map.set(key, result = new Map());
- return result;
- }''');
+// TODO(leafp): This table gets quite large in apps.
+// Keeping the paths is probably expensive. It would probably
+// be more space efficient to just use a direct hash table with
+// an appropriately defined structural equality function.
+Object _lookupNonTerminal(Object map, Object key) {
+ var result = JS('', '#.get(#)', map, key);
+ if (result != null) return result;
+ JS('', '#.set(#, # = new Map())', map, key, result);
+ return result;
+}
+
+Map<K, V> constMap<K, V>(JSArray elements) {
var count = elements.length;
- var map = lookupNonTerminal(constantMaps, count);
+ var map = _lookupNonTerminal(constantMaps, count);
for (var i = 0; i < count; i++) {
- map = lookupNonTerminal(map, JS('', '#[#]', elements, i));
+ map = _lookupNonTerminal(map, JS('', '#[#]', elements, i));
}
- map = lookupNonTerminal(map, K);
+ map = _lookupNonTerminal(map, K);
var result = JS('', '#.get(#)', map, V);
if (result != null) return result;
result = ImmutableMap<K, V>.from(elements);
@@ -522,6 +527,21 @@
return result;
}
+final constantSets = JS('', 'new Map()');
+
+Set<E> constSet<E>(JSArray<E> elements) {
+ var count = elements.length;
+ var map = _lookupNonTerminal(constantSets, count);
+ for (var i = 0; i < count; i++) {
+ map = _lookupNonTerminal(map, JS('', '#[#]', elements, i));
+ }
+ var result = JS('', '#.get(#)', map, E);
+ if (result != null) return result;
+ result = ImmutableSet<E>.from(elements);
+ JS('', '#.set(#, #)', map, E, result);
+ return result;
+}
+
bool dassert(value) {
if (JS('!', '# != null && #[#] instanceof #', value, value, _runtimeType,
AbstractFunctionType)) {
@@ -576,22 +596,12 @@
/// - nested values of the object are themselves already canonicalized.
///
@JSExportName('const')
-const_(obj) => JS('', '''(() => {
- // TODO(leafp): This table gets quite large in apps.
- // Keeping the paths is probably expensive. It would probably
- // be more space efficient to just use a direct hash table with
- // an appropriately defined structural equality function.
- function lookupNonTerminal(map, key) {
- let result = map.get(key);
- if (result !== void 0) return result;
- map.set(key, result = new Map());
- return result;
- };
+const_(obj) => JS('', '''(() => {
let names = $getOwnNamesAndSymbols($obj);
let count = names.length;
// Index by count. All of the paths through this map
// will have 2*count length.
- let map = lookupNonTerminal($constants, count);
+ let map = $_lookupNonTerminal($constants, count);
// TODO(jmesserly): there's no guarantee in JS that names/symbols are
// returned in the same order.
//
@@ -606,8 +616,8 @@
// See issue https://github.com/dart-lang/sdk/issues/30876
for (let i = 0; i < count; i++) {
let name = names[i];
- map = lookupNonTerminal(map, name);
- map = lookupNonTerminal(map, $obj[name]);
+ map = $_lookupNonTerminal(map, name);
+ map = $_lookupNonTerminal(map, $obj[name]);
}
// TODO(leafp): It may be the case that the reified type
// is always one of the keys already used above?
@@ -627,16 +637,10 @@
/// Canonicalize a constant list
constList(elements, elementType) => JS('', '''(() => {
- function lookupNonTerminal(map, key) {
- let result = map.get(key);
- if (result !== void 0) return result;
- map.set(key, result = new Map());
- return result;
- };
let count = $elements.length;
- let map = lookupNonTerminal($constantLists, count);
+ let map = $_lookupNonTerminal($constantLists, count);
for (let i = 0; i < count; i++) {
- map = lookupNonTerminal(map, elements[i]);
+ map = $_lookupNonTerminal(map, elements[i]);
}
let value = map.get($elementType);
if (value) return value;
diff --git a/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/types.dart b/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/types.dart
index 4602bc2..13d6f88 100644
--- a/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/types.dart
+++ b/pkg/dev_compiler/tool/input_sdk/private/ddc_runtime/types.dart
@@ -306,13 +306,6 @@
return $_memoizeArray($map, key, () => $named);
})()''');
-_lookupNonTerminal(map, key) => JS('', '''(() => {
- let result = $map.get($key);
- if (result !== void 0) return result;
- $map.set($key, result = new Map());
- return result;
-})()''');
-
// TODO(leafp): This handles some low hanging fruit, but
// really we should make all of this faster, and also
// handle more cases here.
diff --git a/pkg/front_end/lib/src/api_unstable/ddc.dart b/pkg/front_end/lib/src/api_unstable/ddc.dart
index 1a85b55..0a129d8 100644
--- a/pkg/front_end/lib/src/api_unstable/ddc.dart
+++ b/pkg/front_end/lib/src/api_unstable/ddc.dart
@@ -12,6 +12,8 @@
import '../api_prototype/diagnostic_message.dart' show DiagnosticMessageHandler;
+import '../api_prototype/experimental_flags.dart' show ExperimentalFlag;
+
import '../api_prototype/file_system.dart' show FileSystem;
import '../api_prototype/standard_file_system.dart' show StandardFileSystem;
@@ -26,6 +28,9 @@
export '../api_prototype/diagnostic_message.dart' show DiagnosticMessage;
+export '../api_prototype/experimental_flags.dart'
+ show ExperimentalFlag, parseExperimentalFlag;
+
export '../api_prototype/kernel_generator.dart' show kernelForComponent;
export '../api_prototype/memory_file_system.dart' show MemoryFileSystem;
@@ -59,7 +64,8 @@
Uri librariesSpecificationUri,
List<Uri> inputSummaries,
Target target,
- {FileSystem fileSystem}) async {
+ {FileSystem fileSystem,
+ Map<ExperimentalFlag, bool> experiments}) async {
inputSummaries.sort((a, b) => a.toString().compareTo(b.toString()));
bool listEqual(List<Uri> a, List<Uri> b) {
if (a.length != b.length) return false;
@@ -69,11 +75,21 @@
return true;
}
+ bool mapEqual(Map<ExperimentalFlag, bool> a, Map<ExperimentalFlag, bool> b) {
+ if (a == null || b == null) return a == b;
+ if (a.length != b.length) return false;
+ for (var flag in a.keys) {
+ if (!b.containsKey(flag) || a[flag] != b[flag]) return false;
+ }
+ return true;
+ }
+
if (oldState != null &&
oldState.options.sdkSummary == sdkSummary &&
oldState.options.packagesFileUri == packagesFile &&
oldState.options.librariesSpecificationUri == librariesSpecificationUri &&
- listEqual(oldState.options.inputSummaries, inputSummaries)) {
+ listEqual(oldState.options.inputSummaries, inputSummaries) &&
+ mapEqual(oldState.options.experimentalFlags, experiments)) {
// Reuse old state.
// These libraries are marked external when compiling. If not un-marking
@@ -96,6 +112,7 @@
..librariesSpecificationUri = librariesSpecificationUri
..target = target
..fileSystem = fileSystem ?? StandardFileSystem.instance;
+ if (experiments != null) options.experimentalFlags = experiments;
ProcessedOptions processedOpts = new ProcessedOptions(options: options);
diff --git a/tests/language_2/language_2_dartdevc.status b/tests/language_2/language_2_dartdevc.status
index 1228430..310f9a1 100644
--- a/tests/language_2/language_2_dartdevc.status
+++ b/tests/language_2/language_2_dartdevc.status
@@ -122,7 +122,6 @@
regress_29784_test/02: MissingCompileTimeError
regress_30339_test: CompileTimeError # As expected. Should we make this a multi test?
regress_33479_test/01: Crash # Issue #33479
-set_literals/*: Skip
setter3_test/01: CompileTimeError # Invalid test, see https://github.com/dart-lang/sdk/issues/33837
setter3_test/02: CompileTimeError # Invalid test, see https://github.com/dart-lang/sdk/issues/33837
stacktrace_test: RuntimeError # Issue 29920
@@ -275,7 +274,6 @@
regress_29405_test: CompileTimeError # Issue 31402 Error: A value of type '#lib2::Foo' can't be assigned to a variable of type '(#lib2::Foo) → void'.
regress_30339_test: RuntimeError # Uncaught Expect.isTrue(false) fails.
regress_30339_test: CompileTimeError
-set_literals/*: Skip
setter_no_getter_test/01: CompileTimeError
super_bound_closure_test/none: CompileTimeError # Issue 31533
super_call4_test/01: MissingCompileTimeError
diff --git a/tests/language_2/set_literals/set_literal_test.dart b/tests/language_2/set_literals/set_literal_test.dart
index 6ea5fee..cc17fae 100644
--- a/tests/language_2/set_literals/set_literal_test.dart
+++ b/tests/language_2/set_literals/set_literal_test.dart
@@ -87,27 +87,29 @@
// Nested literals.
Object o = {{2}};
- Expect.type<LinkedHashSet<LinkedHashSet<int>>>(o);
+ Expect.type<LinkedHashSet<Set<int>>>(o);
+ Expect.type<LinkedHashSet<int>>((o as Set).first);
Set<Set<int>> set = o;
Expect.equals(1, set.length);
Expect.equals(1, set.first.length);
Expect.equals(2, set.first.first);
o = {{2}, <int>{}};
- Expect.type<LinkedHashSet<LinkedHashSet<int>>>(o);
+ Expect.type<LinkedHashSet<Set<int>>>(o);
+ Expect.type<LinkedHashSet<int>>((o as Set).first);
set = o;
Expect.equals(2, set.length);
Expect.equals(1, set.first.length);
Expect.equals(2, set.first.first);
- set = {{}};
- Expect.type<Set<Map<dynamic, dynamic>>>(set);
- Expect.equals(1, set.length);
- Expect.equals(0, set.first.length);
+ var set2 = {{}};
+ Expect.type<Set<Map<dynamic, dynamic>>>(set2);
+ Expect.equals(1, set2.length);
+ Expect.equals(0, set2.first.length);
- set = {{1}, {}}; // Set<Object>
- Expect.type<Set<Object>>(set);
- Expect.notType<Set<Set<Object>>>(set);
+ var set3 = {{1}, {}}; // Set<Object>
+ Expect.type<Set<Object>>(set3);
+ Expect.notType<Set<Set<Object>>>(set3);
// Trailing comma.
Iterable<Object> i;
@@ -117,8 +119,8 @@
o = {1, 2, 3,};
Expect.type<Set<int>>(o);
- set = o;
- Expect.equals(3, set.length);
+ Set<Object> set4 = o;
+ Expect.equals(3, set4.length);
}
class Equality {