diff --git a/pkg/vm/lib/transformations/type_flow/native_code.dart b/pkg/vm/lib/transformations/type_flow/native_code.dart index 6d6fe93c8e2b1..7ea23106cc8d1 100644 --- a/pkg/vm/lib/transformations/type_flow/native_code.dart +++ b/pkg/vm/lib/transformations/type_flow/native_code.dart @@ -200,11 +200,17 @@ class PragmaEntryPointsVisitor extends RecursiveVisitor { addSelector(CallKind.PropertyGet); break; case PragmaEntryPointType.SetterOnly: + if (field.isFinal) { + throw "Error: can't use 'set' in entry-point pragma for final field " + "$field"; + } addSelector(CallKind.PropertySet); break; case PragmaEntryPointType.Always: addSelector(CallKind.PropertyGet); - addSelector(CallKind.PropertySet); + if (!field.isFinal) { + addSelector(CallKind.PropertySet); + } break; } diff --git a/runtime/vm/compiler/aot/precompiler.cc b/runtime/vm/compiler/aot/precompiler.cc index 3607520e85aad..45db21837fed2 100644 --- a/runtime/vm/compiler/aot/precompiler.cc +++ b/runtime/vm/compiler/aot/precompiler.cc @@ -88,121 +88,6 @@ DECLARE_FLAG(bool, print_instruction_stats); #if defined(DART_PRECOMPILER) && !defined(TARGET_ARCH_DBC) && \ !defined(TARGET_ARCH_IA32) -class DartPrecompilationPipeline : public DartCompilationPipeline { - public: - explicit DartPrecompilationPipeline(Zone* zone, - FieldTypeMap* field_map = NULL) - : zone_(zone), - result_type_(CompileType::None()), - field_map_(field_map), - class_(Class::Handle(zone)), - fields_(Array::Handle(zone)), - field_(Field::Handle(zone)) {} - - virtual void FinalizeCompilation(FlowGraph* flow_graph) { - if ((field_map_ != NULL) && - flow_graph->function().IsGenerativeConstructor()) { - const Function& constructor = flow_graph->function(); - for (BlockIterator block_it = flow_graph->reverse_postorder_iterator(); - !block_it.Done(); block_it.Advance()) { - ForwardInstructionIterator it(block_it.Current()); - for (; !it.Done(); it.Advance()) { - StoreInstanceFieldInstr* store = it.Current()->AsStoreInstanceField(); - if (store != NULL) { - if (!store->field().IsNull() && store->field().is_final()) { -#ifndef PRODUCT - if (FLAG_trace_precompiler && FLAG_support_il_printer) { - THR_Print("Found store to %s <- %s\n", - store->field().ToCString(), - store->value()->Type()->ToCString()); - } -#endif // !PRODUCT - RecordFieldStore(constructor, store->field(), - store->value()->Type()); - } - } - } - } - - // Final fields which were not initialized in this constructor are - // implicitly initialized with null. - class_ = flow_graph->function().Owner(); - fields_ = class_.fields(); - for (intptr_t i = 0; i < fields_.Length(); ++i) { - field_ ^= fields_.At(i); - if (!field_.is_static() && field_.is_final()) { - FieldTypePair* entry = field_map_->Lookup(&field_); - if ((entry == nullptr) || - (entry->field_info.constructor != constructor.raw())) { -#ifndef PRODUCT - if (FLAG_trace_precompiler && FLAG_support_il_printer) { - THR_Print("Implicit initialization %s <- null\n", - field_.ToCString()); - } -#endif // !PRODUCT - CompileType null_type = CompileType::Null(); - RecordFieldStore(constructor, field_, &null_type); - } - } - } - } - - CompileType result_type = CompileType::None(); - for (BlockIterator block_it = flow_graph->reverse_postorder_iterator(); - !block_it.Done(); block_it.Advance()) { - ForwardInstructionIterator it(block_it.Current()); - for (; !it.Done(); it.Advance()) { - ReturnInstr* return_instr = it.Current()->AsReturn(); - if (return_instr != NULL) { - result_type.Union(return_instr->InputAt(0)->Type()); - } - } - } - result_type_ = result_type; - } - - void RecordFieldStore(const Function& constructor, - const Field& field, - CompileType* value_type) { - FieldTypePair* entry = field_map_->Lookup(&field); - if (entry == NULL) { - field_map_->Insert( - FieldTypePair(&Field::Handle(zone_, field.raw()), // Re-wrap. - value_type->ToCid(), constructor.raw())); -#ifndef PRODUCT - if (FLAG_trace_precompiler && FLAG_support_il_printer) { - THR_Print(" initial type = %s\n", value_type->ToCString()); - } -#endif // !PRODUCT - return; - } - CompileType type = CompileType::FromCid(entry->field_info.cid); -#ifndef PRODUCT - if (FLAG_trace_precompiler && FLAG_support_il_printer) { - THR_Print(" old type = %s\n", type.ToCString()); - } -#endif // !PRODUCT - type.Union(value_type); -#ifndef PRODUCT - if (FLAG_trace_precompiler && FLAG_support_il_printer) { - THR_Print(" new type = %s\n", type.ToCString()); - } -#endif // !PRODUCT - entry->field_info.cid = type.ToCid(); - entry->field_info.constructor = constructor.raw(); - } - - CompileType result_type() { return result_type_; } - - private: - Zone* zone_; - CompileType result_type_; - FieldTypeMap* field_map_; - Class& class_; - Array& fields_; - Field& field_; -}; - class PrecompileParsedFunctionHelper : public ValueObject { public: PrecompileParsedFunctionHelper(Precompiler* precompiler, @@ -291,7 +176,6 @@ Precompiler::Precompiler(Thread* thread) typeargs_to_retain_(), types_to_retain_(), consts_to_retain_(), - field_type_map_(), error_(Error::Handle()), get_runtime_type_is_unique_(false) {} @@ -320,11 +204,8 @@ void Precompiler::DoCompileAll() { // as well as other type checks. HierarchyInfo hierarchy_info(T); - // Precompile static initializers to compute result type information. - PrecompileStaticInitializers(); - ASSERT(Error::Handle(Z, T->sticky_error()).IsNull()); - - // Precompile constructors to compute type information for final fields. + // Precompile constructors to compute information such as + // optimized instruction count (used in inlining heuristics). ClassFinalizer::ClearAllCode(); PrecompileConstructors(); @@ -430,61 +311,6 @@ void Precompiler::DoCompileAll() { } } -static void CompileStaticInitializerIgnoreErrors(const Field& field) { - ASSERT(Error::Handle(Thread::Current()->zone(), - Thread::Current()->sticky_error()) - .IsNull()); - LongJumpScope jump; - if (setjmp(*jump.Set()) == 0) { - const Function& initializer = - Function::Handle(Precompiler::CompileStaticInitializer( - field, /* compute_type = */ true)); - // This function may have become a canonical signature function. Clear - // its code while we have a chance. - initializer.ClearCode(); - initializer.ClearICDataArray(); - } else { - // Ignore compile-time errors here. If the field is actually used, - // the error will be reported later during Iterate(). - Thread::Current()->clear_sticky_error(); - } - ASSERT(Error::Handle(Thread::Current()->zone(), - Thread::Current()->sticky_error()) - .IsNull()); -} - -void Precompiler::PrecompileStaticInitializers() { - class StaticInitializerVisitor : public ClassVisitor { - public: - explicit StaticInitializerVisitor(Zone* zone) - : fields_(Array::Handle(zone)), - field_(Field::Handle(zone)), - function_(Function::Handle(zone)) {} - void Visit(const Class& cls) { - fields_ = cls.fields(); - for (intptr_t j = 0; j < fields_.Length(); j++) { - field_ ^= fields_.At(j); - if (field_.is_static() && field_.is_final() && - field_.has_initializer() && !field_.is_const()) { - if (FLAG_trace_precompiler) { - THR_Print("Precompiling initializer for %s\n", field_.ToCString()); - } - CompileStaticInitializerIgnoreErrors(field_); - } - } - } - - private: - Array& fields_; - Field& field_; - Function& function_; - }; - - HANDLESCOPE(T); - StaticInitializerVisitor visitor(Z); - ProgramVisitor::VisitClasses(&visitor); -} - void Precompiler::PrecompileConstructors() { class ConstructorVisitor : public FunctionVisitor { public: @@ -500,8 +326,7 @@ void Precompiler::PrecompileConstructors() { if (FLAG_trace_precompiler) { THR_Print("Precompiling constructor %s\n", function.ToCString()); } - CompileFunction(precompiler_, Thread::Current(), zone_, function, - precompiler_->field_type_map()); + CompileFunction(precompiler_, Thread::Current(), zone_, function); } private: @@ -512,22 +337,6 @@ void Precompiler::PrecompileConstructors() { HANDLESCOPE(T); ConstructorVisitor visitor(this, zone_); ProgramVisitor::VisitFunctions(&visitor); - - FieldTypeMap::Iterator it(field_type_map_.GetIterator()); - for (FieldTypePair* current = it.Next(); current != NULL; - current = it.Next()) { - const intptr_t cid = current->field_info.cid; - current->field->set_guarded_cid(cid); - current->field->set_is_nullable(cid == kNullCid || cid == kDynamicCid); - // TODO(vegorov) we can actually compute the length in the same way we - // compute cids. - current->field->set_guarded_list_length(Field::kNoFixedLength); - if (FLAG_trace_precompiler) { - THR_Print( - "Field %s <- Type %s\n", current->field->ToCString(), - Class::Handle(T->isolate()->class_table()->At(cid)).ToCString()); - } - } } void Precompiler::AddRoots() { @@ -989,8 +798,8 @@ void Precompiler::AddField(const Field& field) { THR_Print("Precompiling initializer for %s\n", field.ToCString()); } ASSERT(Dart::vm_snapshot_kind() != Snapshot::kFullAOT); - const Function& initializer = Function::Handle( - Z, CompileStaticInitializer(field, /* compute_type = */ true)); + const Function& initializer = + Function::Handle(Z, CompileStaticInitializer(field)); ASSERT(!initializer.IsNull()); field.SetPrecompiledInitializer(initializer); AddCalleesOf(initializer); @@ -999,8 +808,7 @@ void Precompiler::AddField(const Field& field) { } } -RawFunction* Precompiler::CompileStaticInitializer(const Field& field, - bool compute_type) { +RawFunction* Precompiler::CompileStaticInitializer(const Field& field) { ASSERT(field.is_static()); Thread* thread = Thread::Current(); StackZone stack_zone(thread); @@ -1016,7 +824,7 @@ RawFunction* Precompiler::CompileStaticInitializer(const Field& field, parsed_function->AllocateVariables(); } - DartPrecompilationPipeline pipeline(zone); + DartCompilationPipeline pipeline; PrecompileParsedFunctionHelper helper(/* precompiler = */ NULL, parsed_function, /* optimized = */ true); @@ -1027,19 +835,6 @@ RawFunction* Precompiler::CompileStaticInitializer(const Field& field, UNREACHABLE(); } - if (compute_type && field.is_final()) { - intptr_t result_cid = pipeline.result_type().ToCid(); - if (result_cid != kDynamicCid) { -#ifndef PRODUCT - if (FLAG_trace_precompiler && FLAG_support_il_printer) { - THR_Print("Setting guarded_cid of %s to %s\n", field.ToCString(), - pipeline.result_type().ToCString()); - } -#endif // !PRODUCT - field.set_guarded_cid(result_cid); - } - } - if ((FLAG_disassemble || FLAG_disassemble_optimized) && FlowGraphPrinter::ShouldPrint(parsed_function->function())) { Code& code = Code::Handle(parsed_function->function().CurrentCode()); @@ -1064,7 +859,7 @@ RawObject* Precompiler::EvaluateStaticInitializer(const Field& field) { // remembering it because it won't be used again. Function& initializer = Function::Handle(); if (!field.HasPrecompiledInitializer()) { - initializer = CompileStaticInitializer(field, /* compute_type = */ false); + initializer = CompileStaticInitializer(field); } else { initializer ^= field.PrecompiledInitializer(); } @@ -1123,7 +918,7 @@ RawObject* Precompiler::ExecuteOnce(SequenceNode* fragment) { parsed_function->AllocateVariables(); // Non-optimized code generator. - DartPrecompilationPipeline pipeline(Thread::Current()->zone()); + DartCompilationPipeline pipeline; PrecompileParsedFunctionHelper helper(/* precompiler = */ NULL, parsed_function, /* optimized = */ false); @@ -2773,14 +2568,13 @@ static RawError* PrecompileFunctionHelper(Precompiler* precompiler, RawError* Precompiler::CompileFunction(Precompiler* precompiler, Thread* thread, Zone* zone, - const Function& function, - FieldTypeMap* field_type_map) { + const Function& function) { VMTagScope tagScope(thread, VMTag::kCompileUnoptimizedTagId); TIMELINE_FUNCTION_COMPILATION_DURATION(thread, "CompileFunction", function); ASSERT(FLAG_precompiled_mode); const bool optimized = function.IsOptimizable(); // False for natives. - DartPrecompilationPipeline pipeline(zone, field_type_map); + DartCompilationPipeline pipeline; return PrecompileFunctionHelper(precompiler, &pipeline, function, optimized); } diff --git a/runtime/vm/compiler/aot/precompiler.h b/runtime/vm/compiler/aot/precompiler.h index cfa8b7ef6e657..a772f2753eabf 100644 --- a/runtime/vm/compiler/aot/precompiler.h +++ b/runtime/vm/compiler/aot/precompiler.h @@ -229,113 +229,6 @@ class InstanceKeyValueTrait { typedef DirectChainedHashMap InstanceSet; -struct PrecompilerFieldInfo { - intptr_t cid; - - // The most recently compiled constructor which stored the field. - // Used in DartPrecompilationPipeline::FinalizeCompilation to find out if - // this field was not initialized in the constructor being compiled. - const RawFunction* constructor; - - bool operator==(const PrecompilerFieldInfo& other) const { - return (cid == other.cid) && (constructor == other.constructor); - } - - bool operator!=(const PrecompilerFieldInfo& other) const { - return !(*this == other); - } -}; - -struct FieldTypePair { - // Typedefs needed for the DirectChainedHashMap template. - typedef const Field* Key; - typedef PrecompilerFieldInfo Value; - typedef FieldTypePair Pair; - - static Key KeyOf(Pair kv) { return kv.field; } - - static Value ValueOf(Pair kv) { return kv.field_info; } - - static inline intptr_t Hashcode(Key key) { return key->token_pos().value(); } - - static inline bool IsKeyEqual(Pair pair, Key key) { - return pair.field->raw() == key->raw(); - } - - FieldTypePair(const Field* f, intptr_t cid, const RawFunction* constructor) - : field(f), field_info({cid, constructor}) {} - - FieldTypePair() : field(nullptr), field_info({-1, nullptr}) {} - - const Field* field; - PrecompilerFieldInfo field_info; -}; - -typedef DirectChainedHashMap FieldTypeMap; - -struct IntptrPair { - // Typedefs needed for the DirectChainedHashMap template. - typedef intptr_t Key; - typedef intptr_t Value; - typedef IntptrPair Pair; - - static Key KeyOf(Pair kv) { return kv.key_; } - - static Value ValueOf(Pair kv) { return kv.value_; } - - static inline intptr_t Hashcode(Key key) { return key; } - - static inline bool IsKeyEqual(Pair pair, Key key) { return pair.key_ == key; } - - IntptrPair(intptr_t key, intptr_t value) : key_(key), value_(value) {} - - IntptrPair() : key_(kIllegalCid), value_(kIllegalCid) {} - - Key key_; - Value value_; -}; - -typedef DirectChainedHashMap CidMap; - -struct FunctionFeedbackKey { - FunctionFeedbackKey() : owner_cid_(kIllegalCid), token_(0), kind_(0) {} - FunctionFeedbackKey(intptr_t owner_cid, intptr_t token, intptr_t kind) - : owner_cid_(owner_cid), token_(token), kind_(kind) {} - - intptr_t owner_cid_; - intptr_t token_; - intptr_t kind_; -}; - -struct FunctionFeedbackPair { - // Typedefs needed for the DirectChainedHashMap template. - typedef FunctionFeedbackKey Key; - typedef ParsedJSONObject* Value; - typedef FunctionFeedbackPair Pair; - - static Key KeyOf(Pair kv) { return kv.key_; } - - static Value ValueOf(Pair kv) { return kv.value_; } - - static inline intptr_t Hashcode(Key key) { - return key.token_ ^ key.owner_cid_ ^ key.kind_; - } - - static inline bool IsKeyEqual(Pair pair, Key key) { - return (pair.key_.owner_cid_ == key.owner_cid_) && - (pair.key_.token_ == key.token_) && (pair.key_.kind_ == key.kind_); - } - - FunctionFeedbackPair(Key key, Value value) : key_(key), value_(value) {} - - FunctionFeedbackPair() : key_(), value_(NULL) {} - - Key key_; - Value value_; -}; - -typedef DirectChainedHashMap FunctionFeedbackMap; - class Precompiler : public ValueObject { public: static RawError* CompileAll(); @@ -343,22 +236,18 @@ class Precompiler : public ValueObject { static RawError* CompileFunction(Precompiler* precompiler, Thread* thread, Zone* zone, - const Function& function, - FieldTypeMap* field_type_map = NULL); + const Function& function); static RawObject* EvaluateStaticInitializer(const Field& field); static RawObject* ExecuteOnce(SequenceNode* fragment); - static RawFunction* CompileStaticInitializer(const Field& field, - bool compute_type); + static RawFunction* CompileStaticInitializer(const Field& field); // Returns true if get:runtimeType is not overloaded by any class. bool get_runtime_type_is_unique() const { return get_runtime_type_is_unique_; } - FieldTypeMap* field_type_map() { return &field_type_map_; } - private: explicit Precompiler(Thread* thread); @@ -442,9 +331,6 @@ class Precompiler : public ValueObject { TypeArgumentsSet typeargs_to_retain_; AbstractTypeSet types_to_retain_; InstanceSet consts_to_retain_; - FieldTypeMap field_type_map_; - CidMap feedback_cid_map_; - FunctionFeedbackMap function_feedback_map_; Error& error_; bool get_runtime_type_is_unique_; diff --git a/runtime/vm/compiler/backend/type_propagator.cc b/runtime/vm/compiler/backend/type_propagator.cc index 6f63f4d5801bd..ec22c700dd1bd 100644 --- a/runtime/vm/compiler/backend/type_propagator.cc +++ b/runtime/vm/compiler/backend/type_propagator.cc @@ -1256,19 +1256,19 @@ CompileType LoadStaticFieldInstr::ComputeType() const { TraceStrongModeType(this, *abstract_type); } ASSERT(field.is_static()); - if (field.is_final()) { - if (!FLAG_fields_may_be_reset) { - const Instance& obj = Instance::Handle(field.StaticValue()); - if ((obj.raw() != Object::sentinel().raw()) && - (obj.raw() != Object::transition_sentinel().raw()) && !obj.IsNull()) { - is_nullable = CompileType::kNonNullable; - cid = obj.GetClassId(); - } - } else if (field.guarded_cid() != kIllegalCid) { - cid = field.guarded_cid(); - if (!IsNullableCid(cid)) is_nullable = CompileType::kNonNullable; + if (field.is_final() && !FLAG_fields_may_be_reset) { + const Instance& obj = Instance::Handle(field.StaticValue()); + if ((obj.raw() != Object::sentinel().raw()) && + (obj.raw() != Object::transition_sentinel().raw()) && !obj.IsNull()) { + is_nullable = CompileType::kNonNullable; + cid = obj.GetClassId(); } } + if ((field.guarded_cid() != kIllegalCid) && + (field.guarded_cid() != kDynamicCid)) { + cid = field.guarded_cid(); + is_nullable = field.is_nullable(); + } return CompileType(is_nullable, cid, abstract_type); } @@ -1304,27 +1304,24 @@ CompileType LoadFieldInstr::ComputeType() const { } const Isolate* isolate = Isolate::Current(); - CompileType compile_type_annotation = CompileType::None(); + bool is_nullable = CompileType::kNullable; + intptr_t cid = kDynamicCid; + const AbstractType* abstract_type = NULL; if (isolate->can_use_strong_mode_types() || (isolate->type_checks() && (type().IsFunctionType() || type().HasResolvedTypeClass()))) { - const AbstractType* abstract_type = &type(); + cid = kIllegalCid; + abstract_type = &type(); TraceStrongModeType(this, *abstract_type); - compile_type_annotation = CompileType::FromAbstractType(*abstract_type); } - - CompileType compile_type_cid = CompileType::None(); - if ((field_ != NULL) && (field_->guarded_cid() != kIllegalCid)) { - bool is_nullable = field_->is_nullable(); - intptr_t field_cid = field_->guarded_cid(); - - compile_type_cid = CompileType(is_nullable, field_cid, NULL); + if ((field_ != NULL) && (field_->guarded_cid() != kIllegalCid) && + (field_->guarded_cid() != kDynamicCid)) { + cid = field_->guarded_cid(); + is_nullable = field_->is_nullable(); } else { - compile_type_cid = CompileType::FromCid(result_cid_); + cid = result_cid_; } - - return *CompileType::ComputeRefinedType(&compile_type_cid, - &compile_type_annotation); + return CompileType(is_nullable, cid, abstract_type); } CompileType LoadCodeUnitsInstr::ComputeType() const { diff --git a/runtime/vm/kernel_loader.cc b/runtime/vm/kernel_loader.cc index a4ae7e671ef00..a1f1ce6a267a6 100644 --- a/runtime/vm/kernel_loader.cc +++ b/runtime/vm/kernel_loader.cc @@ -179,6 +179,7 @@ KernelLoader::KernelLoader(Program* program) program_->kernel_data_size(), 0), type_translator_(&helper_, &active_class_, /* finalize= */ false), + inferred_type_metadata_helper_(&helper_), external_name_class_(Class::Handle(Z)), external_name_field_(Field::Handle(Z)), potential_natives_(GrowableObjectArray::Handle(Z)), @@ -343,6 +344,7 @@ KernelLoader::KernelLoader(const Script& script, translation_helper_(this, thread_), helper_(zone_, &translation_helper_, script, kernel_data, 0), type_translator_(&helper_, &active_class_, /* finalize= */ false), + inferred_type_metadata_helper_(&helper_), external_name_class_(Class::Handle(Z)), external_name_field_(Field::Handle(Z)), potential_natives_(GrowableObjectArray::Handle(Z)), @@ -760,6 +762,18 @@ void KernelLoader::walk_incremental_kernel(BitVector* modified_libs, } } +void KernelLoader::ReadInferredType(const Field& field, + intptr_t kernel_offset) { + const InferredTypeMetadata type = + inferred_type_metadata_helper_.GetInferredType(kernel_offset); + if (type.IsTrivial()) { + return; + } + field.set_guarded_cid(type.cid); + field.set_is_nullable(type.IsNullable()); + field.set_guarded_list_length(Field::kNoFixedLength); +} + void KernelLoader::CheckForInitializer(const Field& field) { if (helper_.PeekTag() == kSomething) { SimpleExpressionConverter converter(&H, &helper_); @@ -932,6 +946,7 @@ RawLibrary* KernelLoader::LoadLibrary(intptr_t index) { field.set_kernel_offset(field_offset); const AbstractType& type = T.BuildType(); // read type. field.SetFieldType(type); + ReadInferredType(field, field_offset + library_kernel_offset_); CheckForInitializer(field); field_helper.SetJustRead(FieldHelper::kType); field_helper.ReadUntilExcluding(FieldHelper::kInitializer); @@ -1326,6 +1341,7 @@ void KernelLoader::FinishClassLoading(const Class& klass, field_helper.IsConst(), is_reflectable, script_class, type, field_helper.position_, field_helper.end_position_)); field.set_kernel_offset(field_offset); + ReadInferredType(field, field_offset + library_kernel_offset_); CheckForInitializer(field); field_helper.ReadUntilExcluding(FieldHelper::kInitializer); intptr_t field_initializer_offset = helper_.ReaderOffset(); diff --git a/runtime/vm/kernel_loader.h b/runtime/vm/kernel_loader.h index f17681b642e3c..a65f11a1596e5 100644 --- a/runtime/vm/kernel_loader.h +++ b/runtime/vm/kernel_loader.h @@ -221,6 +221,7 @@ class KernelLoader : public ValueObject { void LoadPreliminaryClass(ClassHelper* class_helper, intptr_t type_parameter_count); + void ReadInferredType(const Field& field, intptr_t kernel_offset); void CheckForInitializer(const Field& field); void FixCoreLibraryScriptUri(const Library& library, const Script& script); @@ -340,6 +341,7 @@ class KernelLoader : public ValueObject { BuildingTranslationHelper translation_helper_; KernelReaderHelper helper_; TypeTranslator type_translator_; + InferredTypeMetadataHelper inferred_type_metadata_helper_; Class& external_name_class_; Field& external_name_field_; diff --git a/tests/language_2/vm/regress_flutter_22131_test.dart b/tests/language_2/vm/regress_flutter_22131_test.dart new file mode 100644 index 0000000000000..e9e2bb748e6dc --- /dev/null +++ b/tests/language_2/vm/regress_flutter_22131_test.dart @@ -0,0 +1,30 @@ +// Copyright (c) 2018, 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. + +// Verifies that inferred type of a final field takes constant objects into +// account. This is a regression test for +// https://github.com/flutter/flutter/issues/22131. + +// VMOptions=--no_background_compilation --optimization_counter_threshold=10 + +import "package:expect/expect.dart"; + +class X { + final Map data; + const X(this.data); +} + +const f = X({1: "ok-f"}); +final g = X({1: "ok-g"}); + +void doTest() { + Expect.equals("ok-f", f.data[1]); + Expect.equals("ok-g", g.data[1]); +} + +void main() { + for (int i = 0; i < 20; i++) { + doTest(); + } +}