Reland "[VM/runtime] Handle generic types in intrinsics for type equality and runtimeType comparison."

This is a reland of 09d2025685

Fixes https://github.com/dart-lang/sdk/issues/23746

Intrinsics for sameRuntimeType wrongly assumed that types with unequal type arguments are unequal, which is not true:
1) nullability of individual type arguments may be different
2) one vector may be a prefix of the other vector

Note that the intrinsic for type equality did not make this assumption.

Case 2 above was not handled properly in the runtime.

TEST=added regression test

Original change's description:
> [VM/runtime] Handle generic types in intrinsics for type equality and runtimeType comparison.
>
> Generic types with equal class ids and equal type arguments are now considered equal by the intrinsics and a runtime call is avoided.
>
> Fixes https://github.com/dart-lang/sdk/issues/23746
>
> TEST=existing ones
>
> Change-Id: I668db119ac6d2525eac3a4f17a44f36c53b9dbf5
> Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/203143
> Reviewed-by: Ryan Macnak <rmacnak@google.com>
> Commit-Queue: Régis Crelier <regis@google.com>

Change-Id: I53fc00d856ecd9d9b8d66b8da95285e6e0bd508e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/204363
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Régis Crelier <regis@google.com>
This commit is contained in:
Regis Crelier 2021-06-22 20:35:53 +00:00 committed by commit-bot@chromium.org
parent 902f149e2c
commit 8b7870bc62
8 changed files with 264 additions and 98 deletions

View file

@ -1272,15 +1272,19 @@ void AsmIntrinsifier::ObjectRuntimeType(Assembler* assembler,
// Compares cid1 and cid2 to see if they're syntactically equivalent. If this
// can be determined by this fast path, it jumps to either equal or not_equal,
// otherwise it jumps to normal_ir_body. May clobber cid1, cid2, and scratch.
// if equal but belonging to a generic class, it falls through with the scratch
// register containing host_type_arguments_field_offset_in_words,
// otherwise it jumps to normal_ir_body. May clobber scratch.
static void EquivalentClassIds(Assembler* assembler,
Label* normal_ir_body,
Label* equal,
Label* not_equal,
Register cid1,
Register cid2,
Register scratch) {
Label different_cids, not_integer, not_integer_or_string;
Register scratch,
bool testing_instance_cids) {
Label different_cids, equal_cids_but_generic, not_integer,
not_integer_or_string;
// Check if left hand side is a closure. Closures are handled in the runtime.
__ CompareImmediate(cid1, kClosureCid);
@ -1296,10 +1300,13 @@ static void EquivalentClassIds(Assembler* assembler,
// Check if there are no type arguments. In this case we can return true.
// Otherwise fall through into the runtime to handle comparison.
__ LoadClassById(scratch, cid1);
__ ldrh(scratch,
FieldAddress(scratch, target::Class::num_type_arguments_offset()));
__ CompareImmediate(scratch, 0);
__ b(normal_ir_body, NE);
__ ldr(
scratch,
FieldAddress(
scratch,
target::Class::host_type_arguments_field_offset_in_words_offset()));
__ CompareImmediate(scratch, target::Class::kNoTypeArguments);
__ b(&equal_cids_but_generic, NE);
__ b(equal);
// Class ids are different. Check if we are comparing two string types (with
@ -1318,35 +1325,50 @@ static void EquivalentClassIds(Assembler* assembler,
__ Bind(&not_integer);
// Check if both are String types.
JumpIfNotString(assembler, cid1, scratch, &not_integer_or_string);
JumpIfNotString(assembler, cid1, scratch,
testing_instance_cids ? &not_integer_or_string : not_equal);
// First type is String. Check if the second is a string too.
JumpIfString(assembler, cid2, scratch, equal);
// String types are only equivalent to other String types.
__ b(not_equal);
__ Bind(&not_integer_or_string);
// Check if the first type is a Type. If it is not then types are not
// equivalent because they have different class ids and they are not String
// or integer or Type.
JumpIfNotType(assembler, cid1, scratch, not_equal);
if (testing_instance_cids) {
__ Bind(&not_integer_or_string);
// Check if the first type is a Type. If it is not then types are not
// equivalent because they have different class ids and they are not String
// or integer or Type.
JumpIfNotType(assembler, cid1, scratch, not_equal);
// First type is a Type. Check if the second is a Type too.
JumpIfType(assembler, cid2, scratch, equal);
// Type types are only equivalent to other Type types.
__ b(not_equal);
// First type is a Type. Check if the second is a Type too.
JumpIfType(assembler, cid2, scratch, equal);
// Type types are only equivalent to other Type types.
__ b(not_equal);
}
// The caller must compare the type arguments.
__ Bind(&equal_cids_but_generic);
}
void AsmIntrinsifier::ObjectHaveSameRuntimeType(Assembler* assembler,
Label* normal_ir_body) {
__ ldr(R0, Address(SP, 0 * target::kWordSize));
__ LoadClassIdMayBeSmi(R1, R0);
__ ldr(R0, Address(SP, 1 * target::kWordSize));
__ LoadClassIdMayBeSmi(R2, R0);
__ ldm(IA, SP, (1 << R1 | 1 << R2));
__ LoadClassIdMayBeSmi(R1, R1);
__ LoadClassIdMayBeSmi(R2, R2);
Label equal, not_equal;
EquivalentClassIds(assembler, normal_ir_body, &equal, &not_equal, R1, R2, R0);
EquivalentClassIds(assembler, normal_ir_body, &equal, &not_equal, R1, R2, R0,
/* testing_instance_cids = */ true);
// Compare type arguments, host_type_arguments_field_offset_in_words in R0.
__ ldm(IA, SP, (1 << R1 | 1 << R2));
__ AddImmediate(R1, -kHeapObjectTag);
__ ldr(R1, Address(R1, R0, LSL, target::kWordSizeLog2));
__ AddImmediate(R2, -kHeapObjectTag);
__ ldr(R2, Address(R2, R0, LSL, target::kWordSizeLog2));
__ cmp(R1, Operand(R2));
__ b(normal_ir_body, NE);
// Fall through to equal case if type arguments are equal.
__ Bind(&equal);
__ LoadObject(R0, CastHandle<Object>(TrueObject()));
@ -1396,8 +1418,16 @@ void AsmIntrinsifier::Type_equality(Assembler* assembler,
__ SmiUntag(R3);
__ ldr(R4, FieldAddress(R2, target::Type::type_class_id_offset()));
__ SmiUntag(R4);
// We are not testing instance cids, but type class cids of Type instances.
EquivalentClassIds(assembler, normal_ir_body, &equiv_cids, &not_equal, R3, R4,
R0);
R0, /* testing_instance_cids = */ false);
// Compare type arguments in Type instances.
__ ldr(R3, FieldAddress(R1, target::Type::arguments_offset()));
__ ldr(R4, FieldAddress(R2, target::Type::arguments_offset()));
__ cmp(R3, Operand(R4));
__ b(normal_ir_body, NE);
// Fall through to check nullability if type arguments are equal.
// Check nullability.
__ Bind(&equiv_cids);

View file

@ -1418,15 +1418,19 @@ void AsmIntrinsifier::ObjectRuntimeType(Assembler* assembler,
// Compares cid1 and cid2 to see if they're syntactically equivalent. If this
// can be determined by this fast path, it jumps to either equal or not_equal,
// otherwise it jumps to normal_ir_body. May clobber cid1, cid2, and scratch.
// if equal but belonging to a generic class, it falls through with the scratch
// register containing host_type_arguments_field_offset_in_words,
// otherwise it jumps to normal_ir_body. May clobber scratch.
static void EquivalentClassIds(Assembler* assembler,
Label* normal_ir_body,
Label* equal,
Label* not_equal,
Register cid1,
Register cid2,
Register scratch) {
Label different_cids, not_integer, not_integer_or_string;
Register scratch,
bool testing_instance_cids) {
Label different_cids, equal_cids_but_generic, not_integer,
not_integer_or_string;
// Check if left hand side is a closure. Closures are handled in the runtime.
__ CompareImmediate(cid1, kClosureCid);
@ -1443,10 +1447,12 @@ static void EquivalentClassIds(Assembler* assembler,
// Otherwise fall through into the runtime to handle comparison.
__ LoadClassById(scratch, cid1);
__ ldr(scratch,
FieldAddress(scratch, target::Class::num_type_arguments_offset(),
kTwoBytes),
kTwoBytes);
__ cbnz(normal_ir_body, scratch);
FieldAddress(
scratch,
target::Class::host_type_arguments_field_offset_in_words_offset()),
kFourBytes);
__ CompareImmediate(scratch, target::Class::kNoTypeArguments);
__ b(&equal_cids_but_generic, NE);
__ b(equal);
// Class ids are different. Check if we are comparing two string types (with
@ -1465,35 +1471,50 @@ static void EquivalentClassIds(Assembler* assembler,
__ Bind(&not_integer);
// Check if both are String types.
JumpIfNotString(assembler, cid1, scratch, &not_integer_or_string);
JumpIfNotString(assembler, cid1, scratch,
testing_instance_cids ? &not_integer_or_string : not_equal);
// First type is String. Check if the second is a string too.
JumpIfString(assembler, cid2, scratch, equal);
// String types are only equivalent to other String types.
__ b(not_equal);
__ Bind(&not_integer_or_string);
// Check if the first type is a Type. If it is not then types are not
// equivalent because they have different class ids and they are not String
// or integer or Type.
JumpIfNotType(assembler, cid1, scratch, not_equal);
if (testing_instance_cids) {
__ Bind(&not_integer_or_string);
// Check if the first type is a Type. If it is not then types are not
// equivalent because they have different class ids and they are not String
// or integer or Type.
JumpIfNotType(assembler, cid1, scratch, not_equal);
// First type is a Type. Check if the second is a Type too.
JumpIfType(assembler, cid2, scratch, equal);
// Type types are only equivalent to other Type types.
__ b(not_equal);
// First type is a Type. Check if the second is a Type too.
JumpIfType(assembler, cid2, scratch, equal);
// Type types are only equivalent to other Type types.
__ b(not_equal);
}
// The caller must compare the type arguments.
__ Bind(&equal_cids_but_generic);
}
void AsmIntrinsifier::ObjectHaveSameRuntimeType(Assembler* assembler,
Label* normal_ir_body) {
__ ldr(R0, Address(SP, 0 * target::kWordSize));
__ ldp(R0, R1, Address(SP, 0 * target::kWordSize, Address::PairOffset));
__ LoadClassIdMayBeSmi(R2, R1);
__ LoadClassIdMayBeSmi(R1, R0);
__ ldr(R0, Address(SP, 1 * target::kWordSize));
__ LoadClassIdMayBeSmi(R2, R0);
Label equal, not_equal;
EquivalentClassIds(assembler, normal_ir_body, &equal, &not_equal, R1, R2, R0);
EquivalentClassIds(assembler, normal_ir_body, &equal, &not_equal, R1, R2, R0,
/* testing_instance_cids = */ true);
// Compare type arguments, host_type_arguments_field_offset_in_words in R0.
__ ldp(R1, R2, Address(SP, 0 * target::kWordSize, Address::PairOffset));
__ AddImmediate(R1, -kHeapObjectTag);
__ ldr(R1, Address(R1, R0, UXTX, Address::Scaled));
__ AddImmediate(R2, -kHeapObjectTag);
__ ldr(R2, Address(R2, R0, UXTX, Address::Scaled));
__ CompareObjectRegisters(R1, R2);
__ b(normal_ir_body, NE);
// Fall through to equal case if type arguments are equal.
__ Bind(&equal);
__ LoadObject(R0, CastHandle<Object>(TrueObject()));
@ -1549,8 +1570,16 @@ void AsmIntrinsifier::Type_equality(Assembler* assembler,
__ LoadCompressedSmi(R4,
FieldAddress(R2, target::Type::type_class_id_offset()));
__ SmiUntag(R4);
// We are not testing instance cids, but type class cids of Type instances.
EquivalentClassIds(assembler, normal_ir_body, &equiv_cids, &not_equal, R3, R4,
R0);
R0, /* testing_instance_cids = */ false);
// Compare type arguments in Type instances.
__ LoadCompressed(R3, FieldAddress(R1, target::Type::arguments_offset()));
__ LoadCompressed(R4, FieldAddress(R2, target::Type::arguments_offset()));
__ CompareObjectRegisters(R3, R4);
__ b(normal_ir_body, NE);
// Fall through to check nullability if type arguments are equal.
// Check nullability.
__ Bind(&equiv_cids);

View file

@ -1358,15 +1358,19 @@ void AsmIntrinsifier::ObjectRuntimeType(Assembler* assembler,
// Compares cid1 and cid2 to see if they're syntactically equivalent. If this
// can be determined by this fast path, it jumps to either equal or not_equal,
// otherwise it jumps to normal_ir_body. May clobber cid1, cid2, and scratch.
// if equal but belonging to a generic class, it falls through with the scratch
// register containing host_type_arguments_field_offset_in_words,
// otherwise it jumps to normal_ir_body. May clobber scratch.
static void EquivalentClassIds(Assembler* assembler,
Label* normal_ir_body,
Label* equal,
Label* not_equal,
Register cid1,
Register cid2,
Register scratch) {
Label different_cids, not_integer, not_integer_or_string;
Register scratch,
bool testing_instance_cids) {
Label different_cids, equal_cids_but_generic, not_integer,
not_integer_or_string;
// Check if left hand side is a closure. Closures are handled in the runtime.
__ cmpl(cid1, Immediate(kClosureCid));
@ -1382,10 +1386,13 @@ static void EquivalentClassIds(Assembler* assembler,
// Check if there are no type arguments. In this case we can return true.
// Otherwise fall through into the runtime to handle comparison.
__ LoadClassById(scratch, cid1);
__ movzxw(scratch,
FieldAddress(scratch, target::Class::num_type_arguments_offset()));
__ cmpl(scratch, Immediate(0));
__ j(NOT_EQUAL, normal_ir_body);
__ movl(
scratch,
FieldAddress(
scratch,
target::Class::host_type_arguments_field_offset_in_words_offset()));
__ cmpl(scratch, Immediate(target::Class::kNoTypeArguments));
__ j(NOT_EQUAL, &equal_cids_but_generic, Assembler::kNearJump);
__ jmp(equal);
// Class ids are different. Check if we are comparing two string types (with
@ -1405,23 +1412,29 @@ static void EquivalentClassIds(Assembler* assembler,
__ Bind(&not_integer);
// Check if both are String types.
JumpIfNotString(assembler, cid1, &not_integer_or_string);
JumpIfNotString(assembler, cid1,
testing_instance_cids ? &not_integer_or_string : not_equal);
// First type is a String. Check if the second is a String too.
JumpIfString(assembler, cid2, equal);
// String types are only equivalent to other String types.
__ jmp(not_equal);
__ Bind(&not_integer_or_string);
// Check if the first type is a Type. If it is not then types are not
// equivalent because they have different class ids and they are not String
// or integer or Type.
JumpIfNotType(assembler, cid1, not_equal);
if (testing_instance_cids) {
__ Bind(&not_integer_or_string);
// Check if the first type is a Type. If it is not then types are not
// equivalent because they have different class ids and they are not String
// or integer or Type.
JumpIfNotType(assembler, cid1, not_equal);
// First type is a Type. Check if the second is a Type too.
JumpIfType(assembler, cid2, equal);
// Type types are only equivalent to other Type types.
__ jmp(not_equal);
// First type is a Type. Check if the second is a Type too.
JumpIfType(assembler, cid2, equal);
// Type types are only equivalent to other Type types.
__ jmp(not_equal);
}
// The caller must compare the type arguments.
__ Bind(&equal_cids_but_generic);
}
void AsmIntrinsifier::ObjectHaveSameRuntimeType(Assembler* assembler,
@ -1434,7 +1447,16 @@ void AsmIntrinsifier::ObjectHaveSameRuntimeType(Assembler* assembler,
Label equal, not_equal;
EquivalentClassIds(assembler, normal_ir_body, &equal, &not_equal, EDI, EBX,
EAX);
EAX, /* testing_instance_cids = */ true);
// Compare type arguments, host_type_arguments_field_offset_in_words in EAX.
__ movl(EDI, Address(ESP, +1 * target::kWordSize));
__ movl(EBX, Address(ESP, +2 * target::kWordSize));
__ movl(EDI, FieldAddress(EDI, EAX, TIMES_4, 0));
__ movl(EBX, FieldAddress(EBX, EAX, TIMES_4, 0));
__ cmpl(EDI, EBX);
__ j(NOT_EQUAL, normal_ir_body, Assembler::kNearJump);
// Fall through to equal case if type arguments are equal.
__ Bind(&equal);
__ LoadObject(EAX, CastHandle<Object>(TrueObject()));
@ -1489,8 +1511,16 @@ void AsmIntrinsifier::Type_equality(Assembler* assembler,
__ SmiUntag(ECX);
__ movl(EDX, FieldAddress(EBX, target::Type::type_class_id_offset()));
__ SmiUntag(EDX);
// We are not testing instance cids, but type class cids of Type instances.
EquivalentClassIds(assembler, normal_ir_body, &equiv_cids, &not_equal, ECX,
EDX, EAX);
EDX, EAX, /* testing_instance_cids = */ false);
// Compare type arguments in Type instances.
__ movl(ECX, FieldAddress(EDI, target::Type::arguments_offset()));
__ movl(EDX, FieldAddress(EBX, target::Type::arguments_offset()));
__ cmpl(ECX, EDX);
__ j(NOT_EQUAL, normal_ir_body, Assembler::kNearJump);
// Fall through to check nullability if type arguments are equal.
// Check nullability.
__ Bind(&equiv_cids);

View file

@ -1262,15 +1262,19 @@ void AsmIntrinsifier::ObjectRuntimeType(Assembler* assembler,
// Compares cid1 and cid2 to see if they're syntactically equivalent. If this
// can be determined by this fast path, it jumps to either equal or not_equal,
// otherwise it jumps to normal_ir_body. May clobber cid1, cid2, and scratch.
// if equal but belonging to a generic class, it falls through with the scratch
// register containing host_type_arguments_field_offset_in_words,
// otherwise it jumps to normal_ir_body. May clobber scratch.
static void EquivalentClassIds(Assembler* assembler,
Label* normal_ir_body,
Label* equal,
Label* not_equal,
Register cid1,
Register cid2,
Register scratch) {
Label different_cids, not_integer, not_integer_or_string;
Register scratch,
bool testing_instance_cids) {
Label different_cids, equal_cids_but_generic, not_integer,
not_integer_or_string;
// Check if left hand side is a closure. Closures are handled in the runtime.
__ cmpq(cid1, Immediate(kClosureCid));
@ -1286,10 +1290,13 @@ static void EquivalentClassIds(Assembler* assembler,
// Check if there are no type arguments. In this case we can return true.
// Otherwise fall through into the runtime to handle comparison.
__ LoadClassById(scratch, cid1);
__ movzxw(scratch,
FieldAddress(scratch, target::Class::num_type_arguments_offset()));
__ cmpq(scratch, Immediate(0));
__ j(NOT_EQUAL, normal_ir_body);
__ movl(
scratch,
FieldAddress(
scratch,
target::Class::host_type_arguments_field_offset_in_words_offset()));
__ cmpl(scratch, Immediate(target::Class::kNoTypeArguments));
__ j(NOT_EQUAL, &equal_cids_but_generic, Assembler::kNearJump);
__ jmp(equal);
// Class ids are different. Check if we are comparing two string types (with
@ -1309,23 +1316,29 @@ static void EquivalentClassIds(Assembler* assembler,
__ Bind(&not_integer);
// Check if both are String types.
JumpIfNotString(assembler, cid1, &not_integer_or_string);
JumpIfNotString(assembler, cid1,
testing_instance_cids ? &not_integer_or_string : not_equal);
// First type is a String. Check if the second is a String too.
JumpIfString(assembler, cid2, equal);
// String types are only equivalent to other String types.
__ jmp(not_equal);
__ Bind(&not_integer_or_string);
// Check if the first type is a Type. If it is not then types are not
// equivalent because they have different class ids and they are not String
// or integer or Type.
JumpIfNotType(assembler, cid1, not_equal);
if (testing_instance_cids) {
__ Bind(&not_integer_or_string);
// Check if the first type is a Type. If it is not then types are not
// equivalent because they have different class ids and they are not String
// or integer or Type.
JumpIfNotType(assembler, cid1, not_equal);
// First type is a Type. Check if the second is a Type too.
JumpIfType(assembler, cid2, equal);
// Type types are only equivalent to other Type types.
__ jmp(not_equal);
// First type is a Type. Check if the second is a Type too.
JumpIfType(assembler, cid2, equal);
// Type types are only equivalent to other Type types.
__ jmp(not_equal);
}
// The caller must compare the type arguments.
__ Bind(&equal_cids_but_generic);
}
void AsmIntrinsifier::ObjectHaveSameRuntimeType(Assembler* assembler,
@ -1338,7 +1351,16 @@ void AsmIntrinsifier::ObjectHaveSameRuntimeType(Assembler* assembler,
Label equal, not_equal;
EquivalentClassIds(assembler, normal_ir_body, &equal, &not_equal, RCX, RDX,
RAX);
RAX, /* testing_instance_cids = */ true);
// Compare type arguments, host_type_arguments_field_offset_in_words in RAX.
__ movq(RCX, Address(RSP, +1 * target::kWordSize));
__ movq(RDX, Address(RSP, +2 * target::kWordSize));
__ movq(RCX, FieldAddress(RCX, RAX, TIMES_8, 0));
__ movq(RDX, FieldAddress(RDX, RAX, TIMES_8, 0));
__ cmpq(RCX, RDX);
__ j(NOT_EQUAL, normal_ir_body, Assembler::kNearJump);
// Fall through to equal case if type arguments are equal.
__ Bind(&equal);
__ LoadObject(RAX, CastHandle<Object>(TrueObject()));
@ -1399,8 +1421,16 @@ void AsmIntrinsifier::Type_equality(Assembler* assembler,
__ LoadCompressedSmi(RSI,
FieldAddress(RDX, target::Type::type_class_id_offset()));
__ SmiUntag(RSI);
// We are not testing instance cids, but type class cids of Type instances.
EquivalentClassIds(assembler, normal_ir_body, &equiv_cids, &not_equal, RDI,
RSI, RAX);
RSI, RAX, /* testing_instance_cids = */ false);
// Compare type arguments in Type instances.
__ LoadCompressed(RDI, FieldAddress(RCX, target::Type::arguments_offset()));
__ LoadCompressed(RSI, FieldAddress(RDX, target::Type::arguments_offset()));
__ cmpq(RDI, RSI);
__ j(NOT_EQUAL, normal_ir_body, Assembler::kNearJump);
// Fall through to check nullability if type arguments are equal.
// Check nullability.
__ Bind(&equiv_cids);

View file

@ -6183,17 +6183,18 @@ void TypeParameters::Print(Thread* thread,
printer->AddString(TypeParameter::CanonicalNameCString(
are_class_type_parameters, base, base + i));
}
if (!AllDynamicBounds()) {
if (FLAG_show_internal_names || !AllDynamicBounds()) {
type = BoundAt(i);
// Do not print default bound or non-nullable Object bound in weak mode.
if (!type.IsNull() &&
(!type.IsObjectType() ||
(FLAG_show_internal_names || !type.IsObjectType() ||
(thread->isolate_group()->null_safety() && type.IsNonNullable()))) {
printer->AddString(" extends ");
type.PrintName(name_visibility, printer);
if (FLAG_show_internal_names && !AllDynamicDefaults()) {
type = DefaultAt(i);
if (!type.IsNull() && !type.IsDynamicType()) {
if (!type.IsNull() &&
(FLAG_show_internal_names || !type.IsDynamicType())) {
printer->AddString(" defaults to ");
type.PrintName(name_visibility, printer);
}
@ -6427,18 +6428,19 @@ bool TypeArguments::IsSubvectorEquivalent(const TypeArguments& other,
if (this->ptr() == other.ptr()) {
return true;
}
if (IsNull() || other.IsNull()) {
return false;
}
const intptr_t num_types = Length();
if (num_types != other.Length()) {
return false;
if (kind == TypeEquality::kCanonical) {
if (IsNull() || other.IsNull()) {
return false;
}
if (Length() != other.Length()) {
return false;
}
}
AbstractType& type = AbstractType::Handle();
AbstractType& other_type = AbstractType::Handle();
for (intptr_t i = from_index; i < from_index + len; i++) {
type = TypeAt(i);
other_type = other.TypeAt(i);
type = IsNull() ? Type::DynamicType() : TypeAt(i);
other_type = other.IsNull() ? Type::DynamicType() : other.TypeAt(i);
// Still unfinalized vectors should not be considered equivalent.
if (type.IsNull() || !type.IsEquivalent(other_type, kind, trail)) {
return false;

View file

@ -7735,8 +7735,9 @@ class TypeArguments : public Instance {
bool IsEquivalent(const TypeArguments& other,
TypeEquality kind,
TrailPtr trail = nullptr) const {
return IsSubvectorEquivalent(other, 0, IsNull() ? 0 : Length(), kind,
trail);
// Make a null vector a vector of dynamic as long as the other vector.
return IsSubvectorEquivalent(other, 0, IsNull() ? other.Length() : Length(),
kind, trail);
}
bool IsSubvectorEquivalent(const TypeArguments& other,
intptr_t from_index,

View file

@ -0,0 +1,22 @@
// Copyright (c) 2021, 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.
import "package:expect/expect.dart";
class A<T, U> {
B<T> get b => B<T>();
}
class B<T> {}
@pragma('vm:never-inline')
bool test(Object a, Object b) {
print(a.runtimeType);
print(b.runtimeType);
return a.runtimeType == b.runtimeType;
}
void main() {
Expect.isTrue(test(B<int>(), A<int, String>().b));
}

View file

@ -0,0 +1,22 @@
// Copyright (c) 2021, 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.
import "package:expect/expect.dart";
class A<T, U> {
B<T> get b => B<T>();
}
class B<T> {}
@pragma('vm:never-inline')
bool test(Object a, Object b) {
print(a.runtimeType);
print(b.runtimeType);
return a.runtimeType == b.runtimeType;
}
void main() {
Expect.isTrue(test(B<int>(), A<int, String>().b));
}