v8/test/unittests/torque/torque-unittest.cc
Seth Brenith f30f481525 [torque] Invert the default for @generateCppClass
Since most Torque-defined extern classes use @generateCppClass, it makes
more sense to instead annotate the small number that don't. This is part
of the cleanup work that Nico recommended in [1].

Classes that still have to opt out:

- Those that can be converted by https://crrev.com/c/3015666
- HeapObject: sort of special since it's the root of the inheritance
  hierarchy. Generated code would include two declarations that don't
  compile until HeapObject is defined:
    bool IsHeapObject_NonInline(HeapObject o);
    explicit TorqueGeneratedHeapObject(
        Address ptr, HeapObject::AllowInlineSmiStorage allow_smi);
- SmallOrdered*: these classes use templates on the C++ side, which is
  not currently representable in Torque.
- SwissNameDictionary: according to a comment, the Torque generation for
  this class is incorrect. I haven't investigated further.

Drive-by fix: make the Torque formatter keep LF on Windows rather than
writing CRLF.

[1] https://docs.google.com/document/d/1q_gZLnXd4bGnCx3IUfbln46K3bSs9UHBGasy9McQtHI/edit#

Bug: v8:8952
Change-Id: I1fbb5290f0c645842b84c53816c09bb3398206a5
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3028721
Reviewed-by: Nico Hartmann <nicohartmann@chromium.org>
Reviewed-by: Clemens Backes <clemensb@chromium.org>
Commit-Queue: Seth Brenith <seth.brenith@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#75841}
2021-07-21 15:21:00 +00:00

967 lines
25 KiB
C++

// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/torque/torque-compiler.h"
#include "src/torque/utils.h"
#include "test/unittests/test-utils.h"
#include "testing/gmock-support.h"
namespace v8 {
namespace internal {
namespace torque {
namespace {
// This is a simplified version of the basic Torque type definitions.
// Some class types are replaced by abstact types to keep it self-contained and
// small.
constexpr const char* kTestTorquePrelude = R"(
type void;
type never;
namespace torque_internal {
struct Reference<T: type> {
const object: HeapObject;
const offset: intptr;
}
type ConstReference<T : type> extends Reference<T>;
type MutableReference<T : type> extends ConstReference<T>;
type UninitializedHeapObject extends HeapObject;
macro DownCastForTorqueClass<T : type extends HeapObject>(o: HeapObject):
T labels _CastError {
return %RawDownCast<T>(o);
}
macro IsWithContext<T : type extends HeapObject>(o: HeapObject): bool {
return false;
}
}
type Tagged generates 'TNode<MaybeObject>' constexpr 'MaybeObject';
type StrongTagged extends Tagged
generates 'TNode<Object>' constexpr 'ObjectPtr';
type Smi extends StrongTagged generates 'TNode<Smi>' constexpr 'Smi';
type WeakHeapObject extends Tagged;
type Weak<T : type extends HeapObject> extends WeakHeapObject;
type Uninitialized extends Tagged;
type TaggedIndex extends StrongTagged;
type TaggedZeroPattern extends TaggedIndex;
@abstract
@doNotGenerateCppClass
extern class HeapObject extends StrongTagged {
map: Map;
}
type Map extends HeapObject generates 'TNode<Map>';
type Object = Smi | HeapObject;
type Number = Smi|HeapNumber;
type JSReceiver extends HeapObject generates 'TNode<JSReceiver>';
type JSObject extends JSReceiver generates 'TNode<JSObject>';
type int32 generates 'TNode<Int32T>' constexpr 'int32_t';
type uint32 generates 'TNode<Uint32T>' constexpr 'uint32_t';
type int31 extends int32
generates 'TNode<Int32T>' constexpr 'int31_t';
type uint31 extends uint32
generates 'TNode<Uint32T>' constexpr 'uint31_t';
type int16 extends int31
generates 'TNode<Int16T>' constexpr 'int16_t';
type uint16 extends uint31
generates 'TNode<Uint16T>' constexpr 'uint16_t';
type int8 extends int16 generates 'TNode<Int8T>' constexpr 'int8_t';
type uint8 extends uint16
generates 'TNode<Uint8T>' constexpr 'uint8_t';
type int64 generates 'TNode<Int64T>' constexpr 'int64_t';
type intptr generates 'TNode<IntPtrT>' constexpr 'intptr_t';
type uintptr generates 'TNode<UintPtrT>' constexpr 'uintptr_t';
type float32 generates 'TNode<Float32T>' constexpr 'float';
type float64 generates 'TNode<Float64T>' constexpr 'double';
type bool generates 'TNode<BoolT>' constexpr 'bool';
type bint generates 'TNode<BInt>' constexpr 'BInt';
type string constexpr 'const char*';
type RawPtr generates 'TNode<RawPtrT>' constexpr 'void*';
type ExternalPointer
generates 'TNode<ExternalPointerT>' constexpr 'ExternalPointer_t';
type Code extends HeapObject generates 'TNode<Code>';
type BuiltinPtr extends Smi generates 'TNode<BuiltinPtr>';
type Context extends HeapObject generates 'TNode<Context>';
type NativeContext extends Context;
type SmiTagged<T : type extends uint31> extends Smi;
type String extends HeapObject;
type HeapNumber extends HeapObject;
type FixedArrayBase extends HeapObject;
type Lazy<T: type>;
struct float64_or_hole {
is_hole: bool;
value: float64;
}
extern operator '+' macro IntPtrAdd(intptr, intptr): intptr;
extern operator '!' macro Word32BinaryNot(bool): bool;
extern operator '==' macro Word32Equal(int32, int32): bool;
intrinsic %FromConstexpr<To: type, From: type>(b: From): To;
intrinsic %RawDownCast<To: type, From: type>(x: From): To;
intrinsic %RawConstexprCast<To: type, From: type>(f: From): To;
extern macro SmiConstant(constexpr Smi): Smi;
extern macro TaggedToSmi(Object): Smi
labels CastError;
extern macro TaggedToHeapObject(Object): HeapObject
labels CastError;
extern macro Float64SilenceNaN(float64): float64;
extern macro IntPtrConstant(constexpr int31): intptr;
macro FromConstexpr<To: type, From: type>(o: From): To;
FromConstexpr<Smi, constexpr Smi>(s: constexpr Smi): Smi {
return SmiConstant(s);
}
FromConstexpr<Smi, constexpr int31>(s: constexpr int31): Smi {
return %FromConstexpr<Smi>(s);
}
FromConstexpr<intptr, constexpr int31>(i: constexpr int31): intptr {
return IntPtrConstant(i);
}
FromConstexpr<intptr, constexpr intptr>(i: constexpr intptr): intptr {
return %FromConstexpr<intptr>(i);
}
extern macro BoolConstant(constexpr bool): bool;
FromConstexpr<bool, constexpr bool>(b: constexpr bool): bool {
return BoolConstant(b);
}
FromConstexpr<int32, constexpr int31>(i: constexpr int31): int32 {
return %FromConstexpr<int32>(i);
}
macro Cast<A : type extends Object>(implicit context: Context)(o: Object): A
labels CastError {
return Cast<A>(TaggedToHeapObject(o) otherwise CastError)
otherwise CastError;
}
macro Cast<A : type extends HeapObject>(o: HeapObject): A
labels CastError;
Cast<Smi>(o: Object): Smi
labels CastError {
return TaggedToSmi(o) otherwise CastError;
}
)";
TorqueCompilerResult TestCompileTorque(std::string source) {
TorqueCompilerOptions options;
options.output_directory = "";
options.collect_language_server_data = false;
options.force_assert_statements = false;
options.v8_root = ".";
source = kTestTorquePrelude + source;
return CompileTorque(source, options);
}
void ExpectSuccessfulCompilation(std::string source) {
TorqueCompilerResult result = TestCompileTorque(std::move(source));
std::vector<std::string> messages;
for (const auto& message : result.messages) {
messages.push_back(message.message);
}
EXPECT_EQ(messages, std::vector<std::string>{});
}
template <class T>
using MatcherVector =
std::vector<std::pair<::testing::PolymorphicMatcher<T>, LineAndColumn>>;
template <class T>
void ExpectFailingCompilation(std::string source,
MatcherVector<T> message_patterns) {
TorqueCompilerResult result = TestCompileTorque(std::move(source));
ASSERT_FALSE(result.messages.empty());
EXPECT_GE(result.messages.size(), message_patterns.size());
size_t limit = message_patterns.size();
if (result.messages.size() < limit) {
limit = result.messages.size();
}
for (size_t i = 0; i < limit; ++i) {
EXPECT_THAT(result.messages[i].message, message_patterns[i].first);
if (message_patterns[i].second != LineAndColumn::Invalid()) {
base::Optional<SourcePosition> actual = result.messages[i].position;
EXPECT_TRUE(actual.has_value());
EXPECT_EQ(actual->start, message_patterns[i].second);
}
}
}
template <class T>
void ExpectFailingCompilation(
std::string source, ::testing::PolymorphicMatcher<T> message_pattern) {
ExpectFailingCompilation(
source, MatcherVector<T>{{message_pattern, LineAndColumn::Invalid()}});
}
// TODO(almuthanna): the definition of this function is skipped on Fuchsia
// because it causes an 'unused function' exception upon buidling gn
// Ticket: https://crbug.com/1028617
#if !defined(V8_TARGET_OS_FUCHSIA)
int CountPreludeLines() {
static int result = -1;
if (result == -1) {
std::string prelude(kTestTorquePrelude);
result = static_cast<int>(std::count(prelude.begin(), prelude.end(), '\n'));
}
return result;
}
#endif
using SubstrWithPosition =
std::pair<::testing::PolymorphicMatcher<
::testing::internal::HasSubstrMatcher<std::string>>,
LineAndColumn>;
// TODO(almuthanna): the definition of this function is skipped on Fuchsia
// because it causes an 'unused function' exception upon buidling gn
// Ticket: https://crbug.com/1028617
#if !defined(V8_TARGET_OS_FUCHSIA)
SubstrWithPosition SubstrTester(const std::string& message, int line, int col) {
// Change line and column from 1-based to 0-based.
return {::testing::HasSubstr(message),
LineAndColumn{line + CountPreludeLines() - 1, col - 1}};
}
#endif
using SubstrVector = std::vector<SubstrWithPosition>;
} // namespace
TEST(Torque, Prelude) { ExpectSuccessfulCompilation(""); }
TEST(Torque, StackDeleteRange) {
Stack<int> stack = {1, 2, 3, 4, 5, 6, 7};
stack.DeleteRange(StackRange{BottomOffset{2}, BottomOffset{4}});
Stack<int> result = {1, 2, 5, 6, 7};
ASSERT_TRUE(stack == result);
}
using ::testing::HasSubstr;
TEST(Torque, TypeNamingConventionLintError) {
ExpectFailingCompilation(R"(
type foo generates 'TNode<Foo>';
)",
HasSubstr("\"foo\""));
}
TEST(Torque, StructNamingConventionLintError) {
ExpectFailingCompilation(R"(
struct foo {}
)",
HasSubstr("\"foo\""));
}
TEST(Torque, ClassDefinition) {
ExpectSuccessfulCompilation(R"(
extern class TestClassWithAllTypes extends HeapObject {
a: int8;
b: uint8;
b2: uint8;
b3: uint8;
c: int16;
d: uint16;
e: int32;
f: uint32;
g: RawPtr;
h: intptr;
i: uintptr;
}
@export
macro TestClassWithAllTypesLoadsAndStores(
t: TestClassWithAllTypes, r: RawPtr, v1: int8, v2: uint8, v3: int16,
v4: uint16, v5: int32, v6: uint32, v7: intptr, v8: uintptr) {
t.a = v1;
t.b = v2;
t.c = v3;
t.d = v4;
t.e = v5;
t.f = v6;
t.g = r;
t.h = v7;
t.i = v8;
t.a = t.a;
t.b = t.b;
t.c = t.c;
t.d = t.d;
t.e = t.e;
t.f = t.f;
t.g = t.g;
t.h = t.h;
t.i = t.i;
}
)");
}
TEST(Torque, TypeDeclarationOrder) {
ExpectSuccessfulCompilation(R"(
type Baztype = Foo | FooType;
@abstract
extern class Foo extends HeapObject {
fooField: FooType;
}
extern class Bar extends Foo {
barField: Bartype;
bazfield: Baztype;
}
type Bartype = FooType;
type FooType = Smi | Bar;
)");
}
// TODO(almuthanna): These tests were skipped because they cause a crash when
// they are ran on Fuchsia. This issue should be solved later on
// Ticket: https://crbug.com/1028617
#if !defined(V8_TARGET_OS_FUCHSIA)
TEST(Torque, ConditionalFields) {
// This class should throw alignment errors if @if decorators aren't
// working.
ExpectSuccessfulCompilation(R"(
extern class PreprocessingTest extends HeapObject {
@if(FALSE_FOR_TESTING) a: int8;
@if(TRUE_FOR_TESTING) a: int16;
b: int16;
d: int32;
@ifnot(TRUE_FOR_TESTING) e: int8;
@ifnot(FALSE_FOR_TESTING) f: int16;
g: int16;
h: int32;
}
)");
ExpectFailingCompilation(R"(
extern class PreprocessingTest extends HeapObject {
@if(TRUE_FOR_TESTING) a: int8;
@if(FALSE_FOR_TESTING) a: int16;
b: int16;
d: int32;
@ifnot(FALSE_FOR_TESTING) e: int8;
@ifnot(TRUE_FOR_TESTING) f: int16;
g: int16;
h: int32;
}
)",
HasSubstr("aligned"));
}
TEST(Torque, ConstexprLetBindingDoesNotCrash) {
ExpectFailingCompilation(
R"(@export macro FooBar() { let foo = 0; check(foo >= 0); })",
HasSubstr("Use 'const' instead of 'let' for variable 'foo'"));
}
TEST(Torque, FailedImplicitCastFromConstexprDoesNotCrash) {
ExpectFailingCompilation(
R"(
extern enum SomeEnum {
kValue,
...
}
macro Foo() {
Bar(SomeEnum::kValue);
}
macro Bar<T: type>(value: T) {}
)",
HasSubstr(
"Cannot find non-constexpr type corresponding to constexpr kValue"));
}
TEST(Torque, DoubleUnderScorePrefixIllegalForIdentifiers) {
ExpectFailingCompilation(R"(
@export macro Foo() {
let __x;
}
)",
HasSubstr("Lexer Error"));
}
#endif
TEST(Torque, UnusedLetBindingLintError) {
ExpectFailingCompilation(R"(
@export macro Foo(y: Smi) {
let x: Smi = y;
}
)",
HasSubstr("Variable 'x' is never used."));
}
TEST(Torque, UnderscorePrefixSilencesUnusedWarning) {
ExpectSuccessfulCompilation(R"(
@export macro Foo(y: Smi) {
let _x: Smi = y;
}
)");
}
// TODO(almuthanna): This test was skipped because it causes a crash when it is
// ran on Fuchsia. This issue should be solved later on
// Ticket: https://crbug.com/1028617
#if !defined(V8_TARGET_OS_FUCHSIA)
TEST(Torque, UsingUnderscorePrefixedIdentifierError) {
ExpectFailingCompilation(R"(
@export macro Foo(y: Smi) {
let _x: Smi = y;
check(_x == y);
}
)",
HasSubstr("Trying to reference '_x'"));
}
#endif
TEST(Torque, UnusedArgumentLintError) {
ExpectFailingCompilation(R"(
@export macro Foo(x: Smi) {}
)",
HasSubstr("Variable 'x' is never used."));
}
TEST(Torque, UsingUnderscorePrefixedArgumentSilencesWarning) {
ExpectSuccessfulCompilation(R"(
@export macro Foo(_y: Smi) {}
)");
}
TEST(Torque, UnusedLabelLintError) {
ExpectFailingCompilation(R"(
@export macro Foo() labels Bar {}
)",
HasSubstr("Label 'Bar' is never used."));
}
TEST(Torque, UsingUnderScorePrefixLabelSilencesWarning) {
ExpectSuccessfulCompilation(R"(
@export macro Foo() labels _Bar {}
)");
}
TEST(Torque, NoUnusedWarningForImplicitArguments) {
ExpectSuccessfulCompilation(R"(
@export macro Foo(implicit c: Context, r: JSReceiver)() {}
)");
}
TEST(Torque, NoUnusedWarningForVariablesOnlyUsedInAsserts) {
ExpectSuccessfulCompilation(R"(
@export macro Foo(x: bool) {
assert(x);
}
)");
}
// TODO(almuthanna): This test was skipped because it causes a crash when it is
// ran on Fuchsia. This issue should be solved later on
// Ticket: https://crbug.com/1028617
#if !defined(V8_TARGET_OS_FUCHSIA)
TEST(Torque, ImportNonExistentFile) {
ExpectFailingCompilation(R"(import "foo/bar.tq")",
HasSubstr("File 'foo/bar.tq' not found."));
}
#endif
TEST(Torque, LetShouldBeConstLintError) {
ExpectFailingCompilation(R"(
@export macro Foo(y: Smi): Smi {
let x: Smi = y;
return x;
})",
HasSubstr("Variable 'x' is never assigned to."));
}
TEST(Torque, LetShouldBeConstIsSkippedForStructs) {
ExpectSuccessfulCompilation(R"(
struct Foo{ a: Smi; }
@export macro Bar(x: Smi): Foo {
let foo = Foo{a: x};
return foo;
}
)");
}
// TODO(almuthanna): These tests were skipped because they cause a crash when
// they are ran on Fuchsia. This issue should be solved later on
// Ticket: https://crbug.com/1028617
#if !defined(V8_TARGET_OS_FUCHSIA)
TEST(Torque, GenericAbstractType) {
ExpectSuccessfulCompilation(R"(
type Foo<T: type> extends HeapObject;
extern macro F1(HeapObject);
macro F2<T: type>(x: Foo<T>) {
F1(x);
}
@export
macro F3(a: Foo<Smi>, b: Foo<HeapObject>){
F2(a);
F2(b);
}
)");
ExpectFailingCompilation(R"(
type Foo<T: type> extends HeapObject;
macro F1<T: type>(x: Foo<T>) {}
@export
macro F2(a: Foo<Smi>) {
F1<HeapObject>(a);
})",
HasSubstr("cannot find suitable callable"));
ExpectFailingCompilation(R"(
type Foo<T: type> extends HeapObject;
extern macro F1(Foo<HeapObject>);
@export
macro F2(a: Foo<Smi>) {
F1(a);
})",
HasSubstr("cannot find suitable callable"));
}
TEST(Torque, SpecializationRequesters) {
ExpectFailingCompilation(
R"(
macro A<T: type extends HeapObject>() {}
macro B<T: type>() {
A<T>();
}
macro C<T: type>() {
B<T>();
}
macro D() {
C<Smi>();
}
)",
SubstrVector{
SubstrTester("cannot find suitable callable", 4, 7),
SubstrTester("Note: in specialization B<Smi> requested here", 7, 7),
SubstrTester("Note: in specialization C<Smi> requested here", 10,
7)});
ExpectFailingCompilation(
R"(
extern macro RetVal(): Object;
builtin A<T: type extends HeapObject>(implicit context: Context)(): Object {
return RetVal();
}
builtin B<T: type>(implicit context: Context)(): Object {
return A<T>();
}
builtin C<T: type>(implicit context: Context)(): Object {
return B<T>();
}
builtin D(implicit context: Context)(): Object {
return C<Smi>();
}
)",
SubstrVector{
SubstrTester("cannot find suitable callable", 7, 14),
SubstrTester("Note: in specialization B<Smi> requested here", 10, 14),
SubstrTester("Note: in specialization C<Smi> requested here", 13,
14)});
ExpectFailingCompilation(
R"(
struct A<T: type extends HeapObject> {}
struct B<T: type> {
a: A<T>;
}
struct C<T: type> {
b: B<T>;
}
struct D {
c: C<Smi>;
}
)",
SubstrVector{
SubstrTester("Could not instantiate generic", 4, 10),
SubstrTester("Note: in specialization B<Smi> requested here", 7, 10),
SubstrTester("Note: in specialization C<Smi> requested here", 10,
10)});
ExpectFailingCompilation(
R"(
macro A<T: type extends HeapObject>() {}
macro B<T: type>() {
A<T>();
}
struct C<T: type> {
macro Method() {
B<T>();
}
}
macro D(_b: C<Smi>) {}
)",
SubstrVector{
SubstrTester("cannot find suitable callable", 4, 7),
SubstrTester("Note: in specialization B<Smi> requested here", 8, 9),
SubstrTester("Note: in specialization C<Smi> requested here", 11,
5)});
}
#endif
TEST(Torque, Enums) {
ExpectSuccessfulCompilation(R"(
extern enum MyEnum {
kValue0,
kValue1,
kValue2,
kValue3
}
)");
ExpectFailingCompilation(R"(
extern enum MyEmptyEnum {
}
)",
HasSubstr("unexpected token \"}\""));
}
TEST(Torque, EnumInTypeswitch) {
ExpectSuccessfulCompilation(R"(
extern enum MyEnum extends Smi {
kA,
kB,
kC
}
@export
macro Test(implicit context: Context)(v : MyEnum): Smi {
typeswitch(v) {
case (MyEnum::kA | MyEnum::kB): {
return 1;
}
case (MyEnum::kC): {
return 2;
}
}
}
)");
ExpectSuccessfulCompilation(R"(
extern enum MyEnum extends Smi {
kA,
kB,
kC,
...
}
@export
macro Test(implicit context: Context)(v : MyEnum): Smi {
typeswitch(v) {
case (MyEnum::kC): {
return 2;
}
case (MyEnum::kA | MyEnum::kB): {
return 1;
}
case (MyEnum): {
return 0;
}
}
}
)");
ExpectSuccessfulCompilation(R"(
extern enum MyEnum extends Smi {
kA,
kB,
kC,
...
}
@export
macro Test(implicit context: Context)(b: bool): Smi {
return b ? MyEnum::kB : MyEnum::kA;
}
)");
}
TEST(Torque, EnumTypeAnnotations) {
ExpectSuccessfulCompilation(R"(
type Type1 extends intptr;
type Type2 extends intptr;
extern enum MyEnum extends intptr {
kValue1: Type1,
kValue2: Type2,
kValue3
}
@export macro Foo() {
const _a: Type1 = MyEnum::kValue1;
const _b: Type2 = MyEnum::kValue2;
const _c: intptr = MyEnum::kValue3;
}
)");
}
TEST(Torque, ConstClassFields) {
ExpectSuccessfulCompilation(R"(
class Foo extends HeapObject {
const x: int32;
y: int32;
}
@export
macro Test(implicit context: Context)(o: Foo, n: int32) {
const _x: int32 = o.x;
o.y = n;
}
)");
ExpectFailingCompilation(R"(
class Foo extends HeapObject {
const x: int32;
}
@export
macro Test(implicit context: Context)(o: Foo, n: int32) {
o.x = n;
}
)",
HasSubstr("cannot assign to const value"));
ExpectSuccessfulCompilation(R"(
class Foo extends HeapObject {
s: Bar;
}
struct Bar {
const x: int32;
y: int32;
}
@export
macro Test(implicit context: Context)(o: Foo, n: int32) {
const _x: int32 = o.s.x;
// Assigning a struct as a value is OK, even when the struct contains
// const fields.
o.s = Bar{x: n, y: n};
o.s.y = n;
}
)");
ExpectFailingCompilation(R"(
class Foo extends HeapObject {
const s: Bar;
}
struct Bar {
const x: int32;
y: int32;
}
@export
macro Test(implicit context: Context)(o: Foo, n: int32) {
o.s.y = n;
}
)",
HasSubstr("cannot assign to const value"));
ExpectFailingCompilation(R"(
class Foo extends HeapObject {
s: Bar;
}
struct Bar {
const x: int32;
y: int32;
}
@export
macro Test(implicit context: Context)(o: Foo, n: int32) {
o.s.x = n;
}
)",
HasSubstr("cannot assign to const value"));
}
TEST(Torque, References) {
ExpectSuccessfulCompilation(R"(
class Foo extends HeapObject {
const x: int32;
y: int32;
}
@export
macro Test(implicit context: Context)(o: Foo, n: int32) {
const constRefX: const &int32 = &o.x;
const refY: &int32 = &o.y;
const constRefY: const &int32 = refY;
const _x: int32 = *constRefX;
const _y1: int32 = *refY;
const _y2: int32 = *constRefY;
*refY = n;
let r: const &int32 = constRefX;
r = constRefY;
}
)");
ExpectFailingCompilation(R"(
class Foo extends HeapObject {
const x: int32;
y: int32;
}
@export
macro Test(implicit context: Context)(o: Foo) {
const _refX: &int32 = &o.x;
}
)",
HasSubstr("cannot use expression of type const "
"&int32 as a value of type &int32"));
ExpectFailingCompilation(R"(
class Foo extends HeapObject {
const x: int32;
y: int32;
}
@export
macro Test(implicit context: Context)(o: Foo, n: int32) {
const constRefX: const &int32 = &o.x;
*constRefX = n;
}
)",
HasSubstr("cannot assign to const value"));
}
TEST(Torque, CatchFirstHandler) {
ExpectFailingCompilation(
R"(
@export
macro Test() {
try {
} label Foo {
} catch (e) {}
}
)",
HasSubstr(
"catch handler always has to be first, before any label handler"));
}
TEST(Torque, BitFieldLogicalAnd) {
std::string prelude = R"(
bitfield struct S extends uint32 {
a: bool: 1 bit;
b: bool: 1 bit;
c: int32: 5 bit;
}
macro Test(s: S): bool { return
)";
std::string postlude = ";}";
std::string message = "use & rather than &&";
ExpectFailingCompilation(prelude + "s.a && s.b" + postlude,
HasSubstr(message));
ExpectFailingCompilation(prelude + "s.a && !s.b" + postlude,
HasSubstr(message));
ExpectFailingCompilation(prelude + "!s.b && s.c == 34" + postlude,
HasSubstr(message));
}
TEST(Torque, FieldAccessOnNonClassType) {
ExpectFailingCompilation(
R"(
@export
macro Test(x: Number): Map {
return x.map;
}
)",
HasSubstr("map"));
}
TEST(Torque, UnusedImplicit) {
ExpectSuccessfulCompilation(R"(
@export
macro Test1(implicit c: Smi)(a: Object): Object { return a; }
@export
macro Test2(b: Object) { Test1(b); }
)");
ExpectFailingCompilation(
R"(
macro Test1(implicit c: Smi)(_a: Object): Smi { return c; }
@export
macro Test2(b: Smi) { Test1(b); }
)",
HasSubstr("undefined expression of type Smi: the implicit "
"parameter 'c' is not defined when invoking Test1 at"));
ExpectFailingCompilation(
R"(
extern macro Test3(implicit c: Smi)(Object): Smi;
@export
macro Test4(b: Smi) { Test3(b); }
)",
HasSubstr("unititialized implicit parameters can only be passed to "
"Torque-defined macros: the implicit parameter 'c' is not "
"defined when invoking Test3"));
ExpectSuccessfulCompilation(
R"(
macro Test7<T: type>(implicit c: Smi)(o: T): Smi;
Test7<Smi>(implicit c: Smi)(o: Smi): Smi { return o; }
@export
macro Test8(b: Smi) { Test7(b); }
)");
ExpectFailingCompilation(
R"(
macro Test6<T: type>(_o: T): T;
macro Test6<T: type>(implicit c: T)(_o: T): T {
return c;
}
macro Test7<T: type>(o: T): Smi;
Test7<Smi>(o: Smi): Smi { return Test6<Smi>(o); }
@export
macro Test8(b: Smi) { Test7(b); }
)",
HasSubstr("\nambiguous callable : \n Test6(Smi)\ncandidates are:\n "
"Test6(Smi): Smi\n Test6(implicit Smi)(Smi): Smi"));
}
TEST(Torque, ImplicitTemplateParameterInference) {
ExpectSuccessfulCompilation(R"(
macro Foo(_x: Map) {}
macro Foo(_x: Smi) {}
macro GenericMacro<T: type>(implicit x: T)() {
Foo(x);
}
@export
macro Test1(implicit x: Smi)() { GenericMacro(); }
@export
macro Test2(implicit x: Map)() { GenericMacro(); }
)");
ExpectFailingCompilation(
R"(
// Wrap in namespace to avoid redeclaration error.
namespace foo {
macro Foo(implicit x: Map)() {}
}
macro Foo(implicit x: Smi)() {}
namespace foo{
@export
macro Test(implicit x: Smi)() { Foo(); }
}
)",
HasSubstr("ambiguous callable"));
ExpectFailingCompilation(
R"(
// Wrap in namespace to avoid redeclaration error.
namespace foo {
macro Foo(implicit x: Map)() {}
}
macro Foo(implicit x: Smi)() {}
namespace foo{
@export
macro Test(implicit x: Map)() { Foo(); }
}
)",
HasSubstr("ambiguous callable"));
}
} // namespace torque
} // namespace internal
} // namespace v8