// 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_reflective_loader/test_reflective_loader.dart';

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

main() {
  defineReflectiveSuite(() {
    defineReflectiveTests(TypeAliasCannotReferenceItselfTest);
  });
}

@reflectiveTest
class TypeAliasCannotReferenceItselfTest extends PubPackageResolutionTest {
  test_functionTypeAlias_typeParameterBounds() async {
    await assertErrorsInCode(
      '''
typedef A<T extends A<int>>();
''',
      [error(CompileTimeErrorCode.typeAliasCannotReferenceItself, 8, 1)],
    );
  }

  test_functionTypedParameter_returnType() async {
    await assertErrorsInCode(
      '''
typedef A(A b());
''',
      [error(CompileTimeErrorCode.typeAliasCannotReferenceItself, 8, 1)],
    );
  }

  test_generic() async {
    await assertErrorsInCode(
      r'''
typedef F = void Function(List<G> l);
typedef G = void Function(List<F> l);
main() {
  F? foo(G? g) => g;
  foo(null);
}
''',
      [
        error(CompileTimeErrorCode.typeAliasCannotReferenceItself, 8, 1),
        error(CompileTimeErrorCode.typeAliasCannotReferenceItself, 46, 1),
      ],
    );
  }

  test_genericTypeAlias_typeParameterBounds() async {
    await assertErrorsInCode(
      '''
typedef A<T extends A<int>> = void Function();
''',
      [error(CompileTimeErrorCode.typeAliasCannotReferenceItself, 8, 1)],
    );
  }

  test_infiniteParameterBoundCycle() async {
    await assertErrorsInCode(
      r'''
typedef F<X extends F<X>> = F Function();
''',
      [error(CompileTimeErrorCode.typeAliasCannotReferenceItself, 8, 1)],
    );
  }

  test_issue11987() async {
    await assertErrorsInCode(
      r'''
typedef void F(List<G> l);
typedef void G(List<F> l);
main() {
  F? foo(G? g) => g;
  foo(null);
}
''',
      [
        error(CompileTimeErrorCode.typeAliasCannotReferenceItself, 13, 1),
        error(CompileTimeErrorCode.typeAliasCannotReferenceItself, 40, 1),
      ],
    );
  }

  test_issue19459() async {
    // A complex example involving multiple classes.  This is legal, since
    // typedef F references itself only via a class.
    await assertNoErrorsInCode(r'''
class A<B, C> {}
abstract class D {
  f(E e);
}
abstract class E extends A<dynamic, F> {}
typedef D F();
''');
  }

  test_nonFunction_aliasedType_cycleOf2() async {
    await assertErrorsInCode(
      '''
typedef T1 = T2;
typedef T2 = T1;
''',
      [
        error(CompileTimeErrorCode.typeAliasCannotReferenceItself, 8, 2),
        error(CompileTimeErrorCode.typeAliasCannotReferenceItself, 25, 2),
      ],
    );
  }

  test_nonFunction_aliasedType_directly_functionWithIt() async {
    await assertErrorsInCode(
      '''
typedef T = void Function(T);
''',
      [error(CompileTimeErrorCode.typeAliasCannotReferenceItself, 8, 1)],
    );
  }

  test_nonFunction_aliasedType_directly_it_none() async {
    await assertErrorsInCode(
      '''
typedef T = T;
''',
      [error(CompileTimeErrorCode.typeAliasCannotReferenceItself, 8, 1)],
    );
  }

  test_nonFunction_aliasedType_directly_it_question() async {
    await assertErrorsInCode(
      '''
typedef T = T?;
''',
      [error(CompileTimeErrorCode.typeAliasCannotReferenceItself, 8, 1)],
    );
  }

  test_nonFunction_aliasedType_directly_ListOfIt() async {
    await assertErrorsInCode(
      '''
typedef T = List<T>;
''',
      [error(CompileTimeErrorCode.typeAliasCannotReferenceItself, 8, 1)],
    );
  }

  test_nonFunction_typeParameterBounds() async {
    await assertErrorsInCode(
      '''
typedef T<X extends T<Never>> = List<X>;
''',
      [error(CompileTimeErrorCode.typeAliasCannotReferenceItself, 8, 1)],
    );
  }

  test_parameterType_named() async {
    await assertErrorsInCode(
      '''
typedef A({A a});
''',
      [error(CompileTimeErrorCode.typeAliasCannotReferenceItself, 8, 1)],
    );
  }

  test_parameterType_positional() async {
    await assertErrorsInCode(
      '''
typedef A([A a]);
''',
      [error(CompileTimeErrorCode.typeAliasCannotReferenceItself, 8, 1)],
    );
  }

  test_parameterType_required() async {
    await assertErrorsInCode(
      '''
typedef A(A a);
''',
      [error(CompileTimeErrorCode.typeAliasCannotReferenceItself, 8, 1)],
    );
  }

  test_parameterType_typeArgument() async {
    await assertErrorsInCode(
      '''
typedef A(List<A> a);
''',
      [error(CompileTimeErrorCode.typeAliasCannotReferenceItself, 8, 1)],
    );
  }

  test_referencesReturnType_inTypeAlias() async {
    await assertNoErrorsInCode(r'''
typedef B A();
class B {
  A? a;
}
''');
  }

  test_returnClass_withTypeAlias() async {
    // A typedef is allowed to indirectly reference itself via a class.
    await assertNoErrorsInCode(r'''
typedef C A();
typedef A B();
class C {
  B? a;
}
''');
  }

  test_returnType() async {
    await assertErrorsInCode(
      '''
typedef A A();
''',
      [error(CompileTimeErrorCode.typeAliasCannotReferenceItself, 10, 1)],
    );
  }

  test_returnType_indirect() async {
    await assertErrorsInCode(
      r'''
typedef B A();
typedef A B();
''',
      [
        error(CompileTimeErrorCode.typeAliasCannotReferenceItself, 10, 1),
        error(CompileTimeErrorCode.typeAliasCannotReferenceItself, 25, 1),
      ],
    );
  }

  test_usingRecordType_directly() async {
    await assertErrorsInCode(
      r'''
typedef F = (F, int) Function();
''',
      [error(CompileTimeErrorCode.typeAliasCannotReferenceItself, 8, 1)],
    );
  }
}
