blob: 38f079207bc18dc1cee0e058cdccfa2ed39dad9e [file] [log] [blame]
#!/usr/bin/env dart
// Copyright (c) 2017, 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 'dart:io';
import 'package:kernel/class_hierarchy.dart';
import 'package:kernel/core_types.dart';
import 'package:kernel/kernel.dart';
import 'package:kernel/type_checker.dart' as type_checker;
import 'package:kernel/type_algebra.dart';
import 'package:kernel/text/ast_to_text.dart';
void main(List<String> args) {
final binary = loadProgramFromBinary(args[0]);
final checker = new TypeChecker(binary)..checkProgram(binary);
if (checker.fails > 0) {
print('------- Reported ${checker.fails} errors -------');
exit(-1);
}
}
class TypeChecker extends type_checker.TypeChecker {
/// Number of fails found.
int fails = 0;
TypeChecker(Program program)
: this._(new CoreTypes(program), new ClosedWorldClassHierarchy(program));
TypeChecker._(CoreTypes coreTypes, ClassHierarchy hierarchy)
: super(coreTypes, hierarchy, strongMode: true, ignoreSdk: false);
// TODO(vegorov) this only gets called for immediate overrides which leads
// to less strict checking that Dart 2.0 specification demands for covariant
// parameters.
@override
void checkOverride(
Class host, Member ownMember, Member superMember, bool isSetter) {
final ownMemberIsFieldOrAccessor =
ownMember is Field || (ownMember as Procedure).isAccessor;
final superMemberIsFieldOrAccessor =
superMember is Field || (superMember as Procedure).isAccessor;
// First check if we are overriding field/accessor with a normal method
// or other way around.
if (ownMemberIsFieldOrAccessor != superMemberIsFieldOrAccessor) {
return _reportInvalidOverride(ownMember, superMember, '''
${ownMember} is a ${_memberKind(ownMember)}
${superMember} is a ${_memberKind(superMember)}
''');
}
if (ownMemberIsFieldOrAccessor) {
if (isSetter) {
final DartType ownType = setterType(host, ownMember);
final DartType superType = setterType(host, superMember);
final isCovariant = ownMember is Field
? ownMember.isCovariant
: ownMember.function.positionalParameters[0].isCovariant;
if (!_isValidParameterOverride(isCovariant, ownType, superType)) {
if (isCovariant) {
return _reportInvalidOverride(ownMember, superMember, '''
${ownType} is neither a subtype nor supertype of ${superType}
''');
} else {
return _reportInvalidOverride(ownMember, superMember, '''
${ownType} is not a subtype of ${superType}
''');
}
}
} else {
final DartType ownType = getterType(host, ownMember);
final DartType superType = getterType(host, superMember);
if (!environment.isSubtypeOf(ownType, superType)) {
return _reportInvalidOverride(ownMember, superMember, '''
${ownType} is not a subtype of ${superType}
''');
}
}
} else {
final msg = _checkFunctionOverride(host, ownMember, superMember);
if (msg != null) {
return _reportInvalidOverride(ownMember, superMember, msg);
}
}
}
void _reportInvalidOverride(
Member ownMember, Member superMember, String message) {
fail(ownMember, '''
Incompatible override of ${superMember} with ${ownMember}:
${_realign(message, ' ')}''');
}
String _memberKind(Member m) {
if (m is Field) {
return 'field';
} else {
final p = m as Procedure;
if (p.isGetter) {
return 'getter';
} else if (p.isSetter) {
return 'setter';
} else {
return 'method';
}
}
}
/// Check if [subtype] is subtype of [supertype] after applying
/// type parameter [substitution].
bool _isSubtypeOf(DartType subtype, DartType supertype) =>
environment.isSubtypeOf(subtype, supertype);
Substitution _makeSubstitutionForMember(Class host, Member member) {
final hostType =
hierarchy.getClassAsInstanceOf(host, member.enclosingClass);
return Substitution.fromSupertype(hostType);
}
/// Check if function node [ownMember] is a valid override for [superMember].
/// Returns [null] if override is valid or an error message.
///
/// Note: this function is a copy of [SubtypeTester._isFunctionSubtypeOf]
/// but it additionally accounts for parameter covariance.
String _checkFunctionOverride(
Class host, Member ownMember, Member superMember) {
final FunctionNode ownFunction = ownMember.function;
final FunctionNode superFunction = superMember.function;
Substitution ownSubstitution = _makeSubstitutionForMember(host, ownMember);
final Substitution superSubstitution =
_makeSubstitutionForMember(host, superMember);
if (ownFunction.requiredParameterCount >
superFunction.requiredParameterCount) {
return 'override has more required parameters';
}
if (ownFunction.positionalParameters.length <
superFunction.positionalParameters.length) {
return 'super method has more positional parameters';
}
if (ownFunction.typeParameters.length !=
superFunction.typeParameters.length) {
return 'methods have different type parameters counts';
}
if (ownFunction.typeParameters.isNotEmpty) {
final typeParameterMap = <TypeParameter, DartType>{};
for (int i = 0; i < ownFunction.typeParameters.length; ++i) {
var subParameter = ownFunction.typeParameters[i];
var superParameter = superFunction.typeParameters[i];
typeParameterMap[subParameter] = new TypeParameterType(superParameter);
}
ownSubstitution = Substitution.combine(
ownSubstitution, Substitution.fromMap(typeParameterMap));
for (int i = 0; i < ownFunction.typeParameters.length; ++i) {
var subParameter = ownFunction.typeParameters[i];
var superParameter = superFunction.typeParameters[i];
var subBound = ownSubstitution.substituteType(subParameter.bound);
if (!_isSubtypeOf(
superSubstitution.substituteType(superParameter.bound), subBound)) {
return 'type parameters have incompatible bounds';
}
}
}
if (!_isSubtypeOf(ownSubstitution.substituteType(ownFunction.returnType),
superSubstitution.substituteType(superFunction.returnType))) {
return 'return type of override ${ownFunction.returnType} is not a subtype'
' of ${superFunction.returnType}';
}
for (int i = 0; i < superFunction.positionalParameters.length; ++i) {
final ownParameter = ownFunction.positionalParameters[i];
final superParameter = superFunction.positionalParameters[i];
if (!_isValidParameterOverride(
ownParameter.isCovariant,
ownSubstitution.substituteType(ownParameter.type),
superSubstitution.substituteType(superParameter.type))) {
return '''
type of parameter ${ownParameter.name} is incompatible
override declares ${ownParameter.type}
super method declares ${superParameter.type}
''';
}
}
if (superFunction.namedParameters.isEmpty) {
return null;
}
// Note: FunctionNode.namedParameters are not sorted so we convert them
// to map to make lookup faster.
final ownParameters = new Map<String, VariableDeclaration>.fromIterable(
ownFunction.namedParameters,
key: (v) => v.name);
for (VariableDeclaration superParameter in superFunction.namedParameters) {
final ownParameter = ownParameters[superParameter.name];
if (ownParameter == null) {
return 'override is missing ${superParameter.name} parameter';
}
if (!_isValidParameterOverride(
ownParameter.isCovariant,
ownSubstitution.substituteType(ownParameter.type),
superSubstitution.substituteType(superParameter.type))) {
return '''
type of parameter ${ownParameter.name} is incompatible
override declares ${ownParameter.type}
super method declares ${superParameter.type}
''';
}
}
return null;
}
/// Checks whether parameter with [ownParameterType] type is a valid override
/// for parameter with [superParameterType] type taking into account its
/// covariance and applying type parameter [substitution] if necessary.
bool _isValidParameterOverride(bool isCovariant, DartType ownParameterType,
DartType superParameterType) {
if (_isSubtypeOf(superParameterType, ownParameterType)) {
return true;
} else if (isCovariant &&
_isSubtypeOf(ownParameterType, superParameterType)) {
return true;
} else {
return false;
}
}
@override
void checkAssignable(TreeNode where, DartType from, DartType to) {
// Note: we permit implicit downcasts.
if (from != to &&
!environment.isSubtypeOf(from, to) &&
!environment.isSubtypeOf(to, from)) {
fail(
where,
'${ansiBlue}${from}${ansiReset} ${ansiYellow}is not assignable to'
'${ansiReset} ${ansiBlue}${to}${ansiReset}');
}
}
@override
void checkUnresolvedInvocation(DartType receiver, TreeNode where) {
if (receiver is DynamicType) {
return;
}
// Permit any invocation on Function type.
if (receiver == environment.rawFunctionType &&
where is MethodInvocation &&
where.name.name == 'call') {
return;
}
fail(where, 'Unresolved method invocation');
}
@override
void fail(TreeNode where, String message) {
fails++;
final context = _findEnclosingMember(where);
String sourceLocation = '<unknown source>';
String sourceLine = null;
// Try finding original source line.
final fileOffset = _findFileOffset(where);
if (fileOffset != TreeNode.noOffset) {
final fileUri = _fileUriOf(context);
final program = context.enclosingProgram;
final source = program.uriToSource[fileUri];
final location = program.getLocation(fileUri, fileOffset);
final lineStart = source.lineStarts[location.line - 1];
final lineEnd = (location.line < source.lineStarts.length)
? source.lineStarts[location.line]
: (source.source.length - 1);
if (lineStart < source.source.length &&
lineEnd < source.source.length &&
lineStart < lineEnd) {
sourceLocation = '${fileUri}:${location.line}';
sourceLine = new String.fromCharCodes(
source.source.getRange(lineStart, lineEnd));
}
}
// Find the name of the enclosing member.
var name = "", body = context;
if (context is Procedure || context is Constructor) {
final parent = context.parent;
final parentName =
parent is Class ? parent.name : (parent as Library).name;
name = "${parentName}::${context.name.name}";
body = context;
} else {
final field = context as Field;
if (where is Field) {
name = "${field.parent}.${field.name}";
} else {
name = "field initializer for ${field.parent}.${field.name}";
}
}
print('''
-----------------------------------------------------------------------
In ${name} at ${sourceLocation}:
${message.replaceAll('\n', '\n ')}
Kernel:
|
| ${_realign(HighlightingPrinter.stringifyContainingLines(body, where))}
|
''');
if (sourceLine != null) {
print('''
Source:
|
| ${_realign(sourceLine)}
|
''');
}
}
static String _fileUriOf(Member context) {
if (context is Procedure) {
return context.fileUri;
} else if (context is Field) {
return context.fileUri;
} else {
final klass = context.enclosingClass;
if (klass != null) {
return klass.fileUri;
}
return context.enclosingLibrary.fileUri;
}
}
static String _realign(String str, [String prefix = '| ']) =>
str.trimRight().replaceAll('\n', '\n${prefix}');
static int _findFileOffset(TreeNode context) {
while (context != null && context.fileOffset == TreeNode.noOffset) {
context = context.parent;
}
return context?.fileOffset ?? TreeNode.noOffset;
}
static Member _findEnclosingMember(TreeNode n) {
var context = n;
while (context is! Member) {
context = context.parent;
}
return context;
}
}
/// Extension of a [Printer] that highlights the given node using ANSI
/// escape sequences.
class HighlightingPrinter extends Printer {
final highlight;
HighlightingPrinter(this.highlight)
: super(new StringBuffer(), syntheticNames: globalDebuggingNames);
@override
bool shouldHighlight(Node node) => highlight == node;
static const kHighlightStart = ansiRed;
static const kHighlightEnd = ansiReset;
@override
void startHighlight(Node node) {
sink.write(kHighlightStart);
}
@override
void endHighlight(Node node) {
sink.write(kHighlightEnd);
}
/// Stringify the given [node] but only return lines that contain string
/// representation of the [highlight] node.
static String stringifyContainingLines(Node node, Node highlight) {
if (node == highlight) {
assert(node is Member);
final firstLine = debugNodeToString(node).split('\n').first;
return "${kHighlightStart}${firstLine}${kHighlightEnd}";
}
final HighlightingPrinter p = new HighlightingPrinter(highlight);
p.writeNode(node);
final String text = p.sink.toString();
return _onlyHighlightedLines(text).join('\n');
}
static Iterable<String> _onlyHighlightedLines(String text) sync* {
for (var line
in text.split('\n').skipWhile((l) => !l.contains(kHighlightStart))) {
yield line;
if (line.contains(kHighlightEnd)) {
break;
}
}
}
}
const ansiBlue = "\u001b[1;34m";
const ansiYellow = "\u001b[1;33m";
const ansiRed = "\u001b[1;31m";
const ansiReset = "\u001b[0;0m";