mirror of
https://github.com/dart-lang/sdk
synced 2024-09-19 20:51:50 +00:00
1f55b7ca10
This is a reland of 8a21ab195a
Original change's description:
> [VM/runtime] Refactor the representation of type parameters in the VM.
>
> This introduces a new VM internal class 'TypeParameters' representing the declaration of a list of type parameters, either in a class or function.
> The reference to (or use of) a type parameter is still represented by the existing 'TypeParameter' class.
>
> Fixes https://github.com/dart-lang/sdk/issues/43901
> Fixes https://github.com/dart-lang/sdk/issues/45763
>
> TEST=existing ones and a regression test
>
> Change-Id: I1fde808bf753cc1cb829f2c4383c1836651cee80
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/189942
> Commit-Queue: Régis Crelier <regis@google.com>
> Reviewed-by: Alexander Markov <alexmarkov@google.com>
This fixes https://github.com/dart-lang/sdk/issues/45911
TEST=existing ones and a regression test
Change-Id: I709d38b1df3d73fe3c9796d5aca3cbbdcf77fd38
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/198380
Commit-Queue: Régis Crelier <regis@google.com>
Reviewed-by: Alexander Markov <alexmarkov@google.com>
943 lines
41 KiB
C++
943 lines
41 KiB
C++
// 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/flow_graph_compiler.h"
|
|
#include "vm/compiler/backend/il_test_helper.h"
|
|
#include "vm/symbols.h"
|
|
#include "vm/type_testing_stubs.h"
|
|
#include "vm/unit_test.h"
|
|
|
|
#if defined(TARGET_ARCH_ARM64) || defined(TARGET_ARCH_ARM) || \
|
|
defined(TARGET_ARCH_X64)
|
|
|
|
namespace dart {
|
|
|
|
#define __ assembler->
|
|
|
|
static void GenerateInvokeTTSStub(compiler::Assembler* assembler) {
|
|
auto calculate_breadcrumb = [](const Register& reg) {
|
|
return 0x10 + 2 * (static_cast<intptr_t>(reg));
|
|
};
|
|
|
|
__ EnterDartFrame(0);
|
|
|
|
for (intptr_t i = 0; i < kNumberOfCpuRegisters; ++i) {
|
|
if (((1 << i) & kDartAvailableCpuRegs) == 0) continue;
|
|
if (((1 << i) & TypeTestABI::kAbiRegisters) != 0) continue;
|
|
if (((1 << i) & TTSInternalRegs::kInternalRegisters) != 0) continue;
|
|
const Register reg = static_cast<Register>(i);
|
|
__ LoadImmediate(reg, calculate_breadcrumb(reg));
|
|
}
|
|
|
|
// Load the arguments into the right TTS calling convention registers.
|
|
const intptr_t instance_offset =
|
|
(kCallerSpSlotFromFp + 3) * compiler::target::kWordSize;
|
|
const intptr_t inst_type_args_offset =
|
|
(kCallerSpSlotFromFp + 2) * compiler::target::kWordSize;
|
|
const intptr_t fun_type_args_offset =
|
|
(kCallerSpSlotFromFp + 1) * compiler::target::kWordSize;
|
|
const intptr_t dst_type_offset =
|
|
(kCallerSpSlotFromFp + 0) * compiler::target::kWordSize;
|
|
|
|
__ LoadMemoryValue(TypeTestABI::kInstanceReg, FPREG, instance_offset);
|
|
__ LoadMemoryValue(TypeTestABI::kInstantiatorTypeArgumentsReg, FPREG,
|
|
inst_type_args_offset);
|
|
__ LoadMemoryValue(TypeTestABI::kFunctionTypeArgumentsReg, FPREG,
|
|
fun_type_args_offset);
|
|
__ LoadMemoryValue(TypeTestABI::kDstTypeReg, FPREG, dst_type_offset);
|
|
|
|
const intptr_t subtype_test_cache_index = __ object_pool_builder().AddObject(
|
|
Object::null_object(), compiler::ObjectPoolBuilderEntry::kPatchable);
|
|
const intptr_t dst_name_index = __ object_pool_builder().AddObject(
|
|
Symbols::OptimizedOut(), compiler::ObjectPoolBuilderEntry::kPatchable);
|
|
ASSERT_EQUAL(subtype_test_cache_index + 1, dst_name_index);
|
|
ASSERT(__ constant_pool_allowed());
|
|
|
|
FlowGraphCompiler::GenerateIndirectTTSCall(
|
|
assembler, TypeTestABI::kDstTypeReg, subtype_test_cache_index);
|
|
|
|
// We have the guarantee that TTS preserves all input registers, if the TTS
|
|
// handles the type test successfully.
|
|
//
|
|
// Let the test know which TTS abi registers were not preserved.
|
|
ASSERT(((1 << static_cast<intptr_t>(TypeTestABI::kInstanceReg)) &
|
|
TypeTestABI::kPreservedAbiRegisters) != 0);
|
|
// First we check the instance register, freeing it up in case there are no
|
|
// other safe registers to use since we need two registers: one to accumulate
|
|
// the register mask, another to load the array address when saving the mask.
|
|
__ LoadFromOffset(TypeTestABI::kScratchReg, FPREG, instance_offset);
|
|
compiler::Label instance_matches, done_with_instance;
|
|
__ CompareRegisters(TypeTestABI::kScratchReg, TypeTestABI::kInstanceReg);
|
|
__ BranchIf(EQUAL, &instance_matches, compiler::Assembler::kNearJump);
|
|
__ LoadImmediate(TypeTestABI::kScratchReg,
|
|
1 << static_cast<intptr_t>(TypeTestABI::kInstanceReg));
|
|
__ Jump(&done_with_instance, compiler::Assembler::kNearJump);
|
|
__ Bind(&instance_matches);
|
|
__ LoadImmediate(TypeTestABI::kScratchReg, 0);
|
|
__ Bind(&done_with_instance);
|
|
for (intptr_t i = 0; i < kNumberOfCpuRegisters; ++i) {
|
|
if (((1 << i) & TypeTestABI::kPreservedAbiRegisters) == 0) continue;
|
|
const Register reg = static_cast<Register>(i);
|
|
compiler::Label done;
|
|
switch (reg) {
|
|
case TypeTestABI::kInstanceReg:
|
|
// Skip the already handled instance register.
|
|
continue;
|
|
case TypeTestABI::kDstTypeReg:
|
|
__ LoadFromOffset(TypeTestABI::kInstanceReg, FPREG, dst_type_offset);
|
|
break;
|
|
case TypeTestABI::kFunctionTypeArgumentsReg:
|
|
__ LoadFromOffset(TypeTestABI::kInstanceReg, FPREG,
|
|
fun_type_args_offset);
|
|
break;
|
|
case TypeTestABI::kInstantiatorTypeArgumentsReg:
|
|
__ LoadFromOffset(TypeTestABI::kInstanceReg, FPREG,
|
|
inst_type_args_offset);
|
|
break;
|
|
default:
|
|
FATAL("Unexpected register %s", RegisterNames::RegisterName(reg));
|
|
break;
|
|
}
|
|
__ CompareRegisters(reg, TypeTestABI::kInstanceReg);
|
|
__ BranchIf(EQUAL, &done, compiler::Assembler::kNearJump);
|
|
__ AddImmediate(TypeTestABI::kScratchReg, 1 << i);
|
|
__ Bind(&done);
|
|
}
|
|
__ SmiTag(TypeTestABI::kScratchReg);
|
|
__ LoadFromOffset(TypeTestABI::kInstanceReg, FPREG,
|
|
(kCallerSpSlotFromFp + 5) * compiler::target::kWordSize);
|
|
__ StoreFieldToOffset(TypeTestABI::kScratchReg, TypeTestABI::kInstanceReg,
|
|
compiler::target::Array::element_offset(0));
|
|
|
|
// Let the test know which non-TTS abi registers were not preserved.
|
|
__ LoadImmediate(TypeTestABI::kScratchReg, 0);
|
|
for (intptr_t i = 0; i < kNumberOfCpuRegisters; ++i) {
|
|
if (((1 << i) & kDartAvailableCpuRegs) == 0) continue;
|
|
if (((1 << i) & TypeTestABI::kAbiRegisters) != 0) continue;
|
|
const Register reg = static_cast<Register>(i);
|
|
compiler::Label done;
|
|
__ CompareImmediate(reg, calculate_breadcrumb(reg));
|
|
__ BranchIf(EQUAL, &done, compiler::Assembler::kNearJump);
|
|
__ AddImmediate(TypeTestABI::kScratchReg, 1 << i);
|
|
__ Bind(&done);
|
|
}
|
|
__ SmiTag(TypeTestABI::kScratchReg);
|
|
__ LoadFromOffset(TypeTestABI::kInstanceReg, FPREG,
|
|
(kCallerSpSlotFromFp + 4) * compiler::target::kWordSize);
|
|
__ StoreFieldToOffset(TypeTestABI::kScratchReg, TypeTestABI::kInstanceReg,
|
|
compiler::target::Array::element_offset(0));
|
|
|
|
// Set the return from the stub to be null.
|
|
__ LoadObject(CallingConventions::kReturnReg, Object::null_object());
|
|
__ LeaveDartFrame();
|
|
__ Ret();
|
|
}
|
|
|
|
#undef __
|
|
|
|
static void FinalizeAndCanonicalize(AbstractType* type) {
|
|
*type = ClassFinalizer::FinalizeType(*type);
|
|
ASSERT(type->IsCanonical());
|
|
}
|
|
|
|
static void CanonicalizeTAV(TypeArguments* tav) {
|
|
*tav = tav->Canonicalize(Thread::Current(), nullptr);
|
|
}
|
|
|
|
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 Smi& abi_regs_modified,
|
|
const Smi& rest_regs_modified)> nonlazy) {
|
|
THR_Print(
|
|
"TTS_Test(instance=%s, dst_type=%s, instantiator_tav=%s, "
|
|
"function_tav=%s)\n",
|
|
instance.ToCString(), dst_type.ToCString(), instantiator_tav.ToCString(),
|
|
function_tav.ToCString());
|
|
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_group()->class_table()->At(kInstanceCid));
|
|
const auto& symbol = String::Handle(
|
|
Symbols::New(thread, OS::SCreate(thread->zone(), "TTSTest")));
|
|
const auto& signature = FunctionType::ZoneHandle(FunctionType::New());
|
|
const auto& function = Function::Handle(Function::New(
|
|
signature, symbol, UntaggedFunction::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.ptr());
|
|
invoke_tts.set_owner(function);
|
|
invoke_tts.set_exception_handlers(
|
|
ExceptionHandlers::Handle(ExceptionHandlers::New(0)));
|
|
|
|
EXPECT_EQ(2, pool.Length());
|
|
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.
|
|
auto& instantiated_dst_type = AbstractType::Handle(dst_type.ptr());
|
|
if (dst_type.IsTypeParameter()) {
|
|
instantiated_dst_type = TypeParameter::Cast(dst_type).GetFromTypeArguments(
|
|
instantiator_tav, function_tav);
|
|
}
|
|
instantiated_dst_type.SetTypeTestingStub(StubCode::LazySpecializeTypeTest());
|
|
EXPECT(instantiated_dst_type.type_test_stub() ==
|
|
StubCode::LazySpecializeTypeTest().ptr());
|
|
EXPECT(pool.ObjectAt(kSubtypeTestCacheIndex) == Object::null());
|
|
|
|
auto& result = Object::Handle();
|
|
auto& result2 = Object::Handle();
|
|
auto& abi_regs_modified = Smi::Handle();
|
|
auto& rest_regs_modified = Smi::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, invoke_tts.EntryPoint(),
|
|
arguments_descriptor, arguments, thread);
|
|
stc ^= pool.ObjectAt(kSubtypeTestCacheIndex);
|
|
tts = instantiated_dst_type.type_test_stub();
|
|
if (!result.IsError()) {
|
|
EXPECT(tts.ptr() != StubCode::LazySpecializeTypeTest().ptr());
|
|
}
|
|
lazy(result, stc);
|
|
|
|
// Second invocation will a) keep TTS b) keep optional SubtypeTestCache
|
|
result2 = DartEntry::InvokeCode(invoke_tts, invoke_tts.EntryPoint(),
|
|
arguments_descriptor, arguments, thread);
|
|
stc2 ^= pool.ObjectAt(kSubtypeTestCacheIndex);
|
|
tts2 = instantiated_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.ptr() == tts.ptr());
|
|
EXPECT(stc2.ptr() == stc.ptr());
|
|
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)
|
|
TypeTestingStubGenerator::SpecializeStubFor(thread, instantiated_dst_type);
|
|
tts = instantiated_dst_type.type_test_stub();
|
|
|
|
result2 = DartEntry::InvokeCode(invoke_tts, invoke_tts.EntryPoint(),
|
|
arguments_descriptor, arguments, thread);
|
|
stc2 ^= pool.ObjectAt(kSubtypeTestCacheIndex);
|
|
tts2 = instantiated_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.ptr() == tts.ptr());
|
|
EXPECT(stc2.ptr() == stc.ptr());
|
|
nonlazy(result2, stc2, abi_regs_modified, rest_regs_modified);
|
|
}
|
|
|
|
static void ReportModifiedRegisters(const Smi& modified_registers) {
|
|
const intptr_t reg_mask = Smi::Cast(modified_registers).Value();
|
|
for (intptr_t i = 0; i < kNumberOfCpuRegisters; i++) {
|
|
if (((1 << i) & reg_mask) != 0) {
|
|
const Register reg = static_cast<Register>(i);
|
|
dart::Expect(__FILE__, __LINE__)
|
|
.Fail("%s was modified", RegisterNames::RegisterName(reg));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void CommonTTSHandledChecks(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 ExpectLazilyHandledViaTTS(const Object& result,
|
|
const SubtypeTestCache& stc) {
|
|
THR_Print("Testing lazy handled via TTS\n");
|
|
CommonTTSHandledChecks(result, stc);
|
|
}
|
|
|
|
static void ExpectHandledViaTTS(const Object& result,
|
|
const SubtypeTestCache& stc,
|
|
const Smi& abi_regs_modified,
|
|
const Smi& rest_regs_modified) {
|
|
THR_Print("Testing non-lazy handled via TTS\n");
|
|
CommonTTSHandledChecks(result, stc);
|
|
// Ensure the TTS abi registers were preserved.
|
|
ReportModifiedRegisters(abi_regs_modified);
|
|
// Ensure the non-TTS abi registers were preserved.
|
|
ReportModifiedRegisters(rest_regs_modified);
|
|
}
|
|
|
|
static void CommonSTCHandledChecks(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_EQ(1, stc.NumberOfChecks());
|
|
SubtypeTestCacheTable entries(Array::Handle(stc.cache()));
|
|
EXPECT(entries[0].Get<SubtypeTestCache::kTestResult>() ==
|
|
Object::bool_true().ptr());
|
|
}
|
|
|
|
static void ExpectLazilyHandledViaSTC(const Object& result,
|
|
const SubtypeTestCache& stc) {
|
|
THR_Print("Testing lazy handled via STC\n");
|
|
CommonSTCHandledChecks(result, stc);
|
|
}
|
|
|
|
static void ExpectHandledViaSTC(const Object& result,
|
|
const SubtypeTestCache& stc,
|
|
const Smi& abi_regs_modified,
|
|
const Smi& rest_regs_modified) {
|
|
THR_Print("Testing non-lazy handled via STC\n");
|
|
CommonSTCHandledChecks(result, stc);
|
|
// Ensure the TTS/STC abi registers were preserved.
|
|
ReportModifiedRegisters(abi_regs_modified);
|
|
// Ensure the non-TTS abi registers were preserved.
|
|
ReportModifiedRegisters(rest_regs_modified);
|
|
}
|
|
|
|
static void CommonTTSFailureChecks(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 ExpectLazilyFailedViaTTS(const Object& result,
|
|
const SubtypeTestCache& stc) {
|
|
THR_Print("Testing lazy failure via TTS\n");
|
|
CommonTTSFailureChecks(result, stc);
|
|
}
|
|
|
|
static void ExpectFailedViaTTS(const Object& result,
|
|
const SubtypeTestCache& stc,
|
|
const Smi& abi_regs_modified,
|
|
const Smi& rest_regs_modified) {
|
|
THR_Print("Testing nonlazy failure via TTS\n");
|
|
CommonTTSFailureChecks(result, stc);
|
|
// Registers only need to be preserved on success.
|
|
}
|
|
|
|
static void CommonSTCFailureChecks(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 ExpectLazilyFailedViaSTC(const Object& result,
|
|
const SubtypeTestCache& stc) {
|
|
THR_Print("Testing lazy failure via STC\n");
|
|
CommonSTCFailureChecks(result, stc);
|
|
}
|
|
|
|
static void ExpectFailedViaSTC(const Object& result,
|
|
const SubtypeTestCache& stc,
|
|
const Smi& abi_regs_modified,
|
|
const Smi& rest_regs_modified) {
|
|
THR_Print("Testing non-lazy failure via STC\n");
|
|
CommonSTCFailureChecks(result, stc);
|
|
// Registers only need to be preserved on success.
|
|
}
|
|
|
|
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>();
|
|
createBaseNull() => Base<Null>();
|
|
createBaseNever() => Base<Never>();
|
|
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& obj_i = Object::Handle(Invoke(root_library, "createI"));
|
|
const auto& obj_i2 = Object::Handle(Invoke(root_library, "createI2"));
|
|
const auto& obj_base_int =
|
|
Object::Handle(Invoke(root_library, "createBaseInt"));
|
|
const auto& obj_base_null =
|
|
Object::Handle(Invoke(root_library, "createBaseNull"));
|
|
const auto& obj_base_never =
|
|
Object::Handle(Invoke(root_library, "createBaseNever"));
|
|
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, 0)));
|
|
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_base_int, 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));
|
|
FinalizeAndCanonicalize(&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_base_int, type_base, tav_null, tav_null,
|
|
ExpectLazilyHandledViaTTS, ExpectHandledViaTTS);
|
|
RunTTSTest(obj_base_null, 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);
|
|
|
|
// Base<Null|Never> as Base<int?>
|
|
// This is a regression test verifying that we don't fall through into
|
|
// runtime for Null and Never.
|
|
auto& type_nullable_int = Type::Handle(Type::IntType());
|
|
type_nullable_int = type_nullable_int.ToNullability(
|
|
TestCase::IsNNBD() ? Nullability::kNullable : Nullability::kLegacy,
|
|
Heap::kNew);
|
|
auto& tav_nullable_int = TypeArguments::Handle(TypeArguments::New(1));
|
|
tav_nullable_int.SetTypeAt(0, type_nullable_int);
|
|
CanonicalizeTAV(&tav_nullable_int);
|
|
auto& type_base_nullable_int =
|
|
AbstractType::Handle(Type::New(class_base, tav_nullable_int));
|
|
FinalizeAndCanonicalize(&type_base_nullable_int);
|
|
RunTTSTest(obj_base_null, type_base_nullable_int, tav_null, tav_null,
|
|
ExpectLazilyHandledViaTTS, ExpectHandledViaTTS);
|
|
RunTTSTest(obj_base_never, type_base_nullable_int, tav_null, tav_null,
|
|
ExpectLazilyHandledViaTTS, ExpectHandledViaTTS);
|
|
|
|
if (TestCase::IsNNBD()) {
|
|
// Base<Null|Never> as Base<int>
|
|
auto& type_int = Type::Handle(Type::IntType());
|
|
type_int = type_int.ToNullability(Nullability::kNonNullable, Heap::kNew);
|
|
auto& tav_int = TypeArguments::Handle(TypeArguments::New(1));
|
|
tav_int.SetTypeAt(0, type_int);
|
|
CanonicalizeTAV(&tav_int);
|
|
auto& type_base_int = Type::Handle(Type::New(class_base, tav_int));
|
|
type_base_int =
|
|
type_base_int.ToNullability(Nullability::kNonNullable, Heap::kNew);
|
|
FinalizeAndCanonicalize(&type_base_int);
|
|
if (IsolateGroup::Current()->null_safety()) {
|
|
RunTTSTest(obj_base_null, type_base_int, tav_null, tav_null,
|
|
ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
|
|
}
|
|
RunTTSTest(obj_base_never, type_base_int, 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_base_int, 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));
|
|
FinalizeAndCanonicalize(&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_base_int, 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));
|
|
FinalizeAndCanonicalize(&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_base_int, 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)
|
|
auto isolate_group = IsolateGroup::Current();
|
|
if (isolate_group->null_safety()) {
|
|
auto& type_non_nullable_object =
|
|
Type::Handle(isolate_group->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& obj_i = Object::Handle(Invoke(root_library, "createI"));
|
|
const auto& obj_i2 = Object::Handle(Invoke(root_library, "createI2"));
|
|
const auto& obj_base_int =
|
|
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(&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));
|
|
FinalizeAndCanonicalize(&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));
|
|
FinalizeAndCanonicalize(&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, 0)));
|
|
auto& type_base_t = AbstractType::Handle(Type::New(class_base, tav_baset));
|
|
FinalizeAndCanonicalize(&type_base_t);
|
|
RunTTSTest(obj_base_int, 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, 1)));
|
|
auto& type_base_b = AbstractType::Handle(Type::New(class_base, tav_baseb));
|
|
FinalizeAndCanonicalize(&type_base_b);
|
|
// With B == int
|
|
RunTTSTest(obj_base_int, 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_base_int, 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));
|
|
type_i_dynamic_string = type_i_dynamic_string.ToNullability(
|
|
Nullability::kNonNullable, Heap::kNew);
|
|
FinalizeAndCanonicalize(&type_i_dynamic_string);
|
|
RunTTSTest(obj_i, type_i_dynamic_string, tav_null, tav_null,
|
|
ExpectLazilyHandledViaSTC, ExpectHandledViaSTC);
|
|
RunTTSTest(obj_base_int, 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, 0)));
|
|
auto& type_a2_t = Type::Handle(Type::New(class_a2, tav_t));
|
|
type_a2_t = type_a2_t.ToNullability(Nullability::kLegacy, Heap::kNew);
|
|
FinalizeAndCanonicalize(&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));
|
|
type_base_a2_t =
|
|
type_base_a2_t.ToNullability(Nullability::kNonNullable, Heap::kNew);
|
|
FinalizeAndCanonicalize(&type_base_a2_t);
|
|
RunTTSTest(obj_basea2int, type_base_a2_t, tav_null, tav_null,
|
|
ExpectLazilyHandledViaSTC, ExpectHandledViaSTC);
|
|
RunTTSTest(obj_base_int, 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));
|
|
type_a2_a1 = type_a2_a1.ToNullability(Nullability::kLegacy, Heap::kNew);
|
|
FinalizeAndCanonicalize(&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));
|
|
type_base_a2_a1 =
|
|
type_base_a2_a1.ToNullability(Nullability::kNonNullable, Heap::kNew);
|
|
FinalizeAndCanonicalize(&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& 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, 0)));
|
|
auto& dst_type = Type::Handle(Type::New(class_b, dst_tav));
|
|
FinalizeAndCanonicalize(&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);
|
|
}
|
|
|
|
ISOLATE_UNIT_TEST_CASE(TTS_TypeParameter) {
|
|
const char* kScript =
|
|
R"(
|
|
class A<T> {
|
|
T test(dynamic x) => x as T;
|
|
}
|
|
H genericFun<H>(dynamic x) => x as H;
|
|
|
|
createAInt() => A<int>();
|
|
createAString() => A<String>();
|
|
)";
|
|
|
|
const auto& root_library = Library::Handle(LoadTestScript(kScript));
|
|
const auto& class_a = Class::Handle(GetClass(root_library, "A"));
|
|
ClassFinalizer::FinalizeTypesInClass(class_a);
|
|
|
|
const auto& fun_generic =
|
|
Function::Handle(GetFunction(root_library, "genericFun"));
|
|
|
|
const auto& dst_type_t =
|
|
TypeParameter::Handle(GetClassTypeParameter(class_a, 0));
|
|
|
|
const auto& dst_type_h =
|
|
TypeParameter::Handle(GetFunctionTypeParameter(fun_generic, 0));
|
|
|
|
const auto& aint = Object::Handle(Invoke(root_library, "createAInt"));
|
|
const auto& astring = Object::Handle(Invoke(root_library, "createAString"));
|
|
|
|
const auto& int_tav =
|
|
TypeArguments::Handle(Instance::Cast(aint).GetTypeArguments());
|
|
const auto& string_tav =
|
|
TypeArguments::Handle(Instance::Cast(astring).GetTypeArguments());
|
|
|
|
const auto& int_instance = Integer::Handle(Integer::New(1));
|
|
const auto& string_instance = String::Handle(String::New("foo"));
|
|
|
|
THR_Print("Testing int instance, class parameter instantiated to int\n");
|
|
RunTTSTest(int_instance, dst_type_t, int_tav, string_tav,
|
|
ExpectLazilyHandledViaTTS, ExpectHandledViaTTS);
|
|
THR_Print("\nTesting string instance, class parameter instantiated to int\n");
|
|
RunTTSTest(string_instance, dst_type_t, int_tav, string_tav,
|
|
ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
|
|
|
|
THR_Print(
|
|
"\nTesting string instance, function parameter instantiated to string\n");
|
|
RunTTSTest(string_instance, dst_type_h, int_tav, string_tav,
|
|
ExpectLazilyHandledViaTTS, ExpectHandledViaTTS);
|
|
RunTTSTest(int_instance, dst_type_h, int_tav, string_tav,
|
|
ExpectLazilyFailedViaTTS, ExpectFailedViaTTS);
|
|
}
|
|
|
|
// Check that we generate correct TTS for _Smi type.
|
|
ISOLATE_UNIT_TEST_CASE(TTS_Smi) {
|
|
const auto& root_library = Library::Handle(Library::CoreLibrary());
|
|
const auto& smi_class = Class::Handle(GetClass(root_library, "_Smi"));
|
|
ClassFinalizer::FinalizeTypesInClass(smi_class);
|
|
|
|
const auto& dst_type = AbstractType::Handle(smi_class.RareType());
|
|
const auto& tav_null = TypeArguments::Handle(TypeArguments::null());
|
|
|
|
THR_Print("\nTesting that instance of _Smi is a subtype of _Smi\n");
|
|
RunTTSTest(Smi::Handle(Smi::New(0)), dst_type, tav_null, tav_null,
|
|
ExpectLazilyHandledViaTTS, ExpectHandledViaTTS);
|
|
}
|
|
|
|
} // namespace dart
|
|
|
|
#endif // defined(TARGET_ARCH_ARM64) || defined(TARGET_ARCH_ARM) || \
|
|
// defined(TARGET_ARCH_X64)
|