add Any* classes to reduce cascading errors

This commit is contained in:
Dave Holoway
2020-06-06 11:39:15 +01:00
parent 61b787d5ff
commit 19810a2f75

View File

@@ -941,6 +941,9 @@ function resolveAssignment(tokens, ident, lhs, op, rhs) {
* @param {Local|Parameter|Field|ArrayElement|Value} value * @param {Local|Parameter|Field|ArrayElement|Value} value
*/ */
function checkAssignmentExpression(tokens, variable, op, value) { function checkAssignmentExpression(tokens, variable, op, value) {
if (variable instanceof AnyValue || value instanceof AnyValue) {
return true;
}
if (variable instanceof Value) { if (variable instanceof Value) {
addproblem(tokens, ParseProblem.Error(op, `Invalid assignment: left-hand side is not a variable`)); addproblem(tokens, ParseProblem.Error(op, `Invalid assignment: left-hand side is not a variable`));
return; return;
@@ -1048,8 +1051,12 @@ function isTypeCastable(source_type, cast_type) {
} }
} }
} }
} }
if (source_type instanceof AnyType || cast_type instanceof AnyType) {
return true;
}
return false; return false;
} }
@@ -1061,8 +1068,9 @@ function isTypeAssignable(dest_type, value_type) {
let is_assignable = false; let is_assignable = false;
if (dest_type.typeSignature === value_type.typeSignature) { if (dest_type.typeSignature === value_type.typeSignature) {
is_assignable = true; is_assignable = true;
} } else if (dest_type instanceof AnyType || value_type instanceof AnyType) {
else if (value_type instanceof PrimitiveType) { return true;
} else if (value_type instanceof PrimitiveType) {
const valid_dest_types = { const valid_dest_types = {
I: /^[IJFD]$/, I: /^[IJFD]$/,
J: /^[JFD]$/, J: /^[JFD]$/,
@@ -1119,8 +1127,9 @@ function checkEqualityComparison(tokens, lhs, op, rhs) {
let is_comparable; let is_comparable;
if (lhs.type.typeSignature === rhs.type.typeSignature) { if (lhs.type.typeSignature === rhs.type.typeSignature) {
is_comparable = true; is_comparable = true;
} } else if (lhs.type instanceof AnyType || rhs.type instanceof AnyType) {
else if (lhs.type instanceof PrimitiveType) { is_comparable = true;
} else if (lhs.type instanceof PrimitiveType) {
const valid_rhs_type = { const valid_rhs_type = {
Z: /^Z$/, Z: /^Z$/,
V: /^$/, V: /^$/,
@@ -1170,6 +1179,9 @@ function resolveComparison(tokens, ident, lhs, op, rhs) {
* @param {Local|Parameter|Field|ArrayElement|Value} rhs * @param {Local|Parameter|Field|ArrayElement|Value} rhs
*/ */
function checkOperator(tokens, lhs, op, rhs, re) { function checkOperator(tokens, lhs, op, rhs, re) {
if (lhs.type instanceof AnyType || rhs.type instanceof AnyType) {
return;
}
let is_comparable = re.test(`${lhs.type.typeSignature}${rhs.type.typeSignature}`); let is_comparable = re.test(`${lhs.type.typeSignature}${rhs.type.typeSignature}`);
if (!is_comparable) { if (!is_comparable) {
addproblem(tokens, ParseProblem.Error(op, `Operator ${op.value} cannot be applied to types '${lhs.type.fullyDottedTypeName}' and '${rhs.type.fullyDottedTypeName}'`)); addproblem(tokens, ParseProblem.Error(op, `Operator ${op.value} cannot be applied to types '${lhs.type.fullyDottedTypeName}' and '${rhs.type.fullyDottedTypeName}'`));
@@ -1534,14 +1546,7 @@ function arrayElementOrConstructor(tokens, open_array, matches, index) {
*/ */
function methodCallExpression(tokens, instance, call_arguments, typemap) { function methodCallExpression(tokens, instance, call_arguments, typemap) {
const ident = `${instance.source}(${call_arguments.map(arg => arg.source).join(',')})`; const ident = `${instance.source}(${call_arguments.map(arg => arg.source).join(',')})`;
// to keep this simple for now, only resolve if there is exactly one variable for each argument
for (let arg of call_arguments) {
switch(arg.variables.length) {
case 0:
return new ResolvedIdent(ident);
default: continue;
}
}
// method call resolving is painful in Java - we need to match arguments against // method call resolving is painful in Java - we need to match arguments against
// possible types in the call, but this must include matching against inherited types and choosing the // possible types in the call, but this must include matching against inherited types and choosing the
// most-specific match // most-specific match
@@ -1581,6 +1586,9 @@ function methodCallExpression(tokens, instance, call_arguments, typemap) {
* @param {string[][]} arg_type_signatures * @param {string[][]} arg_type_signatures
*/ */
function isCallCompatible(m, arg_type_signatures) { function isCallCompatible(m, arg_type_signatures) {
if (m instanceof AnyMethod) {
return true;
}
if (m.parameterCount !== arg_type_signatures.length) { if (m.parameterCount !== arg_type_signatures.length) {
return; return;
} }
@@ -1744,7 +1752,10 @@ function arrayTypeExpression(matches) {
* @param {Map<string,JavaType>} typemap * @param {Map<string,JavaType>} typemap
*/ */
function parseDottedIdent(matches, tokens, typemap) { function parseDottedIdent(matches, tokens, typemap) {
let variables = [], methods = [], types = [], package_name = ''; let variables = [],
methods = [],
types = [],
package_name = '';
const qualified_ident = `${matches.source}.${tokens.current.value}`; const qualified_ident = `${matches.source}.${tokens.current.value}`;
switch (tokens.current.value) { switch (tokens.current.value) {
@@ -1752,7 +1763,10 @@ function parseDottedIdent(matches, tokens, typemap) {
// e.g int.class // e.g int.class
// convert the types to Class instances // convert the types to Class instances
tokens.inc(); tokens.inc();
variables = matches.types.map(t => new Value(qualified_ident, signatureToType(`Ljava/lang/Class<${t.typeSignature}>;`, typemap))); variables = matches.types.map(t => {
const type_signature = t instanceof AnyType ? '' : `<${t.typeSignature}>`
return new Value(qualified_ident, signatureToType(`Ljava/lang/Class${type_signature};`, typemap));
});
return new ResolvedIdent(qualified_ident, variables); return new ResolvedIdent(qualified_ident, variables);
case 'this': case 'this':
// e.g Type.this - it must be an enclosing type // e.g Type.this - it must be an enclosing type
@@ -1776,6 +1790,15 @@ function parseDottedIdent(matches, tokens, typemap) {
}); });
/** @type {JavaType[]} */ /** @type {JavaType[]} */
matches.types.forEach(t => { matches.types.forEach(t => {
// if there is an AnyType, then add a type, variable and method
// - this prevents multiple errors in dotted values/
// e.g R.layout.name wiil only error once (on R), not on all 3 idents
if (t instanceof AnyType) {
types.push(new AnyType(qualified_ident));
variables.push(new AnyValue(qualified_ident));
methods.push(new AnyMethod(tokens.current.value));
return;
}
if (t instanceof CEIType) { if (t instanceof CEIType) {
const enclosed_type_signature = `${t.shortSignature}$${tokens.current.value}`; const enclosed_type_signature = `${t.shortSignature}$${tokens.current.value}`;
const enc_type = typemap.get(enclosed_type_signature); const enc_type = typemap.get(enclosed_type_signature);
@@ -1803,8 +1826,10 @@ function parseDottedIdent(matches, tokens, typemap) {
} }
} }
const match = new ResolvedIdent(qualified_ident, variables, methods, types, package_name);
checkIdentifierFound(tokens, tokens.current.value, match);
tokens.inc(); tokens.inc();
return new ResolvedIdent(qualified_ident, variables, methods, types, package_name); return match;
} }
/** /**
@@ -1837,13 +1862,26 @@ function parseDottedIdent(matches, tokens, typemap) {
* @param {Map<string,JavaType>} typemap * @param {Map<string,JavaType>} typemap
*/ */
function resolveIdentifier(tokens, locals, method, imports, typemap) { function resolveIdentifier(tokens, locals, method, imports, typemap) {
const matches = findIdentifier(tokens.current.value, locals, method, imports, typemap); const ident = tokens.current.value;
if (!matches.variables[0] && !matches.methods[0] && !matches.types[0] && !matches.package_name) { const matches = findIdentifier(ident, locals, method, imports, typemap);
addproblem(tokens, ParseProblem.Error(tokens.current, `Undeclared identifier: ${tokens.current.value}`)) checkIdentifierFound(tokens, ident, matches);
}
return matches; return matches;
} }
/**
* @param {TokenList} tokens
* @param {ResolvedIdent} matches
*/
function checkIdentifierFound(tokens, ident, matches) {
if (!matches.variables[0] && !matches.methods[0] && !matches.types[0] && !matches.package_name) {
addproblem(tokens, ParseProblem.Error(tokens.current, `Unresolved identifier: ${matches.source}`));
// pretend it matches everything
matches.variables = [new AnyValue(matches.source)];
matches.methods = [new AnyMethod(ident)];
matches.types = [new AnyType(matches.source)];
}
}
/** /**
* @param {string} ident * @param {string} ident
* @param {Local[]} locals * @param {Local[]} locals
@@ -1945,6 +1983,45 @@ function resolveTypeOrPackage(ident, scoped_type, imports, typemap) {
} }
} }
/**
* AnyType is a special type that's used to fill in types that are missing.
* To prevent cascading errors, AnyType should be fully assign/case/type-compatible
* with any other type
*/
class AnyType extends JavaType {
/**
*
* @param {String} label
*/
constructor(label) {
super("class", [], '');
super.simpleTypeName = label;
}
static Instance = new AnyType('');
get rawTypeSignature() {
return 'U';
}
get typeSignature() {
return 'U';
}
}
class AnyMethod extends Method {
/**
* @param {string} name
*/
constructor(name) {
super(name, [], '');
}
get returnType() {
return AnyType.Instance;
}
}
class Local { class Local {
/** /**
* @param {Token[]} modifiers * @param {Token[]} modifiers
@@ -2003,6 +2080,12 @@ class Value {
} }
} }
class AnyValue extends Value {
constructor(name) {
super(name, AnyType.Instance);
}
}
class LiteralValue extends Value { } class LiteralValue extends Value { }
class MethodCall extends Value { class MethodCall extends Value {