blob: f6afdad948280d4128e2803945a544965e852347 [file] [log] [blame]
// Copyright (c) 2020, 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.
#include <functional>
#include "platform/assert.h"
#include "vm/class_finalizer.h"
#include "vm/compiler/backend/il_test_helper.h"
#include "vm/symbols.h"
#include "vm/type_testing_stubs.h"
#include "vm/type_testing_stubs_test.h"
#include "vm/unit_test.h"
#if defined(TARGET_ARCH_ARM64) || defined(TARGET_ARCH_ARM) || \
defined(TARGET_ARCH_X64)
namespace dart {
static void FinalizeAndCanonicalize(const Class& klass, AbstractType* type) {
*type = ClassFinalizer::FinalizeType(klass, *type);
ASSERT(type->IsCanonical());
}
static void CanonicalizeTAV(TypeArguments* tav) {
*tav = tav->Canonicalize();
}
static void RunTTSTest(
const Object& instance,
const AbstractType& dst_type,
const TypeArguments& instantiator_tav,
const TypeArguments& function_tav,
std::function<void(const Object& result, const SubtypeTestCache& stc)> lazy,
std::function<void(const Object& result,
const SubtypeTestCache& stc,
const Bool& abi_regs_modified,
const Bool& rest_regs_modified)> nonlazy) {
ASSERT(instantiator_tav.IsNull() || instantiator_tav.IsCanonical());
ASSERT(function_tav.IsNull() || function_tav.IsCanonical());
auto thread = Thread::Current();
// Build a stub which will do calling conversion to call TTS stubs.
const auto& klass =
Class::Handle(thread->isolate()->class_table()->At(kInstanceCid));
const auto& symbol = String::Handle(
Symbols::New(thread, OS::SCreate(thread->zone(), "TTSTest")));
const auto& function = Function::Handle(
Function::New(symbol, FunctionLayout::kRegularFunction, false, false,
false, false, false, klass, TokenPosition::kNoSource));
compiler::ObjectPoolBuilder pool_builder;
const auto& invoke_tts = Code::Handle(
StubCode::Generate("InvokeTTS", &pool_builder, &GenerateInvokeTTSStub));
const auto& pool =
ObjectPool::Handle(ObjectPool::NewFromBuilder(pool_builder));
invoke_tts.set_object_pool(pool.raw());
invoke_tts.set_owner(function);
invoke_tts.set_exception_handlers(
ExceptionHandlers::Handle(ExceptionHandlers::New(0)));
EXPECT(pool.Length() == 2);
const intptr_t kSubtypeTestCacheIndex = 0;
const auto& arguments_descriptor =
Array::Handle(ArgumentsDescriptor::NewBoxed(0, 6));
const auto& arguments = Array::Handle(Array::New(6));
const auto& abi_regs_modified_box = Array::Handle(Array::New(1));
const auto& rest_regs_modified_box = Array::Handle(Array::New(1));
arguments.SetAt(0, abi_regs_modified_box);
arguments.SetAt(1, rest_regs_modified_box);
arguments.SetAt(2, instance);
arguments.SetAt(3, instantiator_tav);
arguments.SetAt(4, function_tav);
arguments.SetAt(5, dst_type);
// Ensure we have a) uninitialized TTS b) no/empty SubtypeTestCache.
dst_type.SetTypeTestingStub(StubCode::LazySpecializeTypeTest());
EXPECT(dst_type.type_test_stub() == StubCode::LazySpecializeTypeTest().raw());
EXPECT(pool.ObjectAt(kSubtypeTestCacheIndex) == Object::null());
auto& result = Object::Handle();
auto& result2 = Object::Handle();
auto& abi_regs_modified = Bool::Handle();
auto& rest_regs_modified = Bool::Handle();
auto& tts = Code::Handle();
auto& tts2 = Code::Handle();
auto& stc = SubtypeTestCache::Handle();
auto& stc2 = SubtypeTestCache::Handle();
// First invocation will a) specialize the TTS b) may create SubtypeTestCache
result = DartEntry::InvokeCode(invoke_tts, arguments_descriptor, arguments,
thread);
stc ^= pool.ObjectAt(kSubtypeTestCacheIndex);
tts = dst_type.type_test_stub();
if (!result.IsError()) {
EXPECT(tts.raw() != StubCode::LazySpecializeTypeTest().raw());
}
lazy(result, stc);
// Second invocation will a) keep TTS b) keep optional SubtypeTestCache
result2 = DartEntry::InvokeCode(invoke_tts, arguments_descriptor, arguments,
thread);
stc2 ^= pool.ObjectAt(kSubtypeTestCacheIndex);
tts2 = dst_type.type_test_stub();
abi_regs_modified ^= abi_regs_modified_box.At(0);
rest_regs_modified ^= rest_regs_modified_box.At(0);
EXPECT(result2.IsError() || !abi_regs_modified.IsNull());
EXPECT(tts2.raw() == tts.raw());
EXPECT(stc2.raw() == stc.raw());
nonlazy(result2, stc2, abi_regs_modified, rest_regs_modified);
// Third invocation will a) explicitly install TTS beforehand b) keep optional
// SubtypeTestCache
// (This is to simulate AOT where we don't use lazy specialization but
// precompile the TTS)
auto& dst_type_to_specialize = AbstractType::Handle(dst_type.raw());
if (dst_type_to_specialize.IsTypeParameter()) {
dst_type_to_specialize = TypeParameter::Cast(dst_type).GetFromTypeArguments(
instantiator_tav, function_tav);
}
TypeTestingStubGenerator::SpecializeStubFor(thread, dst_type_to_specialize);
tts = dst_type.type_test_stub();
result2 = DartEntry::InvokeCode(invoke_tts, arguments_descriptor, arguments,
thread);
stc2 ^= pool.ObjectAt(kSubtypeTestCacheIndex);
tts2 = dst_type.type_test_stub();
abi_regs_modified ^= abi_regs_modified_box.At(0);
rest_regs_modified ^= rest_regs_modified_box.At(0);
EXPECT(result2.IsError() || !abi_regs_modified.IsNull());
EXPECT(tts2.raw() == tts.raw());
EXPECT(stc2.raw() == stc.raw());
nonlazy(result2, stc2, abi_regs_modified, rest_regs_modified);
}
static void ExpectLazilyHandledViaTTS(const Object& result,
const SubtypeTestCache& stc) {
// Ensure the type test succeeded.
EXPECT(result.IsNull());
// Ensure we didn't fall back to the subtype test cache.
EXPECT(stc.IsNull());
}
static void ExpectHandledViaTTS(const Object& result,
const SubtypeTestCache& stc,
const Bool& abi_regs_modified,
const Bool& rest_regs_modified) {
ExpectLazilyHandledViaTTS(result, stc);
// Ensure the TTS abi registers were preserved.
EXPECT(!abi_regs_modified.value());
// Ensure the non-TTS abi registers were preserved.
EXPECT(!rest_regs_modified.value());
}
static void ExpectLazilyHandledViaSTC(const Object& result,
const SubtypeTestCache& stc) {
// Ensure the type test succeeded.
EXPECT(result.IsNull());
// Ensure we did fall back to the subtype test cache.
EXPECT(!stc.IsNull());
// Ensure the test is marked as succeeding in the STC.
EXPECT(stc.NumberOfChecks() == 1);
SubtypeTestCacheTable entries(Array::Handle(stc.cache()));
EXPECT(entries[0].Get<SubtypeTestCache::kTestResult>() ==
Object::bool_true().raw());
}
static void ExpectHandledViaSTC(const Object& result,
const SubtypeTestCache& stc,
const Bool& abi_regs_modified,
const Bool& rest_regs_modified) {
ExpectLazilyHandledViaSTC(result, stc);
// Ensure the TTS/STC abi registers were preserved.
EXPECT(!abi_regs_modified.value());
}
static void ExpectLazilyFailedViaTTS(const Object& result,
const SubtypeTestCache& stc) {
// Ensure we have not updated STC (which we shouldn't do in case the type test
// fails, i.e. an exception is thrown).
EXPECT(stc.IsNull());
// Ensure we get a proper exception for the type test.
EXPECT(result.IsUnhandledException());
const auto& error =
Instance::Handle(UnhandledException::Cast(result).exception());
EXPECT(strstr(error.ToCString(), "_TypeError"));
}
static void ExpectFailedViaTTS(const Object& result,
const SubtypeTestCache& stc,
const Bool& abi_regs_modified,
const Bool& rest_regs_modified) {
ExpectLazilyFailedViaTTS(result, stc);
}
static void ExpectLazilyFailedViaSTC(const Object& result,
const SubtypeTestCache& stc) {
// Ensure we have not updated STC (which we shouldn't do in case the type test
// fails, i.e. an exception is thrown).
EXPECT(stc.IsNull());
// Ensure we get a proper exception for the type test.
EXPECT(result.IsUnhandledException());
const auto& error =
Instance::Handle(UnhandledException::Cast(result).exception());
EXPECT(strstr(error.ToCString(), "_TypeError"));
}
static void ExpectFailedViaSTC(const Object& result,
const SubtypeTestCache& stc,
const Bool& abi_regs_modified,
const Bool& rest_regs_modified) {
ExpectLazilyFailedViaSTC(result, stc);
}
const char* kSubtypeRangeCheckScript =
R"(
class I<T, U> {}
class I2 {}
class Base<T> {}
class A extends Base<int> {}
class A1 extends A implements I2 {}
class A2<T> extends A implements I<int, T> {}
class B extends Base<String> {}
class B1 extends B implements I2 {}
class B2<T> extends B implements I<T, String> {}
genericFun<A, B>() {}
createI() => I<int, String>();
createI2() => I2();
createBaseInt() => Base<int>();
createA() => A();
createA1() => A1();
createA2() => A2<int>();
createB() => B();
createB1() => B1();
createB2() => B2<int>();
createBaseIStringDouble() => Base<I<String, double>>();
createBaseA2Int() => Base<A2<int>>();
createBaseA2A1() => Base<A2<A1>>();
createBaseB2Int() => Base<B2<int>>();
)";
ISOLATE_UNIT_TEST_CASE(TTS_SubtypeRangeCheck) {
const auto& root_library =
Library::Handle(LoadTestScript(kSubtypeRangeCheckScript));
const auto& class_a = Class::Handle(GetClass(root_library, "A"));
const auto& class_base = Class::Handle(GetClass(root_library, "Base"));
const auto& class_i = Class::Handle(GetClass(root_library, "I"));
const auto& class_i2 = Class::Handle(GetClass(root_library, "I2"));
const auto& class_null = Class::Handle(Class::null());
const auto& obj_i = Object::Handle(Invoke(root_library, "createI"));
const auto& obj_i2 = Object::Handle(Invoke(root_library, "createI2"));
const auto& obj_baseint =
Object::Handle(Invoke(root_library, "createBaseInt"));
const auto& obj_a = Object::Handle(Invoke(root_library, "createA"));
const auto& obj_a1 = Object::Handle(Invoke(root_library, "createA1"));
const auto& obj_a2 = Object::Handle(Invoke(root_library, "createA2"));
const auto& obj_b = Object::Handle(Invoke(root_library, "createB"));
const auto& obj_b1 = Object::Handle(Invoke(root_library, "createB1"));
const auto& obj_b2 = Object::Handle(Invoke(root_library, "createB2"));
const auto& type_dynamic = Type::Handle(Type::DynamicType());
auto& type_object = Type::Handle(Type::ObjectType());
type_object = type_object.ToNullability(Nullability::kNullable, Heap::kNew);
const auto& tav_null = TypeArguments::Handle(TypeArguments::null());
auto& tav_object = TypeArguments::Handle(TypeArguments::New(1));
tav_object.SetTypeAt(0, type_object);
CanonicalizeTAV(&tav_object);
auto& tav_object_dynamic = TypeArguments::Handle(TypeArguments::New(2));
tav_object_dynamic.SetTypeAt(0, type_object);
tav_object_dynamic.SetTypeAt(1, type_dynamic);
CanonicalizeTAV(&tav_object_dynamic);
auto& tav_dynamic_t = TypeArguments::Handle(TypeArguments::New(2));
tav_dynamic_t.SetTypeAt(0, type_dynamic);
tav_dynamic_t.SetTypeAt(
1, TypeParameter::Handle(GetClassTypeParameter(class_base, "T")));
CanonicalizeTAV(&tav_dynamic_t);
// We will generate specialized TTS for instantiated interface types
// where there are no type arguments or the type arguments are top
// types.
//
// obj as A // Subclass ranges
// obj as Base<Object> // Subclass ranges with top-type tav
// obj as I2 // Subtype ranges
// obj as I<Object, dynamic> // Subtype ranges with top-type tav
//
// <...> as A
const auto& type_a = AbstractType::Handle(class_a.RareType());
RunTTSTest(obj_i, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS,
ExpectFailedViaTTS);
RunTTSTest(obj_i2, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS,
ExpectFailedViaTTS);
RunTTSTest(obj_baseint, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS,
ExpectFailedViaTTS);
RunTTSTest(obj_a, type_a, tav_null, tav_null, ExpectLazilyHandledViaTTS,
ExpectHandledViaTTS);
RunTTSTest(obj_a1, type_a, tav_null, tav_null, ExpectLazilyHandledViaTTS,
ExpectHandledViaTTS);
RunTTSTest(obj_a2, type_a, tav_null, tav_null, ExpectLazilyHandledViaTTS,
ExpectHandledViaTTS);
RunTTSTest(obj_b, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS,
ExpectFailedViaTTS);
RunTTSTest(obj_b1, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS,
ExpectFailedViaTTS);
RunTTSTest(obj_b2, type_a, tav_null, tav_null, ExpectLazilyFailedViaTTS,
ExpectFailedViaTTS);
// <...> as Base<Object>
auto& type_base = AbstractType::Handle(
Type::New(class_base, tav_object, TokenPosition::kNoSource));
FinalizeAndCanonicalize(class_null, &type_base);
RunTTSTest(obj_i, type_base, tav_null, tav_null, ExpectLazilyFailedViaTTS,
ExpectFailedViaTTS);
RunTTSTest(obj_i2, type_base, tav_null, tav_null, ExpectLazilyFailedViaTTS,
ExpectFailedViaTTS);
RunTTSTest(obj_baseint, type_base, tav_null, tav_null,
ExpectLazilyHandledViaTTS, ExpectHandledViaTTS);
RunTTSTest(obj_a, type_base, tav_null, tav_null, ExpectLazilyHandledViaTTS,
ExpectHandledViaTTS);
RunTTSTest(obj_a1, type_base, tav_null, tav_null, ExpectLazilyHandledViaTTS,
ExpectHandledViaTTS);
RunTTSTest(obj_a2, type_base, tav_null, tav_null, ExpectLazilyHandledViaTTS,
ExpectHandledViaTTS);
RunTTSTest(obj_b, type_base, tav_null, tav_null, ExpectLazilyHandledViaTTS,
ExpectHandledViaTTS);
RunTTSTest(obj_b1, type_base, tav_null, tav_null, ExpectLazilyHandledViaTTS,
ExpectHandledViaTTS);
RunTTSTest(obj_b2, type_base, tav_null, tav_null, ExpectLazilyHandledViaTTS,
ExpectHandledViaTTS);
// <...> as I2
const auto& type_i2 = AbstractType::Handle(class_i2.RareType());
RunTTSTest(obj_i, type_i2, tav_null, tav_null, ExpectLazilyFailedViaTTS,
ExpectFailedViaTTS);
RunTTSTest(obj_i2, type_i2, tav_null, tav_null, ExpectLazilyHandledViaTTS,
ExpectHandledViaTTS);
RunTTSTest(obj_baseint, type_i2, tav_null, tav_null, ExpectLazilyFailedViaTTS,
ExpectFailedViaTTS);
RunTTSTest(obj_a, type_i2, tav_null, tav_null, ExpectLazilyFailedViaTTS,
ExpectFailedViaTTS);
RunTTSTest(obj_a1, type_i2, tav_null, tav_null, ExpectLazilyHandledViaTTS,
ExpectHandledViaTTS);
RunTTSTest(obj_a2, type_i2, tav_null, tav_null, ExpectLazilyFailedViaTTS,
ExpectFailedViaTTS);
RunTTSTest(obj_b, type_i2, tav_null, tav_null, ExpectLazilyFailedViaTTS,
ExpectFailedViaTTS);
RunTTSTest(obj_b1, type_i2, tav_null, tav_null, ExpectLazilyHandledViaTTS,
ExpectHandledViaTTS);
RunTTSTest(obj_b2, type_i2, tav_null, tav_null, ExpectLazilyFailedViaTTS,
ExpectFailedViaTTS);
// <...> as I<Object, dynamic>
auto& type_i_object_dynamic = AbstractType::Handle(
Type::New(class_i, tav_object_dynamic, TokenPosition::kNoSource));
FinalizeAndCanonicalize(class_null, &type_i_object_dynamic);
RunTTSTest(obj_i, type_i_object_dynamic, tav_null, tav_null,
ExpectLazilyHandledViaTTS, ExpectHandledViaTTS);
RunTTSTest(obj_i2, type_i_object_dynamic, tav_null, tav_null,
ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
RunTTSTest(obj_baseint, type_i_object_dynamic, tav_null, tav_null,
ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
RunTTSTest(obj_a, type_i_object_dynamic, tav_null, tav_null,
ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
RunTTSTest(obj_a1, type_i_object_dynamic, tav_null, tav_null,
ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
RunTTSTest(obj_a2, type_i_object_dynamic, tav_null, tav_null,
ExpectLazilyHandledViaTTS, ExpectHandledViaTTS);
RunTTSTest(obj_b, type_i_object_dynamic, tav_null, tav_null,
ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
RunTTSTest(obj_b1, type_i_object_dynamic, tav_null, tav_null,
ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
RunTTSTest(obj_b2, type_i_object_dynamic, tav_null, tav_null,
ExpectLazilyHandledViaTTS, ExpectHandledViaTTS);
// We do not generate TTS for uninstantiated types if we would need to use
// subtype range checks for the class of the interface type.
//
// obj as I<dynamic, T>
//
auto& type_dynamic_t = AbstractType::Handle(
Type::New(class_i, tav_dynamic_t, TokenPosition::kNoSource));
FinalizeAndCanonicalize(class_base, &type_dynamic_t);
RunTTSTest(obj_i, type_dynamic_t, tav_object, tav_null,
ExpectLazilyHandledViaSTC, ExpectHandledViaSTC);
RunTTSTest(obj_i2, type_dynamic_t, tav_object, tav_null,
ExpectLazilyFailedViaSTC, ExpectFailedViaSTC);
RunTTSTest(obj_baseint, type_dynamic_t, tav_object, tav_null,
ExpectLazilyFailedViaSTC, ExpectFailedViaSTC);
RunTTSTest(obj_a, type_dynamic_t, tav_object, tav_null,
ExpectLazilyFailedViaSTC, ExpectFailedViaSTC);
RunTTSTest(obj_a1, type_dynamic_t, tav_object, tav_null,
ExpectLazilyFailedViaSTC, ExpectFailedViaSTC);
RunTTSTest(obj_a2, type_dynamic_t, tav_object, tav_null,
ExpectLazilyHandledViaSTC, ExpectHandledViaSTC);
RunTTSTest(obj_b, type_dynamic_t, tav_object, tav_null,
ExpectLazilyFailedViaSTC, ExpectFailedViaSTC);
RunTTSTest(obj_b1, type_dynamic_t, tav_object, tav_null,
ExpectLazilyFailedViaSTC, ExpectFailedViaSTC);
RunTTSTest(obj_b2, type_dynamic_t, tav_object, tav_null,
ExpectLazilyHandledViaSTC, ExpectHandledViaSTC);
// obj as Object (with null safety)
Isolate* isolate = Isolate::Current();
if (isolate->null_safety()) {
auto& type_non_nullable_object =
Type::Handle(isolate->object_store()->non_nullable_object_type());
RunTTSTest(obj_a, type_non_nullable_object, tav_null, tav_null,
ExpectLazilyHandledViaTTS, ExpectHandledViaTTS);
RunTTSTest(Object::null_object(), type_non_nullable_object, tav_null,
tav_null, ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
}
}
ISOLATE_UNIT_TEST_CASE(TTS_GenericSubtypeRangeCheck) {
const auto& root_library =
Library::Handle(LoadTestScript(kSubtypeRangeCheckScript));
const auto& class_a1 = Class::Handle(GetClass(root_library, "A1"));
const auto& class_a2 = Class::Handle(GetClass(root_library, "A2"));
const auto& class_base = Class::Handle(GetClass(root_library, "Base"));
const auto& class_i = Class::Handle(GetClass(root_library, "I"));
const auto& fun_generic =
Function::Handle(GetFunction(root_library, "genericFun"));
const auto& class_null = Class::Handle(Class::null());
const auto& obj_i = Object::Handle(Invoke(root_library, "createI"));
const auto& obj_i2 = Object::Handle(Invoke(root_library, "createI2"));
const auto& obj_baseint =
Object::Handle(Invoke(root_library, "createBaseInt"));
const auto& obj_a = Object::Handle(Invoke(root_library, "createA"));
const auto& obj_a1 = Object::Handle(Invoke(root_library, "createA1"));
const auto& obj_a2 = Object::Handle(Invoke(root_library, "createA2"));
const auto& obj_b = Object::Handle(Invoke(root_library, "createB"));
const auto& obj_b1 = Object::Handle(Invoke(root_library, "createB1"));
const auto& obj_b2 = Object::Handle(Invoke(root_library, "createB2"));
const auto& obj_basea2int =
Object::Handle(Invoke(root_library, "createBaseA2Int"));
const auto& obj_basea2a1 =
Object::Handle(Invoke(root_library, "createBaseA2A1"));
const auto& obj_baseb2int =
Object::Handle(Invoke(root_library, "createBaseB2Int"));
const auto& obj_baseistringdouble =
Object::Handle(Invoke(root_library, "createBaseIStringDouble"));
const auto& type_dynamic = Type::Handle(Type::DynamicType());
auto& type_int = Type::Handle(Type::IntType());
if (!TestCase::IsNNBD()) {
type_int = type_int.ToNullability(Nullability::kLegacy, Heap::kNew);
}
auto& type_string = Type::Handle(Type::StringType());
if (!TestCase::IsNNBD()) {
type_string = type_string.ToNullability(Nullability::kLegacy, Heap::kNew);
}
auto& type_object = Type::Handle(Type::ObjectType());
type_object = type_object.ToNullability(
TestCase::IsNNBD() ? Nullability::kNullable : Nullability::kLegacy,
Heap::kNew);
auto& type_a1 = Type::Handle(class_a1.DeclarationType());
if (!TestCase::IsNNBD()) {
type_a1 = type_a1.ToNullability(Nullability::kLegacy, Heap::kNew);
}
FinalizeAndCanonicalize(class_null, &type_a1);
const auto& tav_null = TypeArguments::Handle(TypeArguments::null());
auto& tav_object_dynamic = TypeArguments::Handle(TypeArguments::New(2));
tav_object_dynamic.SetTypeAt(0, type_object);
tav_object_dynamic.SetTypeAt(1, type_dynamic);
CanonicalizeTAV(&tav_object_dynamic);
auto& tav_dynamic_int = TypeArguments::Handle(TypeArguments::New(2));
tav_dynamic_int.SetTypeAt(0, type_dynamic);
tav_dynamic_int.SetTypeAt(1, type_int);
CanonicalizeTAV(&tav_dynamic_int);
auto& tav_dynamic_string = TypeArguments::Handle(TypeArguments::New(2));
tav_dynamic_string.SetTypeAt(0, type_dynamic);
tav_dynamic_string.SetTypeAt(1, type_string);
CanonicalizeTAV(&tav_dynamic_string);
auto& tav_int = TypeArguments::Handle(TypeArguments::New(1));
tav_int.SetTypeAt(0, type_int);
CanonicalizeTAV(&tav_int);
auto& type_i_object_dynamic = AbstractType::Handle(
Type::New(class_i, tav_object_dynamic, TokenPosition::kNoSource));
FinalizeAndCanonicalize(class_null, &type_i_object_dynamic);
const auto& tav_iod = TypeArguments::Handle(TypeArguments::New(1));
tav_iod.SetTypeAt(0, type_i_object_dynamic);
// We will generate specialized TTS for instantiated interface types
// where there are no type arguments or the type arguments are top
// types.
//
// obj as Base<I<Object, dynamic>> // Subclass ranges for Base, subtype
// // ranges tav arguments.
// obj as Base<T> // Subclass ranges for Base, type
// // equality for instantiator type arg T
// obj as Base<B> // Subclass ranges for Base, type
// // equality for function type arg B.
//
// <...> as Base<I<Object, dynamic>>
auto& type_base_i_object_dynamic = AbstractType::Handle(
Type::New(class_base, tav_iod, TokenPosition::kNoSource));
FinalizeAndCanonicalize(class_null, &type_base_i_object_dynamic);
RunTTSTest(obj_baseb2int, type_base_i_object_dynamic, tav_null, tav_null,
ExpectLazilyHandledViaTTS, ExpectHandledViaTTS);
RunTTSTest(obj_baseistringdouble, type_base_i_object_dynamic, tav_null,
tav_null, ExpectLazilyHandledViaTTS, ExpectHandledViaTTS);
RunTTSTest(obj_a, type_base_i_object_dynamic, tav_null, tav_null,
ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
RunTTSTest(obj_a1, type_base_i_object_dynamic, tav_null, tav_null,
ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
RunTTSTest(obj_a2, type_base_i_object_dynamic, tav_null, tav_null,
ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
RunTTSTest(obj_b, type_base_i_object_dynamic, tav_null, tav_null,
ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
RunTTSTest(obj_b1, type_base_i_object_dynamic, tav_null, tav_null,
ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
RunTTSTest(obj_b2, type_base_i_object_dynamic, tav_null, tav_null,
ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
// <...> as Base<T> with T instantiantiator type parameter (T == int)
const auto& tav_baset = TypeArguments::Handle(TypeArguments::New(1));
tav_baset.SetTypeAt(
0, TypeParameter::Handle(GetClassTypeParameter(class_base, "T")));
auto& type_base_t = AbstractType::Handle(
Type::New(class_base, tav_baset, TokenPosition::kNoSource));
FinalizeAndCanonicalize(class_base, &type_base_t);
RunTTSTest(obj_baseint, type_base_t, tav_int, tav_null,
ExpectLazilyHandledViaTTS, ExpectHandledViaTTS);
RunTTSTest(obj_baseistringdouble, type_base_t, tav_int, tav_null,
ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
// <...> as Base<B> with B function type parameter
const auto& tav_baseb = TypeArguments::Handle(TypeArguments::New(1));
tav_baseb.SetTypeAt(
0, TypeParameter::Handle(GetFunctionTypeParameter(fun_generic, "B")));
auto& type_base_b = AbstractType::Handle(
Type::New(class_base, tav_baseb, TokenPosition::kNoSource));
FinalizeAndCanonicalize(class_null, &type_base_b);
// With B == int
RunTTSTest(obj_baseint, type_base_b, tav_null, tav_dynamic_int,
ExpectLazilyHandledViaTTS, ExpectHandledViaTTS);
RunTTSTest(obj_baseistringdouble, type_base_b, tav_null, tav_dynamic_int,
ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
// With B == dynamic (null vector)
RunTTSTest(obj_baseint, type_base_b, tav_null, tav_null,
ExpectLazilyHandledViaTTS, ExpectHandledViaTTS);
RunTTSTest(obj_i2, type_base_b, tav_null, tav_null, ExpectLazilyFailedViaTTS,
ExpectFailedViaTTS);
// We do not generate TTS for uninstantiated types if we would need to use
// subtype range checks for the class of the interface type.
//
// obj as I<dynamic, String> // I is generic & implemented.
// obj as Base<A2<T>> // A2<T> is not instantiated.
// obj as Base<A2<A1>> // A2<A1> is not a rare type.
//
// <...> as I<dynamic, String>
RELEASE_ASSERT(class_i.is_implemented());
auto& type_i_dynamic_string = Type::Handle(
Type::New(class_i, tav_dynamic_string, TokenPosition::kNoSource));
type_i_dynamic_string = type_i_dynamic_string.ToNullability(
Nullability::kNonNullable, Heap::kNew);
FinalizeAndCanonicalize(class_null, &type_i_dynamic_string);
RunTTSTest(obj_i, type_i_dynamic_string, tav_null, tav_null,
ExpectLazilyHandledViaSTC, ExpectHandledViaSTC);
RunTTSTest(obj_baseint, type_i_dynamic_string, tav_null, tav_null,
ExpectLazilyFailedViaSTC, ExpectFailedViaSTC);
// <...> as Base<A2<T>>
const auto& tav_t = TypeArguments::Handle(TypeArguments::New(1));
tav_t.SetTypeAt(
0, TypeParameter::Handle(GetClassTypeParameter(class_base, "T")));
auto& type_a2_t =
Type::Handle(Type::New(class_a2, tav_t, TokenPosition::kNoSource));
type_a2_t = type_a2_t.ToNullability(Nullability::kLegacy, Heap::kNew);
FinalizeAndCanonicalize(class_null, &type_a2_t);
const auto& tav_a2_t = TypeArguments::Handle(TypeArguments::New(1));
tav_a2_t.SetTypeAt(0, type_a2_t);
auto& type_base_a2_t =
Type::Handle(Type::New(class_base, tav_a2_t, TokenPosition::kNoSource));
type_base_a2_t =
type_base_a2_t.ToNullability(Nullability::kNonNullable, Heap::kNew);
FinalizeAndCanonicalize(class_null, &type_base_a2_t);
RunTTSTest(obj_basea2int, type_base_a2_t, tav_null, tav_null,
ExpectLazilyHandledViaSTC, ExpectHandledViaSTC);
RunTTSTest(obj_baseint, type_base_a2_t, tav_null, tav_null,
ExpectLazilyFailedViaSTC, ExpectFailedViaSTC);
// <...> as Base<A2<A1>>
const auto& tav_a1 = TypeArguments::Handle(TypeArguments::New(1));
tav_a1.SetTypeAt(0, type_a1);
auto& type_a2_a1 =
Type::Handle(Type::New(class_a2, tav_a1, TokenPosition::kNoSource));
type_a2_a1 = type_a2_a1.ToNullability(Nullability::kLegacy, Heap::kNew);
FinalizeAndCanonicalize(class_null, &type_a2_a1);
const auto& tav_a2_a1 = TypeArguments::Handle(TypeArguments::New(1));
tav_a2_a1.SetTypeAt(0, type_a2_a1);
auto& type_base_a2_a1 =
Type::Handle(Type::New(class_base, tav_a2_a1, TokenPosition::kNoSource));
type_base_a2_a1 =
type_base_a2_a1.ToNullability(Nullability::kNonNullable, Heap::kNew);
FinalizeAndCanonicalize(class_null, &type_base_a2_a1);
RunTTSTest(obj_basea2a1, type_base_a2_a1, tav_null, tav_null,
ExpectLazilyHandledViaSTC, ExpectHandledViaSTC);
RunTTSTest(obj_basea2int, type_base_a2_a1, tav_null, tav_null,
ExpectLazilyFailedViaSTC, ExpectFailedViaSTC);
}
ISOLATE_UNIT_TEST_CASE(TTS_Regress40964) {
const char* kScript =
R"(
class A<T> {
test(x) => x as B<T>;
}
class B<T> {}
class C<T> {}
createACint() => A<C<int>>();
createBCint() => B<C<int>>();
createBCnum() => B<C<num>>();
)";
const auto& root_library = Library::Handle(LoadTestScript(kScript));
const auto& class_b = Class::Handle(GetClass(root_library, "B"));
const auto& class_null = Class::Handle(Class::null());
const auto& acint = Object::Handle(Invoke(root_library, "createACint"));
const auto& bcint = Object::Handle(Invoke(root_library, "createBCint"));
const auto& bcnum = Object::Handle(Invoke(root_library, "createBCnum"));
// dst_type = B<T>
const auto& dst_tav = TypeArguments::Handle(TypeArguments::New(1));
dst_tav.SetTypeAt(0,
TypeParameter::Handle(GetClassTypeParameter(class_b, "T")));
auto& dst_type =
Type::Handle(Type::New(class_b, dst_tav, TokenPosition::kNoSource));
FinalizeAndCanonicalize(class_null, &dst_type);
const auto& cint_tav =
TypeArguments::Handle(Instance::Cast(acint).GetTypeArguments());
const auto& function_tav = TypeArguments::Handle();
// a as B<T> -- a==B<C<int>, T==<C<int>>
RunTTSTest(bcint, dst_type, cint_tav, function_tav, ExpectLazilyHandledViaTTS,
ExpectHandledViaTTS);
// a as B<T> -- a==B<C<num>, T==<C<int>>
RunTTSTest(bcnum, dst_type, cint_tav, function_tav, ExpectLazilyFailedViaTTS,
ExpectFailedViaTTS);
}
} // namespace dart
#endif // defined(TARGET_ARCH_ARM64) || defined(TARGET_ARCH_ARM) || \
// defined(TARGET_ARCH_X64)