blob: f99ede1e7505fa0c616f57cbdce0048630412af6 [file] [log] [blame]
// Copyright (c) 2022, 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:_fe_analyzer_shared/src/type_inference/nullability_suffix.dart';
import 'package:test/test.dart';
import 'mini_types.dart';
main() {
late TypeParameter t;
late TypeParameter u;
late TypeParameter v;
setUp(() {
TypeRegistry.init();
t = TypeRegistry.addTypeParameter('T');
u = TypeRegistry.addTypeParameter('U');
v = TypeRegistry.addTypeParameter('V');
});
tearDown(() {
TypeRegistry.uninit();
});
group('toString:', () {
group('FunctionType:', () {
group('positional parameters:', () {
test('all required', () {
expect(
FunctionType(TypeParameterType(t),
[TypeParameterType(u), TypeParameterType(v)]).toString(),
'T Function(U, V)');
});
test('all optional', () {
expect(
FunctionType(TypeParameterType(t),
[TypeParameterType(u), TypeParameterType(v)],
requiredPositionalParameterCount: 0)
.toString(),
'T Function([U, V])');
});
test('mixed required and optional', () {
expect(
FunctionType(TypeParameterType(t),
[TypeParameterType(u), TypeParameterType(v)],
requiredPositionalParameterCount: 1)
.toString(),
'T Function(U, [V])');
});
});
test('named parameters', () {
expect(
FunctionType(TypeParameterType(t), [], namedParameters: [
NamedFunctionParameter(
isRequired: false, type: TypeParameterType(u), name: 'x'),
NamedFunctionParameter(
isRequired: true, type: TypeParameterType(v), name: 'y')
]).toString(),
'T Function({U x, required V y})');
});
test('positional and named parameters', () {
expect(
FunctionType(TypeParameterType(t), [
TypeParameterType(u)
], namedParameters: [
NamedFunctionParameter(
isRequired: false, type: TypeParameterType(v), name: 'y')
]).toString(),
'T Function(U, {V y})');
});
test('needs parentheses', () {
expect(
TypeParameterType(t, promotion: FunctionType(VoidType.instance, []))
.toString(),
'T&(void Function())');
});
});
group('PrimaryType:', () {
test('simple', () {
expect(TypeParameterType(t).toString(), 'T');
});
test('with arguments', () {
expect(
PrimaryType(TypeRegistry.map,
args: [TypeParameterType(t), TypeParameterType(u)]).toString(),
'Map<T, U>');
});
});
group('PromotedTypeVariableType:', () {
test('basic', () {
expect(TypeParameterType(t, promotion: TypeParameterType(u)).toString(),
'T&U');
});
test('needs parentheses (right)', () {
expect(
TypeParameterType(t,
promotion:
TypeParameterType(u, promotion: TypeParameterType(v)))
.toString(),
'T&(U&V)');
});
test('needs parentheses (question)', () {
expect(
TypeParameterType(t,
promotion: TypeParameterType(u),
nullabilitySuffix: NullabilitySuffix.question)
.toString(),
'(T&U)?');
});
test('needs parentheses (star)', () {
expect(
TypeParameterType(t,
promotion: TypeParameterType(u),
nullabilitySuffix: NullabilitySuffix.star)
.toString(),
'(T&U)*');
});
});
group('QuestionType:', () {
test('basic', () {
expect(
TypeParameterType(t, nullabilitySuffix: NullabilitySuffix.question)
.toString(),
'T?');
});
test('needs parentheses', () {
expect(
TypeParameterType(t,
promotion: TypeParameterType(u,
nullabilitySuffix: NullabilitySuffix.question))
.toString(),
'T&(U?)');
});
});
group('RecordType:', () {
test('no arguments', () {
expect(
RecordType(positionalTypes: [], namedTypes: []).toString(), '()');
});
test('single positional argument', () {
expect(
RecordType(positionalTypes: [TypeParameterType(t)], namedTypes: [])
.toString(),
'(T,)');
});
test('multiple positional arguments', () {
expect(
RecordType(
positionalTypes: [TypeParameterType(t), TypeParameterType(u)],
namedTypes: []).toString(),
'(T, U)');
});
test('single named argument', () {
expect(
RecordType(positionalTypes: [], namedTypes: [
NamedType(name: 't', type: TypeParameterType(t))
]).toString(),
'({T t})');
});
test('multiple named arguments', () {
expect(
RecordType(positionalTypes: [], namedTypes: [
NamedType(name: 't', type: TypeParameterType(t)),
NamedType(name: 'u', type: TypeParameterType(u))
]).toString(),
'({T t, U u})');
});
test('both positional and named arguments', () {
expect(
RecordType(positionalTypes: [
TypeParameterType(t)
], namedTypes: [
NamedType(name: 'u', type: TypeParameterType(u))
]).toString(),
'(T, {U u})');
});
});
group('StarType:', () {
test('basic', () {
expect(
TypeParameterType(t, nullabilitySuffix: NullabilitySuffix.star)
.toString(),
'T*');
});
test('needs parentheses', () {
expect(
TypeParameterType(t,
promotion: TypeParameterType(u,
nullabilitySuffix: NullabilitySuffix.star))
.toString(),
'T&(U*)');
});
});
test('UnknownType', () {
expect(UnknownType().toString(), '_');
});
});
group('parse', () {
var throwsParseError = throwsA(TypeMatcher<ParseError>());
group('primary type:', () {
test('no type args', () {
var type = Type('int') as PrimaryType;
expect(type.name, 'int');
expect(type.args, isEmpty);
});
test('type arg', () {
var type = Type('List<int>') as PrimaryType;
expect(type.name, 'List');
expect(type.args, hasLength(1));
expect(type.args[0].type, 'int');
});
test('type args', () {
var type = Type('Map<int, String>') as PrimaryType;
expect(type.name, 'Map');
expect(type.args, hasLength(2));
expect(type.args[0].type, 'int');
expect(type.args[1].type, 'String');
});
test('invalid type arg separator', () {
expect(() => Type('Map<int) String>'), throwsParseError);
});
test('dynamic', () {
expect(Type('dynamic'), same(DynamicType.instance));
});
test('error', () {
expect(Type('error'), same(InvalidType.instance));
});
test('FutureOr', () {
var type = Type('FutureOr<int>') as FutureOrType;
expect(type.typeArgument.type, 'int');
});
test('Never', () {
expect(Type('Never'), same(NeverType.instance));
});
test('Null', () {
expect(Type('Null'), same(NullType.instance));
});
test('void', () {
expect(Type('void'), same(VoidType.instance));
});
});
test('invalid initial token', () {
expect(() => Type('<'), throwsParseError);
});
test('unknown type', () {
var type = Type('_');
expect(type, TypeMatcher<UnknownType>());
});
test('question type', () {
var type = Type('int?');
expect(type.nullabilitySuffix, NullabilitySuffix.question);
expect(type.withNullability(NullabilitySuffix.none).type, 'int');
});
test('star type', () {
var type = Type('int*');
expect(type.nullabilitySuffix, NullabilitySuffix.star);
expect(type.withNullability(NullabilitySuffix.none).type, 'int');
});
test('promoted type variable', () {
var type = Type('T&int') as TypeParameterType;
expect(type.typeParameter, t);
expect(type.promotion!.type, 'int');
});
test('parenthesized type', () {
var type = Type('(int)');
expect(type.type, 'int');
});
test('invalid token terminating parenthesized type', () {
expect(() => Type('(?<'), throwsParseError);
});
group('function type:', () {
test('no parameters', () {
var type = Type('int Function()') as FunctionType;
expect(type.returnType.type, 'int');
expect(type.positionalParameters, isEmpty);
expect(type.requiredPositionalParameterCount, 0);
expect(type.namedParameters, isEmpty);
});
test('required positional parameter', () {
var type = Type('int Function(String)') as FunctionType;
expect(type.returnType.type, 'int');
expect(type.positionalParameters, hasLength(1));
expect(type.positionalParameters[0].type, 'String');
expect(type.requiredPositionalParameterCount, 1);
expect(type.namedParameters, isEmpty);
});
test('required positional parameters', () {
var type = Type('int Function(String, double)') as FunctionType;
expect(type.returnType.type, 'int');
expect(type.positionalParameters, hasLength(2));
expect(type.positionalParameters[0].type, 'String');
expect(type.positionalParameters[1].type, 'double');
expect(type.requiredPositionalParameterCount, 2);
expect(type.namedParameters, isEmpty);
});
test('optional positional parameter', () {
var type = Type('int Function([String])') as FunctionType;
expect(type.returnType.type, 'int');
expect(type.positionalParameters, hasLength(1));
expect(type.positionalParameters[0].type, 'String');
expect(type.requiredPositionalParameterCount, 0);
expect(type.namedParameters, isEmpty);
});
test('optional positional parameters', () {
var type = Type('int Function([String, double])') as FunctionType;
expect(type.returnType.type, 'int');
expect(type.positionalParameters, hasLength(2));
expect(type.positionalParameters[0].type, 'String');
expect(type.positionalParameters[1].type, 'double');
expect(type.requiredPositionalParameterCount, 0);
expect(type.namedParameters, isEmpty);
});
group('named parameter:', () {
test('not required', () {
var type = Type('int Function({String x})') as FunctionType;
expect(type.returnType.type, 'int');
expect(type.positionalParameters, isEmpty);
expect(type.requiredPositionalParameterCount, 0);
expect(type.namedParameters, hasLength(1));
expect(type.namedParameters[0].isRequired, false);
expect(type.namedParameters[0].type.type, 'String');
expect(type.namedParameters[0].name, 'x');
});
test('required', () {
var type = Type('int Function({required String x})') as FunctionType;
expect(type.returnType.type, 'int');
expect(type.positionalParameters, isEmpty);
expect(type.requiredPositionalParameterCount, 0);
expect(type.namedParameters, hasLength(1));
expect(type.namedParameters[0].isRequired, true);
expect(type.namedParameters[0].type.type, 'String');
expect(type.namedParameters[0].name, 'x');
});
});
test('named parameters', () {
var type = Type('int Function({String x, double y})') as FunctionType;
expect(type.returnType.type, 'int');
expect(type.positionalParameters, isEmpty);
expect(type.requiredPositionalParameterCount, 0);
expect(type.namedParameters, hasLength(2));
expect(type.namedParameters[0].isRequired, false);
expect(type.namedParameters[0].type.type, 'String');
expect(type.namedParameters[0].name, 'x');
expect(type.namedParameters[1].isRequired, false);
expect(type.namedParameters[1].type.type, 'double');
expect(type.namedParameters[1].name, 'y');
});
test('named parameter sorting', () {
var type = Type('int Function({double y, String x})') as FunctionType;
expect(type.returnType.type, 'int');
expect(type.positionalParameters, isEmpty);
expect(type.requiredPositionalParameterCount, 0);
expect(type.namedParameters, hasLength(2));
expect(type.namedParameters[0].isRequired, false);
expect(type.namedParameters[0].type.type, 'String');
expect(type.namedParameters[0].name, 'x');
expect(type.namedParameters[1].isRequired, false);
expect(type.namedParameters[1].type.type, 'double');
expect(type.namedParameters[1].name, 'y');
});
test('invalid parameter separator', () {
expect(() => Type('int Function(String Function()< double)'),
throwsParseError);
});
test('invalid token after Function', () {
expect(() => Type('int Function&)'), throwsParseError);
});
});
group('record type:', () {
test('no fields', () {
var type = Type('()') as RecordType;
expect(type.positionalTypes, isEmpty);
expect(type.namedTypes, isEmpty);
});
test('named field', () {
var type = Type('({int x})') as RecordType;
expect(type.positionalTypes, isEmpty);
expect(type.namedTypes, hasLength(1));
expect(type.namedTypes[0].name, 'x');
expect(type.namedTypes[0].type.type, 'int');
});
test('named field followed by comma', () {
var type = Type('({int x,})') as RecordType;
expect(type.positionalTypes, isEmpty);
expect(type.namedTypes, hasLength(1));
expect(type.namedTypes[0].name, 'x');
expect(type.namedTypes[0].type.type, 'int');
});
test('named field followed by invalid token', () {
expect(() => Type('({int x))'), throwsParseError);
});
test('named field name is not an identifier', () {
expect(() => Type('({int )})'), throwsParseError);
});
test('named fields', () {
var type = Type('({int x, String y})') as RecordType;
expect(type.positionalTypes, isEmpty);
expect(type.namedTypes, hasLength(2));
expect(type.namedTypes[0].name, 'x');
expect(type.namedTypes[0].type.type, 'int');
expect(type.namedTypes[1].name, 'y');
expect(type.namedTypes[1].type.type, 'String');
});
test('curly braces followed by invalid token', () {
expect(() => Type('({int x}&'), throwsParseError);
});
test('curly braces but no named fields', () {
expect(() => Type('({})'), throwsParseError);
});
test('positional field', () {
var type = Type('(int,)') as RecordType;
expect(type.namedTypes, isEmpty);
expect(type.positionalTypes, hasLength(1));
expect(type.positionalTypes[0].type, 'int');
});
group('positional fields:', () {
test('two', () {
var type = Type('(int, String)') as RecordType;
expect(type.namedTypes, isEmpty);
expect(type.positionalTypes, hasLength(2));
expect(type.positionalTypes[0].type, 'int');
expect(type.positionalTypes[1].type, 'String');
});
test('three', () {
var type = Type('(int, String, double)') as RecordType;
expect(type.namedTypes, isEmpty);
expect(type.positionalTypes, hasLength(3));
expect(type.positionalTypes[0].type, 'int');
expect(type.positionalTypes[1].type, 'String');
expect(type.positionalTypes[2].type, 'double');
});
});
test('named and positional fields', () {
var type = Type('(int, {String x})') as RecordType;
expect(type.positionalTypes, hasLength(1));
expect(type.positionalTypes[0].type, 'int');
expect(type.namedTypes, hasLength(1));
expect(type.namedTypes[0].name, 'x');
expect(type.namedTypes[0].type.type, 'String');
});
test('terminated by invalid token', () {
expect(() => Type('(int, String('), throwsParseError);
});
});
group('invalid token:', () {
test('before other tokens', () {
expect(() => Type('#int'), throwsParseError);
});
test('at end', () {
expect(() => Type('int#'), throwsParseError);
});
});
test('extra token after type', () {
expect(() => Type('int)'), throwsParseError);
});
});
group('recursivelyDemote:', () {
group('FunctionType:', () {
group('return type:', () {
test('unchanged', () {
expect(Type('int Function()').recursivelyDemote(covariant: true),
isNull);
expect(Type('int Function()').recursivelyDemote(covariant: false),
isNull);
});
test('covariant', () {
expect(
Type('T&int Function()').recursivelyDemote(covariant: true)!.type,
'T Function()');
});
test('contravariant', () {
expect(
Type('T&int Function()')
.recursivelyDemote(covariant: false)!
.type,
'Never Function()');
});
});
group('positional parameters:', () {
test('unchanged', () {
expect(
Type('void Function(int, String)')
.recursivelyDemote(covariant: true),
isNull);
expect(
Type('void Function(int, String)')
.recursivelyDemote(covariant: false),
isNull);
});
test('covariant', () {
expect(
Type('void Function(T&int, String)')
.recursivelyDemote(covariant: true)!
.type,
'void Function(Never, String)');
});
test('contravariant', () {
expect(
Type('void Function(T&int, String)')
.recursivelyDemote(covariant: false)!
.type,
'void Function(T, String)');
});
});
group('named parameters:', () {
test('unchanged', () {
expect(
Type('void Function({int x, String y})')
.recursivelyDemote(covariant: true),
isNull);
expect(
Type('void Function({int x, String y})')
.recursivelyDemote(covariant: false),
isNull);
});
test('covariant', () {
expect(
Type('void Function({T&int x, String y})')
.recursivelyDemote(covariant: true)!
.type,
'void Function({Never x, String y})');
});
test('contravariant', () {
expect(
Type('void Function({T&int x, String y})')
.recursivelyDemote(covariant: false)!
.type,
'void Function({T x, String y})');
});
});
});
group('NonFunctionType', () {
test('unchanged', () {
expect(Type('int').recursivelyDemote(covariant: true), isNull);
expect(Type('int').recursivelyDemote(covariant: false), isNull);
});
group('type parameters:', () {
test('unchanged', () {
expect(Type('Map<int, String>').recursivelyDemote(covariant: true),
isNull);
expect(Type('Map<int, String>').recursivelyDemote(covariant: false),
isNull);
});
test('covariant', () {
expect(
Type('Map<T&int, String>')
.recursivelyDemote(covariant: true)!
.type,
'Map<T, String>');
});
test('contravariant', () {
expect(
Type('Map<T&int, String>')
.recursivelyDemote(covariant: false)!
.type,
'Map<Never, String>');
});
});
});
group('QuestionType:', () {
test('unchanged', () {
expect(Type('int?').recursivelyDemote(covariant: true), isNull);
expect(Type('int?').recursivelyDemote(covariant: false), isNull);
});
test('covariant', () {
expect(Type('(T&int)?').recursivelyDemote(covariant: true)!.type, 'T?');
});
test('contravariant', () {
// Note: we don't normalize `Never?` to `Null`.
expect(Type('(T&int)?').recursivelyDemote(covariant: false)!.type,
'Never?');
});
});
group('RecordType:', () {
test('unchanged', () {
expect(Type('(int, {double a})').recursivelyDemote(covariant: true),
isNull);
expect(Type('(int, {double a})').recursivelyDemote(covariant: false),
isNull);
});
group('changed:', () {
group('positional:', () {
test('covariant', () {
expect(
Type('(T&int, {double a})')
.recursivelyDemote(covariant: true)!
.type,
'(T, {double a})',
);
});
test('contravariant', () {
expect(
Type('(T&int, {double a})')
.recursivelyDemote(covariant: false)!
.type,
'(Never, {double a})',
);
});
});
group('named:', () {
test('covariant', () {
expect(
Type('(double, {T&int a})')
.recursivelyDemote(covariant: true)!
.type,
'(double, {T a})',
);
});
test('contravariant', () {
expect(
Type('(double, {T&int a})')
.recursivelyDemote(covariant: false)!
.type,
'(double, {Never a})',
);
});
});
});
});
group('StarType:', () {
test('unchanged', () {
expect(Type('int*').recursivelyDemote(covariant: true), isNull);
expect(Type('int*').recursivelyDemote(covariant: false), isNull);
});
test('covariant', () {
expect(Type('(T&int)*').recursivelyDemote(covariant: true)!.type, 'T*');
});
test('contravariant', () {
expect(Type('(T&int)*').recursivelyDemote(covariant: false)!.type,
'Never*');
});
});
test('UnknownType:', () {
expect(Type('_').recursivelyDemote(covariant: true), isNull);
expect(Type('_').recursivelyDemote(covariant: false), isNull);
});
});
group('closureWithRespectToUnknown:', () {
test('UnknownType:', () {
expect(Type('_').closureWithRespectToUnknown(covariant: true)!.type,
'Object?');
expect(Type('_').closureWithRespectToUnknown(covariant: false)!.type,
'Never');
});
group('FunctionType:', () {
group('return type:', () {
test('unchanged', () {
expect(
Type('int Function()')
.closureWithRespectToUnknown(covariant: true),
isNull);
expect(
Type('int Function()')
.closureWithRespectToUnknown(covariant: false),
isNull);
});
test('covariant', () {
expect(
Type('_ Function()')
.closureWithRespectToUnknown(covariant: true)!
.type,
'Object? Function()');
});
test('contravariant', () {
expect(
Type('_ Function()')
.closureWithRespectToUnknown(covariant: false)!
.type,
'Never Function()');
});
});
group('positional parameters:', () {
test('unchanged', () {
expect(
Type('void Function(int, String)')
.closureWithRespectToUnknown(covariant: true),
isNull);
expect(
Type('void Function(int, String)')
.closureWithRespectToUnknown(covariant: false),
isNull);
});
test('covariant', () {
expect(
Type('void Function(_, String)')
.closureWithRespectToUnknown(covariant: true)!
.type,
'void Function(Never, String)');
});
test('contravariant', () {
expect(
Type('void Function(_, String)')
.closureWithRespectToUnknown(covariant: false)!
.type,
'void Function(Object?, String)');
});
});
group('named parameters:', () {
test('unchanged', () {
expect(
Type('void Function({int x, String y})')
.closureWithRespectToUnknown(covariant: true),
isNull);
expect(
Type('void Function({int x, String y})')
.closureWithRespectToUnknown(covariant: false),
isNull);
});
test('covariant', () {
expect(
Type('void Function({_ x, String y})')
.closureWithRespectToUnknown(covariant: true)!
.type,
'void Function({Never x, String y})');
});
test('contravariant', () {
expect(
Type('void Function({_ x, String y})')
.closureWithRespectToUnknown(covariant: false)!
.type,
'void Function({Object? x, String y})');
});
});
});
group('NonFunctionType', () {
test('unchanged', () {
expect(
Type('int').closureWithRespectToUnknown(covariant: true), isNull);
expect(
Type('int').closureWithRespectToUnknown(covariant: false), isNull);
});
group('type parameters:', () {
test('unchanged', () {
expect(
Type('Map<int, String>')
.closureWithRespectToUnknown(covariant: true),
isNull);
expect(
Type('Map<int, String>')
.closureWithRespectToUnknown(covariant: false),
isNull);
});
test('covariant', () {
expect(
Type('Map<_, String>')
.closureWithRespectToUnknown(covariant: true)!
.type,
'Map<Object?, String>');
});
test('contravariant', () {
expect(
Type('Map<_, String>')
.closureWithRespectToUnknown(covariant: false)!
.type,
'Map<Never, String>');
});
});
});
group('QuestionType:', () {
test('unchanged', () {
expect(
Type('int?').closureWithRespectToUnknown(covariant: true), isNull);
expect(
Type('int?').closureWithRespectToUnknown(covariant: false), isNull);
});
test('covariant', () {
expect(Type('_?').closureWithRespectToUnknown(covariant: true)!.type,
'Object?');
});
});
group('RecordType:', () {
test('unchanged', () {
expect(
Type('(int, {double a})')
.closureWithRespectToUnknown(covariant: true),
isNull);
expect(
Type('(int, {double a})')
.closureWithRespectToUnknown(covariant: false),
isNull);
});
group('changed:', () {
group('positional:', () {
test('covariant', () {
expect(
Type('(_, {double a})')
.closureWithRespectToUnknown(covariant: true)!
.type,
'(Object?, {double a})',
);
});
test('contravariant', () {
expect(
Type('(_, {double a})')
.closureWithRespectToUnknown(covariant: false)!
.type,
'(Never, {double a})',
);
});
});
group('named:', () {
test('covariant', () {
expect(
Type('(double, {_ a})')
.closureWithRespectToUnknown(covariant: true)!
.type,
'(double, {Object? a})',
);
});
test('contravariant', () {
expect(
Type('(double, {_ a})')
.closureWithRespectToUnknown(covariant: false)!
.type,
'(double, {Never a})',
);
});
});
});
});
group('StarType:', () {
test('unchanged', () {
expect(
Type('int*').closureWithRespectToUnknown(covariant: true), isNull);
expect(
Type('int*').closureWithRespectToUnknown(covariant: false), isNull);
});
test('covariant', () {
expect(Type('_*').closureWithRespectToUnknown(covariant: true)!.type,
'Object?');
});
});
});
}