// Copyright (c) 2019, 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:analyzer/src/error/codes.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';

import '../dart/resolution/context_collection_resolution.dart';

main() {
  defineReflectiveSuite(() {
    defineReflectiveTests(ArgumentTypeNotAssignableTest);
    defineReflectiveTests(ArgumentTypeNotAssignableWithStrictCastsTest);
  });
}

@reflectiveTest
class ArgumentTypeNotAssignableTest extends PubPackageResolutionTest {
  test_ambiguousClassName() async {
    // See dartbug.com/19624
    newFile('$testPackageLibPath/lib2.dart', '''
class _A {}
g(h(_A a)) {}''');
    await assertErrorsInCode('''
import 'lib2.dart';
class _A {}
f() {
  g((_A a) {});
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 42, 9),
    ]);
    // The name _A is private to the library it's defined in, so this is a type
    // mismatch. Furthermore, the error message should mention both _A and the
    // filenames so the user can figure out what's going on.
    String message = result.errors[0].message;
    expect(message.contains("_A"), isTrue);
  }

  test_annotation_namedConstructor() async {
    await assertErrorsInCode('''
class A {
  const A.fromInt(int p);
}
@A.fromInt('0')
main() {}
''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 49, 3),
    ]);
  }

  test_annotation_namedConstructor_generic() async {
    await assertErrorsInCode('''
class A<T> {
  const A.fromInt(T p);
}
@A<int>.fromInt('0')
main() {
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 55, 3),
    ]);
  }

  test_annotation_type_arguments_inferred() async {
    await assertNoErrorsInCode('''
@C([])
int i = 0;

class C<T> {
  const C(List<List<T>> arg);
}
''');
  }

  test_annotation_unnamedConstructor() async {
    await assertErrorsInCode('''
class A {
  const A(int p);
}
@A('0')
main() {
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 33, 3),
    ]);
  }

  test_binary() async {
    await assertErrorsInCode('''
class A {
  operator +(int p) {}
}
f(A a) {
  a + '0';
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 50, 3),
    ]);
  }

  test_binary_eqEq_covariantParameterType() async {
    await assertErrorsInCode(r'''
class A {
  bool operator==(covariant A other) => false;
}

void f(A a, A? aq) {
  a == 0;
  aq == 1;
  aq == aq;
  aq == null;
}
''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 88, 1),
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 99, 1),
    ]);
  }

  test_call() async {
    await assertErrorsInCode('''
typedef bool Predicate<T>(T object);

Predicate<String> f() => (String s) => false;

void main() {
  f().call(3);
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 110, 1),
    ]);
  }

  test_cascadeSecond() async {
    await assertErrorsInCode('''
// filler filler filler filler filler filler filler filler filler filler
class A {
  B ma() { return new B(); }
}
class B {
  mb(String p) {}
}

main() {
  A a = new A();
  a..  ma().mb(0);
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 186, 1),
    ]);
  }

  test_const() async {
    await assertErrorsInCode('''
class A {
  const A(String p);
}
main() {
  const A(42);
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 52, 2),
      error(CompileTimeErrorCode.CONST_CONSTRUCTOR_PARAM_TYPE_MISMATCH, 52, 2),
    ]);
  }

  test_const_super() async {
    await assertErrorsInCode('''
class A {
  const A(String p);
}
class B extends A {
  const B() : super(42);
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 73, 2),
    ]);
  }

  test_downcast() async {
    await assertErrorsInCode(r'''
m() {
  num y = 1;
  n(y);
}
n(int x) {}
''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 23, 1),
    ]);
  }

  test_downcast_nullableNonNullable() async {
    await assertErrorsInCode(r'''
m() {
  int? y;
  n(y);
}
n(int x) {}
''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 20, 1),
    ]);
  }

  test_dynamicCast() async {
    await assertNoErrorsInCode(r'''
m() {
  dynamic i;
  n(i);
}
n(int i) {}
''');
  }

  test_enumConstant() async {
    await assertErrorsInCode(r'''
enum E {
  v(0);
  const E(String a);
}
''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 13, 1),
      error(CompileTimeErrorCode.CONST_CONSTRUCTOR_PARAM_TYPE_MISMATCH, 13, 1),
    ]);
  }

  test_enumConstant_implicitDouble() async {
    await assertNoErrorsInCode(r'''
enum E {
  v(0);
  const E(double a);
}
''');
  }

  test_expressionFromConstructorTearoff_withoutTypeArgs() async {
    await assertNoErrorsInCode('''
class C<T> {
  C(T a);
}

var g = C.new;
var x = g('Hello');
''');
  }

  test_expressionFromConstructorTearoff_withTypeArgs_assignable() async {
    await assertNoErrorsInCode('''
class C<T> {
  C(T a);
}

var g = C<int>.new;
var x = g(0);
''');
  }

  test_expressionFromConstructorTearoff_withTypeArgs_notAssignable() async {
    await assertErrorsInCode('''
class C<T> {
  C(T a);
}

var g = C<int>.new;
var x = g('Hello');
''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 56, 7),
    ]);
  }

  test_expressionFromFunctionTearoff_withoutTypeArgs() async {
    await assertNoErrorsInCode('''
void f<T>(T a) {}

var g = f;
var x = g('Hello');
''');
  }

  test_expressionFromFunctionTearoff_withTypeArgs_assignable() async {
    await assertNoErrorsInCode('''
void f<T>(T a) {}

var g = f<int>;
var x = g(0);
''');
  }

  test_expressionFromFunctionTearoff_withTypeArgs_notAssignable() async {
    await assertErrorsInCode('''
void f<T>(T a) {}

var g = f<int>;
var x = g('Hello');
''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 45, 7),
    ]);
  }

  test_for_element_type_inferred_from_rewritten_node() async {
    // See https://github.com/dart-lang/sdk/issues/39171
    await assertNoErrorsInCode('''
void f<T>(Iterable<T> Function() g, int Function(T) h) {
  [for (var x in g()) if (x is String) h(x)];
}
''');
  }

  test_for_statement_type_inferred_from_rewritten_node() async {
    // See https://github.com/dart-lang/sdk/issues/39171
    await assertNoErrorsInCode('''
void f<T>(Iterable<T> Function() g, void Function(T) h) {
  for (var x in g()) {
    if (x is String) {
      h(x);
    }
  }
}
''');
  }

  test_functionExpressionInvocation_required() async {
    await assertErrorsInCode('''
main() {
  (int x) {} ('');
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 23, 2),
    ]);
  }

  test_functionType() async {
    await assertErrorsInCode(r'''
m() {
  var a = new A();
  a.n(() => 0);
}
class A {
  n(void f(int i)) {}
}
''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 31, 7),
    ]);
  }

  test_implicitCallReference() async {
    await assertNoErrorsInCode('''
class A {
  void call(int p) {}
}
void f(void Function(int) a) {}
void g(A a) {
  f(a);
}
''');
  }

  test_implicitCallReference_named() async {
    await assertNoErrorsInCode('''
class A {
  void call(int p) {}
}
void defaultFunc(int p) {}
void f({void Function(int) a = defaultFunc}) {}
void g(A a) {
  f(a: a);
}
''');
  }

  test_implicitCallReference_namedAndRequired() async {
    await assertNoErrorsInCode('''
class A {
  void call(int p) {}
}
void f({required void Function(int) a}) {}
void g(A a) {
  f(a: a);
}
''');
  }

  test_implicitCallReference_this() async {
    await assertNoErrorsInCode('''
class A {
  void call(int p) {}

  void f(void Function(int) a) {}
  void g() {
    f(this);
  }
}
''');
  }

  test_index_invalidRead() async {
    await assertErrorsInCode('''
class A {
  int operator [](int index) => 0;
}
f(A a) {
  a['0'];
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 60, 3),
    ]);
  }

  test_index_invalidRead_validWrite() async {
    await assertErrorsInCode('''
class A {
  int operator [](int index) => 0;
  operator []=(String index, int value) {}
}
f(A a) {
  a['0'] += 0;
  ++a['0'];
  a['0']++;
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 103, 3),
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 120, 3),
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 130, 3),
    ]);
  }

  test_index_invalidWrite() async {
    await assertErrorsInCode('''
class A {
  operator []=(int index, int value) {}
}
f(A a) {
  a['0'] = 0;
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 65, 3),
    ]);
  }

  test_index_validRead_invalidWrite() async {
    await assertErrorsInCode('''
class A {
  int operator [](String index) => 0;
  operator []=(int index, int value) {}
}
f(A a) {
  a['0'] += 0;
  ++a['0'];
  a['0']++;
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 103, 3),
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 120, 3),
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 130, 3),
    ]);
  }

  test_interfaceType() async {
    await assertErrorsInCode(r'''
m() {
  var i = '';
  n(i);
}
n(int i) {}
''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 24, 1),
    ]);
  }

  test_invocation_callParameter() async {
    await assertErrorsInCode('''
class A {
  call(int p) {}
}
f(A a) {
  a('0');
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 42, 3),
    ]);
  }

  test_invocation_callVariable() async {
    await assertErrorsInCode('''
class A {
  call(int p) {}
}
main() {
  A a = new A();
  a('0');
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 59, 3),
    ]);
  }

  test_invocation_functionParameter() async {
    await assertErrorsInCode('''
a(b(int p)) {
  b('0');
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 18, 3),
    ]);
  }

  test_invocation_functionParameter_generic() async {
    await assertErrorsInCode('''
class A<K, V> {
  m(f(K k), V v) {
    f(v);
  }
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 41, 1),
    ]);
  }

  test_invocation_functionTypes_optional() async {
    await assertErrorsInCode('''
void acceptFunOptBool(void funNumOptBool([bool b])) {}
void funBool(bool b) {}
main() {
  acceptFunOptBool(funBool);
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 107, 7),
    ]);
  }

  test_invocation_functionTypes_optional_method() async {
    await assertErrorsInCode('''
void acceptFunOptBool(void funOptBool([bool b])) {}
class C {
  static void funBool(bool b) {}
}
main() {
  acceptFunOptBool(C.funBool);
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 125, 9),
    ]);
  }

  test_invocation_generic() async {
    await assertErrorsInCode('''
class A<T> {
  m(T t) {}
}
f(A<String> a) {
  a.m(1);
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 50, 1),
    ]);
  }

  test_invocation_named() async {
    await assertErrorsInCode('''
f({String p = ''}) {}
main() {
  f(p: 42);
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 38, 2),
    ]);
  }

  test_invocation_optional() async {
    await assertErrorsInCode('''
f([String p = '']) {}
main() {
  f(42);
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 35, 2),
    ]);
  }

  test_invocation_required() async {
    await assertErrorsInCode('''
f(String p) {}
main() {
  f(42);
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 28, 2),
    ]);
  }

  test_invocation_typedef_generic() async {
    await assertErrorsInCode('''
typedef A<T>(T p);
f(A<int> a) {
  a('1');
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 37, 3),
    ]);
  }

  test_invocation_typedef_local() async {
    await assertErrorsInCode('''
typedef A(int p);
A getA() => throw '';
main() {
  A a = getA();
  a('1');
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 69, 3),
    ]);
  }

  test_invocation_typedef_parameter() async {
    await assertErrorsInCode('''
typedef A(int p);
f(A a) {
  a('1');
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 31, 3),
    ]);
  }

  test_map_indexGet() async {
    // Any type may be passed to Map.operator[].
    await assertNoErrorsInCode('''
main() {
  Map<int, int> m = <int, int>{};
  m['x'];
}
''');
  }

  test_map_indexSet() async {
    // The type passed to Map.operator[]= must match the key type.
    await assertErrorsInCode('''
main() {
  Map<int, int> m = <int, int>{};
  m['x'] = 0;
}
''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 47, 3),
    ]);
  }

  test_map_indexSet_ifNull() async {
    // The type passed to Map.operator[]= must match the key type.
    await assertErrorsInCode('''
main() {
  Map<int, int> m = <int, int>{};
  m['x'] ??= 0;
}
''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 47, 3),
    ]);
  }

  test_new_generic() async {
    await assertErrorsInCode('''
class A<T> {
  A(T p) {}
}
main() {
  new A<String>(42);
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 52, 2),
    ]);
  }

  test_new_optional() async {
    await assertErrorsInCode('''
class A {
  A([String p = '']) {}
}
main() {
  new A(42);
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 53, 2),
    ]);
  }

  test_new_required() async {
    await assertErrorsInCode('''
class A {
  A(String p) {}
}
main() {
  new A(42);
}''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 46, 2),
    ]);
  }

  void test_recordType() async {
    await assertErrorsInCode('''
void f((int a, int b) r) {}

void g() {
  f((a: 1, b: 2));
}
''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 44, 12,
          messageContains: [
            'Expected 2 positional arguments, but got 0 instead.'
          ]),
    ]);
  }

  void test_recordType_namedArguments() async {
    await assertErrorsInCode('''
typedef A = ({
  int b,
  int c,
});

void f(A a){print(a);}

main() {
 f((bb:2, c:3));
}
''', [
      error(
        CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE,
        74,
        11,
        messageContains: ['Unexpected named argument `bb` with type `int`.'],
      ),
    ]);
  }

  void test_recordType_namedArguments_missing() async {
    await assertErrorsInCode('''
typedef A = ({
  int b,
  int c,
});

void f(A a){print(a);}

main() {
 f((b:2));
}
''', [
      error(
        CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE,
        74,
        5,
        messageContains: [
          'Expected 2 named arguments, but got 1 instead.',
        ],
      ),
    ]);
  }

  void test_recordType_positionalArguments() async {
    await assertErrorsInCode('''
typedef A = (
  int b,
  int c,
);

void f(A a){print(a);}

main() {
 f((3, 2, 1));
}
''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 72, 9,
          messageContains: [
            'Expected 2 positional arguments, but got 3 instead.'
          ]),
    ]);
  }

  @failingTest
  test_tearOff_required() async {
    await assertErrorsInCode('''
class C {
  Object/*=T*/ f/*<T>*/(Object/*=T*/ x) => x;
}
g(C c) {
  var h = c.f/*<int>*/;
  print(h('s'));
}
''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 99, 1),
    ]);
  }
}

@reflectiveTest
class ArgumentTypeNotAssignableWithStrictCastsTest
    extends PubPackageResolutionTest with WithStrictCastsMixin {
  test_extensionTypePrimaryConstructor() async {
    await assertErrorsWithStrictCasts('''
extension type E(int i) {}

dynamic a;
var e = E(a);
''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 49, 1),
    ]);
  }

  test_functionCall() async {
    await assertErrorsWithStrictCasts('''
void f(int i) {}
void foo(dynamic a) {
  f(a);
}
''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 43, 1),
    ]);
  }

  test_operator() async {
    await assertErrorsWithStrictCasts('''
void foo(int i, dynamic a) {
  i + a;
}
''', [
      error(CompileTimeErrorCode.ARGUMENT_TYPE_NOT_ASSIGNABLE, 35, 1),
    ]);
  }
}
