ede2740711
This change adds a new abstract type Lazy<T> which can be used to interoperate with CSA code that uses LazyNode. This new type has special code-generation rules because its generated type is not TNode<...> but std::function<TNode<...>()>. Torque code can do nothing with this type except pass it around, but passing it to the CSA function RunLazy is an easy way to execute the std::function and get back a normal value. Torque code can also create Lazy<T> values using the intrinsic function %MakeLazy, which takes the name of a macro as its first parameter, followed by arguments to that macro which will be passed when the LazyNode is evaluated. We use the macro's name because the language doesn't support taking references to macros, and implementing such a feature would be complicated. Bug: v8:7793 Change-Id: I09120960e3492dd51be0d4c57e14ff3826b99262 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2701752 Commit-Queue: Seth Brenith <seth.brenith@microsoft.com> Reviewed-by: Nico Hartmann <nicohartmann@chromium.org> Reviewed-by: Leszek Swirski <leszeks@chromium.org> Cr-Commit-Position: refs/heads/master@{#72964}
966 lines
25 KiB
C++
966 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
|
|
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
|