| // 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; |
| |
| type Tagged generates 'TNode<Object>' constexpr 'ObjectPtr'; |
| type Smi extends Tagged generates 'TNode<Smi>' constexpr 'Smi'; |
| |
| @abstract |
| extern class HeapObject extends Tagged { |
| map: Map; |
| } |
| type Map extends HeapObject generates 'TNode<Map>'; |
| type Object = Smi | HeapObject; |
| 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 Code extends HeapObject generates 'TNode<Code>'; |
| type BuiltinPtr extends Smi generates 'TNode<BuiltinPtr>'; |
| type Context extends HeapObject generates 'TNode<Context>'; |
| type NativeContext extends Context; |
| )"; |
| |
| 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> |
| void ExpectFailingCompilation( |
| std::string source, ::testing::PolymorphicMatcher<T> message_pattern) { |
| TorqueCompilerResult result = TestCompileTorque(std::move(source)); |
| ASSERT_FALSE(result.messages.empty()); |
| EXPECT_THAT(result.messages[0].message, message_pattern); |
| } |
| |
| } // 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; |
| )"); |
| } |
| |
| 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, DoubleUnderScorePrefixIllegalForIdentifiers) { |
| ExpectFailingCompilation(R"( |
| @export macro Foo() { |
| let __x; |
| } |
| )", |
| HasSubstr("Lexer Error")); |
| } |
| |
| 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; |
| } |
| )"); |
| } |
| |
| TEST(Torque, UsingUnderscorePrefixedIdentifierError) { |
| ExpectFailingCompilation(R"( |
| @export macro Foo(y: Smi) { |
| let _x: Smi = y; |
| check(_x == y); |
| } |
| )", |
| HasSubstr("Trying to reference '_x'")); |
| } |
| |
| 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); |
| } |
| )"); |
| } |
| |
| TEST(Torque, ImportNonExistentFile) { |
| ExpectFailingCompilation(R"(import "foo/bar.tq")", |
| HasSubstr("File 'foo/bar.tq' not found.")); |
| } |
| |
| 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; |
| } |
| )"); |
| } |
| |
| } // namespace torque |
| } // namespace internal |
| } // namespace v8 |