// Copyright (c) 2014, 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_reflective_loader/test_reflective_loader.dart';

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

main() {
  defineReflectiveSuite(() {
    defineReflectiveTests(StaticTypeWarningCodeTest);
    defineReflectiveTests(StrongModeStaticTypeWarningCodeTest);
  });
}

// TODO(srawlins): Figure out what to do with the rest of these tests.
//  The names do not correspond to diagnostic codes, so it isn't clear what
//  they're testing.
@reflectiveTest
class StaticTypeWarningCodeTest extends PubPackageResolutionTest {
  test_await_flattened() async {
    await assertErrorsInCode(
      '''
external Future<Future<int>> ffi();
f() async {
  Future<int> b = await ffi();
}
''',
      [error(WarningCode.unusedLocalVariable, 62, 1)],
    );
  }

  test_await_simple() async {
    await assertErrorsInCode(
      '''
Future<int> fi() => Future.value(0);
f() async {
  String a = await fi(); // Warning: int not assignable to String
}
''',
      [
        error(WarningCode.unusedLocalVariable, 58, 1),
        error(CompileTimeErrorCode.invalidAssignment, 62, 10),
      ],
    );
  }

  test_awaitForIn_declaredVariableRightType() async {
    await assertErrorsInCode(
      '''
f(Stream<int> stream) async {
  await for (int i in stream) {}
}
''',
      [error(WarningCode.unusedLocalVariable, 47, 1)],
    );
  }

  test_awaitForIn_declaredVariableWrongType() async {
    await assertErrorsInCode(
      '''
f(Stream<String> stream) async {
  await for (int i in stream) {}
}
''',
      [
        error(WarningCode.unusedLocalVariable, 50, 1),
        error(CompileTimeErrorCode.forInOfInvalidElementType, 55, 6),
      ],
    );
  }

  test_awaitForIn_downcast() async {
    await assertErrorsInCode(
      '''
f(Stream<num> stream) async {
  await for (int i in stream) {}
}
''',
      [
        error(WarningCode.unusedLocalVariable, 47, 1),
        error(CompileTimeErrorCode.forInOfInvalidElementType, 52, 6),
      ],
    );
  }

  test_awaitForIn_dynamicVariable() async {
    await assertErrorsInCode(
      '''
f(Stream<int> stream) async {
  await for (var i in stream) {}
}
''',
      [error(WarningCode.unusedLocalVariable, 47, 1)],
    );
  }

  test_awaitForIn_existingVariableRightType() async {
    await assertErrorsInCode(
      '''
f(Stream<int> stream) async {
  late int i;
  await for (i in stream) {}
}
''',
      [error(WarningCode.unusedLocalVariable, 41, 1)],
    );
  }

  test_awaitForIn_existingVariableWrongType() async {
    await assertErrorsInCode(
      '''
f(Stream<String> stream) async {
  late int i;
  await for (i in stream) {}
}
''',
      [
        error(WarningCode.unusedLocalVariable, 44, 1),
        error(CompileTimeErrorCode.forInOfInvalidElementType, 65, 6),
      ],
    );
  }

  test_awaitForIn_streamOfDynamic() async {
    await assertErrorsInCode(
      '''
f(Stream stream) async {
  await for (int i in stream) {}
}
''',
      [error(WarningCode.unusedLocalVariable, 42, 1)],
    );
  }

  test_awaitForIn_upcast() async {
    await assertErrorsInCode(
      '''
f(Stream<int> stream) async {
  await for (num i in stream) {}
}
''',
      [error(WarningCode.unusedLocalVariable, 47, 1)],
    );
  }

  test_bug21912() async {
    await assertErrorsInCode(
      '''
class A {}
class B extends A {}

typedef T Function2<S, T>(S z);
typedef B AToB(A x);
typedef A BToA(B x);

void f(
  Function2<Function2<A, B>, Function2<B, A>> t1,
  Function2<AToB, BToA> t2
) {
  {
    Function2<Function2<int, double>, Function2<int, double>> left;

    left = t1;
    left = t2;
  }
}
''',
      [
        error(WarningCode.unusedLocalVariable, 263, 4),
        error(CompileTimeErrorCode.invalidAssignment, 281, 2),
        error(CompileTimeErrorCode.invalidAssignment, 296, 2),
      ],
    );
  }

  test_forIn_declaredVariableRightType() async {
    await assertErrorsInCode(
      '''
f() {
  for (int i in <int>[]) {}
}
''',
      [error(WarningCode.unusedLocalVariable, 17, 1)],
    );
  }

  test_forIn_declaredVariableWrongType() async {
    await assertErrorsInCode(
      '''
f() {
  for (int i in <String>[]) {}
}
''',
      [
        error(WarningCode.unusedLocalVariable, 17, 1),
        error(CompileTimeErrorCode.forInOfInvalidElementType, 22, 10),
      ],
    );
  }

  test_forIn_dynamic() async {
    await assertErrorsInCode(
      '''
f() {
  dynamic d; // Could be [].
  for (var i in d) {}
}
''',
      [error(WarningCode.unusedLocalVariable, 46, 1)],
    );
  }

  test_forIn_dynamicIterable() async {
    await assertErrorsInCode(
      '''
f() {
  dynamic iterable;
  for (int i in iterable) {}
}
''',
      [error(WarningCode.unusedLocalVariable, 37, 1)],
    );
  }

  test_forIn_dynamicVariable() async {
    await assertErrorsInCode(
      '''
f() {
  for (var i in <int>[]) {}
}
''',
      [error(WarningCode.unusedLocalVariable, 17, 1)],
    );
  }

  test_forIn_existingVariableRightType() async {
    await assertErrorsInCode(
      '''
f() {
  int i;
  for (i in <int>[]) {}
}
''',
      [error(WarningCode.unusedLocalVariable, 12, 1)],
    );
  }

  test_forIn_existingVariableWrongType() async {
    await assertErrorsInCode(
      '''
f() {
  int i;
  for (i in <String>[]) {}
}
''',
      [
        error(WarningCode.unusedLocalVariable, 12, 1),
        error(CompileTimeErrorCode.forInOfInvalidElementType, 27, 10),
      ],
    );
  }

  test_forIn_iterableOfDynamic() async {
    await assertErrorsInCode(
      '''
f() {
  for (int i in []) {}
}
''',
      [error(WarningCode.unusedLocalVariable, 17, 1)],
    );
  }

  test_forIn_object() async {
    await assertErrorsInCode(
      '''
f(List o) { // Could be [].
  for (var i in o) {}
}
''',
      [error(WarningCode.unusedLocalVariable, 39, 1)],
    );
  }

  test_forIn_typeBoundBad() async {
    await assertErrorsInCode(
      '''
class Foo<T extends Iterable<int>> {
  void method(T iterable) {
    for (String i in iterable) {}
  }
}
''',
      [
        error(WarningCode.unusedLocalVariable, 81, 1),
        error(CompileTimeErrorCode.forInOfInvalidElementType, 86, 8),
      ],
    );
  }

  test_forIn_typeBoundGood() async {
    await assertErrorsInCode(
      '''
class Foo<T extends Iterable<int>> {
  void method(T iterable) {
    for (var i in iterable) {}
  }
}
''',
      [error(WarningCode.unusedLocalVariable, 78, 1)],
    );
  }

  test_forIn_upcast() async {
    await assertErrorsInCode(
      '''
f() {
  for (num i in <int>[]) {}
}
''',
      [error(WarningCode.unusedLocalVariable, 17, 1)],
    );
  }

  test_typePromotion_booleanAnd_useInRight_accessedInClosureRight_mutated() async {
    await assertErrorsInCode(
      r'''
callMe(f()) { f(); }
f(Object p) {
  (p is String) && callMe(() { p.length; });
  p = 0;
}
''',
      [error(CompileTimeErrorCode.undefinedGetter, 68, 6)],
    );
  }

  test_typePromotion_booleanAnd_useInRight_mutatedInLeft() async {
    await assertErrorsInCode(
      r'''
f(Object p) {
  ((p is String) && ((p = 42) == 42)) && p.length != 0;
}
''',
      [error(CompileTimeErrorCode.undefinedGetter, 57, 6)],
    );
  }

  test_typePromotion_booleanAnd_useInRight_mutatedInRight() async {
    await assertErrorsInCode(
      r'''
f(Object p) {
  (p is String) && (((p = 42) == 42) && p.length != 0);
}
''',
      [error(CompileTimeErrorCode.undefinedGetter, 56, 6)],
    );
  }

  test_typePromotion_conditional_useInThen_accessedInClosure_hasAssignment_after() async {
    await assertErrorsInCode(
      r'''
callMe(f()) { f(); }
g(Object p) {
  p is String ? callMe(() { p.length; }) : 0;
  p = 42;
}
''',
      [error(CompileTimeErrorCode.undefinedGetter, 65, 6)],
    );
  }

  test_typePromotion_conditional_useInThen_accessedInClosure_hasAssignment_before() async {
    await assertErrorsInCode(
      r'''
callMe(f()) { f(); }
g(Object p) {
  p = 42;
  p is String ? callMe(() { p.length; }) : 0;
}
''',
      [error(CompileTimeErrorCode.undefinedGetter, 75, 6)],
    );
  }

  test_typePromotion_if_accessedInClosure_hasAssignment() async {
    await assertErrorsInCode(
      r'''
callMe(f()) { f(); }
f(Object p) {
  if (p is String) {
    callMe(() {
      p.length;
    });
  }
  p = 0;
}
''',
      [error(CompileTimeErrorCode.undefinedGetter, 80, 6)],
    );
  }

  test_typePromotion_if_extends_notMoreSpecific_dynamic() async {
    await assertErrorsInCode(
      r'''
class V {}
class A<T> {}
class B<S> extends A<S> {
  var b;
}

f(A<V> p) {
  if (p is B) {
    p.b;
  }
}
''',
      [error(CompileTimeErrorCode.undefinedGetter, 97, 1)],
    );
  }

  test_typePromotion_if_extends_notMoreSpecific_notMoreSpecificTypeArg() async {
    await assertErrorsInCode(
      r'''
class V {}
class A<T> {}
class B<S> extends A<S> {
  var b;
}

f(A<V> p) {
  if (p is B<int>) {
    p.b;
  }
}
''',
      [error(CompileTimeErrorCode.undefinedGetter, 102, 1)],
    );
  }

  test_typePromotion_if_hasAssignment_before() async {
    await assertErrorsInCode(
      r'''
f(Object p) {
  if (p is String) {
    p = 0;
    p.length;
  }
}
''',
      [error(CompileTimeErrorCode.undefinedGetter, 52, 6)],
    );
  }

  test_typePromotion_if_hasAssignment_inClosure_anonymous_before() async {
    await assertErrorsInCode(
      r'''
f(Object p) {
  () {p = 0;};
  if (p is String) {
    p.length;
  }
}
''',
      [error(CompileTimeErrorCode.undefinedGetter, 56, 6)],
    );
  }

  test_typePromotion_if_hasAssignment_inClosure_function_before() async {
    await assertErrorsInCode(
      r'''
g(Object p) {
  f() {p = 0;};
  if (p is String) {
    p.length;
  }
}
''',
      [
        error(WarningCode.unusedElement, 16, 1),
        error(CompileTimeErrorCode.undefinedGetter, 57, 6),
      ],
    );
  }

  test_typePromotion_if_implements_notMoreSpecific_dynamic() async {
    await assertErrorsInCode(
      r'''
class V {}
class A<T> {}
class B<S> implements A<S> {
  var b;
}

f(A<V> p) {
  if (p is B) {
    p.b;
  }
}
''',
      [error(CompileTimeErrorCode.undefinedGetter, 100, 1)],
    );
  }

  test_typePromotion_if_with_notMoreSpecific_dynamic() async {
    await assertErrorsInCode(
      r'''
class V {}
mixin A<T> {}
class B<S> extends Object with A<S> {
  var b;
}

f(A<V> p) {
  if (p is B) {
    p.b;
  }
}
''',
      [error(CompileTimeErrorCode.undefinedGetter, 109, 1)],
    );
  }

  test_wrongNumberOfTypeArguments() async {
    await assertErrorsInCode(
      r'''
class A<E> {
  late E element;
}
g(A<NoSuchType> a) {
  a.element.anyGetterExistsInDynamic;
}
''',
      [error(CompileTimeErrorCode.nonTypeAsTypeArgument, 37, 10)],
    );
  }
}

@reflectiveTest
class StrongModeStaticTypeWarningCodeTest extends PubPackageResolutionTest {
  test_legalAsyncGeneratorReturnType_function_supertypeOfStream() async {
    await assertNoErrorsInCode('''
f() async* { yield 42; }
dynamic f2() async* { yield 42; }
Object f3() async* { yield 42; }
Stream f4() async* { yield 42; }
Stream<dynamic> f5() async* { yield 42; }
Stream<Object> f6() async* { yield 42; }
Stream<num> f7() async* { yield 42; }
Stream<int> f8() async* { yield 42; }
''');
  }

  test_legalAsyncReturnType_function_supertypeOfFuture() async {
    await assertNoErrorsInCode('''
f() async { return 42; }
dynamic f2() async { return 42; }
Object f3() async { return 42; }
Future f4() async { return 42; }
Future<dynamic> f5() async { return 42; }
Future<Object> f6() async { return 42; }
Future<num> f7() async { return 42; }
Future<int> f8() async { return 42; }
''');
  }

  test_legalSyncGeneratorReturnType_function_supertypeOfIterable() async {
    await assertNoErrorsInCode('''
f() sync* { yield 42; }
dynamic f2() sync* { yield 42; }
Object f3() sync* { yield 42; }
Iterable f4() sync* { yield 42; }
Iterable<dynamic> f5() sync* { yield 42; }
Iterable<Object> f6() sync* { yield 42; }
Iterable<num> f7() sync* { yield 42; }
Iterable<int> f8() sync* { yield 42; }
''');
  }
}
