// 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
