ExpressionTypeChecker.java
package com.mackenziehigh.autumn.lang.compiler.compilers;
import autumn.lang.compiler.ast.commons.IExpression;
import autumn.lang.compiler.ast.nodes.*;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IClassType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IConstructor;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IDeclaredType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IExpressionType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IField;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IMethod;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IReferenceType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IReturnType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IVariableType;
import com.mackenziehigh.autumn.lang.compiler.utils.Conversion;
import com.mackenziehigh.autumn.lang.compiler.utils.TypeSystemUtils;
import java.util.Collections;
import java.util.List;
import java.util.Set;
/**
* An instance of this class performs type usage checking on an expression.
*
* @author Mackenzie High
*/
class ExpressionTypeChecker
extends AbstractTypeChecker
{
/**
* This field refers to the standard library method that implements identity-equality.
*/
private final IMethod IDENTITY_EQUALITY;
/**
* This field refers to the standard library method that implements identity-inequality.
*/
private final IMethod IDENTITY_INEQUALITY;
/**
* This object manages the allocation of local variables.
*/
protected final VariableAllocator allocator;
/**
* Essentially, this is the enclosing function that is being compiled.
*/
protected final AbstractFunctionCompiler abstract_function;
/**
* Sole Constructor.
*
* @param function is the function being compiled.
* @param allocator manages the allocation of local variables.
*/
public ExpressionTypeChecker(final AbstractFunctionCompiler function,
final VariableAllocator allocator)
{
super(function);
Preconditions.checkNotNull(function);
Preconditions.checkNotNull(allocator);
this.abstract_function = function;
this.allocator = allocator;
// The Operators class in the standard library proivdes the operator implementations.
final IDeclaredType OPERATORS = program.typesystem.utils.OPERATORS;
// Find the method that implements identity equality.
this.IDENTITY_EQUALITY = TypeSystemUtils.find(OPERATORS.getMethods(),
"identityEquals",
"(Ljava/lang/Object;Ljava/lang/Object;)Z");
// Find the method that implements identity inequality.
this.IDENTITY_INEQUALITY = TypeSystemUtils.find(OPERATORS.getMethods(),
"identityNotEquals",
"(Ljava/lang/Object;Ljava/lang/Object;)Z");
}
/**
* {@inheritDoc}
*/
@Override
public AbstractFunctionCompiler function()
{
return abstract_function;
}
@Override
public void visit(final BooleanDatum object)
{
/**
* Assign a datatype to the literal based on its syntactic form.
*/
infer(object, program.typesystem.utils.PRIMITIVE_BOOLEAN);
}
@Override
public void visit(final CharDatum object)
{
/**
* Ensure that the literal can actually be compiled without losing information.
*/
program.checker.checkLiteral(object, object.getValue().value(), object.getValue().source(), Character.MIN_VALUE, Character.MAX_VALUE);
/**
* Assign a datatype to the literal based on its syntactic form.
*/
infer(object, program.typesystem.utils.PRIMITIVE_CHAR);
}
@Override
public void visit(final ByteDatum object)
{
/**
* Ensure that the literal can actually be compiled without losing information.
*/
program.checker.checkLiteral(object, object.getValue().value(), object.getValue().source(), Byte.MIN_VALUE, Byte.MAX_VALUE);
/**
* Assign a datatype to the literal based on its syntactic form.
*/
infer(object, program.typesystem.utils.PRIMITIVE_BYTE);
}
@Override
public void visit(final ShortDatum object)
{
/**
* Ensure that the literal can actually be compiled without losing information.
*/
program.checker.checkLiteral(object, object.getValue().value(), object.getValue().source(), Short.MIN_VALUE, Short.MAX_VALUE);
/**
* Assign a datatype to the literal based on its syntactic form.
*/
infer(object, program.typesystem.utils.PRIMITIVE_SHORT);
}
@Override
public void visit(final IntDatum object)
{
/**
* Ensure that the literal can actually be compiled without losing information.
*/
program.checker.checkLiteral(object, object.getValue().value(), object.getValue().source(), Integer.MIN_VALUE, Integer.MAX_VALUE);
/**
* Assign a datatype to the literal based on its syntactic form.
*/
infer(object, program.typesystem.utils.PRIMITIVE_INT);
}
@Override
public void visit(final LongDatum object)
{
/**
* Ensure that the literal can actually be compiled without losing information.
*/
program.checker.checkLiteral(object, object.getValue().value(), object.getValue().source(), Long.MIN_VALUE, Long.MAX_VALUE);
/**
* Assign a datatype to the literal based on its syntactic form.
*/
infer(object, program.typesystem.utils.PRIMITIVE_LONG);
}
@Override
public void visit(final FloatDatum object)
{
/**
* Ensure that the literal can actually be compiled without losing information.
*/
program.checker.checkLiteral(object, object.getValue().value(), object.getValue().source(), Float.MIN_VALUE, Float.MAX_VALUE);
infer(object, program.typesystem.utils.PRIMITIVE_FLOAT);
}
@Override
public void visit(final DoubleDatum object)
{
/**
* Ensure that the literal can actually be compiled without losing information.
*/
program.checker.checkLiteral(object, object.getValue().value(), object.getValue().source(), Double.MIN_VALUE, Double.MAX_VALUE);
/**
* Assign a datatype to the literal based on its syntactic form.
*/
infer(object, program.typesystem.utils.PRIMITIVE_DOUBLE);
}
@Override
public void visit(final BigIntegerDatum object)
{
/**
* Assign a datatype to the literal based on its syntactic form.
*/
infer(object, program.typesystem.utils.BIG_INTEGER);
}
@Override
public void visit(final BigDecimalDatum object)
{
/**
* Assign a datatype to the literal based on its syntactic form.
*/
infer(object, program.typesystem.utils.BIG_DECIMAL);
}
@Override
public void visit(final StringDatum object)
{
program.checker.requireWellFormedStringLiteral(object);
/**
* Assign a datatype to the literal based on its syntactic form.
*/
infer(object, program.typesystem.utils.STRING);
}
@Override
public void visit(final NullDatum object)
{
infer(object, program.typesystem.utils.NULL);
}
@Override
public void visit(final VariableDatum object)
{
// The variable must have already been declared.
program.checker.checkDeclared(allocator, object.getVariable());
// The variable must be in-scope.
program.checker.checkScope(allocator, object.getVariable());
// This is the type of the variable based on its declaration.
final IVariableType type = allocator.typeOf(object.getVariable().getName());
// A variable datum returns the value stored in the variable.
// Thus, the datum's type is the type of the variable.
infer(object, type);
}
@Override
public void visit(final ClassDatum object)
{
// Perform type-checking.
// This call will throw an exception, if the type does not exist, etc.
module.imports.resolveReturnType(object.getType());
infer(object, program.typesystem.utils.CLASS);
}
@Override
public void visit(final PrognExpression object)
{
/**
* A progn-expression cannot be empty.
*/
if (object.getElements().isEmpty())
{
program.checker.reportEmptyProgn(object);
}
/**
* Visit and type-check each one of the elements in the sequence.
*/
IExpression last = null;
for (IExpression x : object.getElements())
{
x.accept(this);
last = x;
}
assert last != null;
/**
* The last element must return a value.
*/
program.checker.requireNonVoid(last);
/**
* The return-type of the progn-expression is the return-type of its last element.
*/
final IExpressionType type = program.symbols.expressions.get(last);
infer(object, type);
}
@Override
public void visit(final ListExpression object)
{
// Visit the elements.
object.getElements().accept(this);
// A list-expression returns type java.util.List.
infer(object, program.typesystem.utils.LIST);
// Type-check the elements.
program.checker.requireArguments(object.getElements());
}
@Override
public void visit(final ListComprehensionExpression object)
{
/**
* A list-comprehension returns type java.util.List.
*/
infer(object, program.typesystem.utils.LIST);
/**
* Type-check the iterable.
*/
object.getIterable().accept(this);
program.checker.requireIterable(object.getIterable());
try
{
/**
* A list-comprehension defines a nested scope that covers modifier and condition.
*/
allocator.enterScope();
/**
* Declare the variable and type-check the type.
*/
final Variable variable = object.getVariable();
final IReferenceType type = module.imports.resolveReferenceType(object.getType());
super.declareVar(variable, type, false);
/**
* Type-check the modifier.
*/
object.getModifier().accept(this);
program.checker.requireNonVoid(object.getModifier());
/**
* Type-check the condition.
*/
if (object.getCondition() != null)
{
condition(object.getCondition());
}
}
catch (TypeCheckFailed ex)
{
throw ex;
}
finally
{
/**
* This must always be done; otherwise, the scope management could get messed up.
*/
allocator.exitScope();
}
}
@Override
public void visit(final DispatchExpression object)
{
/**
* Visit and type-check the expressions that produce the arguments.
*/
for (IExpression arg : object.getArguments())
{
arg.accept(this);
}
/**
* This object will be used to perform both type-checking
* and code-generation of the dispatch-expression.
*/
final DispatchCompiler cmp = new DispatchCompiler(object,
module,
allocator,
function().vars,
object.getName().getName(),
object.getArguments().asMutableList());
/**
* Find the overloads that will be included in the dispatch-table.
*/
cmp.resolve();
/**
* Perform type-checking of the dispatch-expression itself.
*/
cmp.check();
/**
* We will need the dispatch-expression compiler later.
*/
program.symbols.dispatches.put(object, cmp);
/**
* The return-type of the expression is Object.
*/
infer(object, program.typesystem.utils.OBJECT);
}
@Override
public void visit(final CallStaticMethodExpression object)
{
/**
* The compilation of static methods is abstracted,
* because that are used for other things also,
* such as operators.
*/
callStaticMethod(object,
object.getOwner(),
object.getName(),
object.getArguments());
}
@Override
public void visit(final SetStaticFieldExpression object)
{
/**
* Resolve the field.
*/
final IField field = findStaticField(object, object.getOwner(), object.getName().getName());
/**
* Visit and type-check the value.
*/
object.getValue().accept(this);
/**
* Ensure that the field is not readonly.
*/
program.checker.requireNonFinalFieldAssignment(object, field);
/**
* Ensure that the value can actually be assigned to the field.
*/
program.checker.checkAssign(object, field.getType(), object.getValue());
/**
* The return-type of the expression is void.
*/
infer(object, program.typesystem.utils.VOID);
}
@Override
public void visit(final GetStaticFieldExpression object)
{
/**
* Resolve the field.
*/
final IField field = findStaticField(object, object.getOwner(), object.getName().getName());
/**
* The return-type of the expression is the type of the field.
*/
infer(object, field.getType());
}
@Override
public void visit(final NewExpression object)
{
/**
* Get the type that is being instantiated.
* This will throw an exception, if the type does not exist or is inaccessible.
*/
final IClassType type = module.imports.resolveClassType(object.getType());
/**
* Visit and type-check the arguments.
*/
final List<IExpressionType> args = Lists.newLinkedList();
for (IExpression arg : object.getArguments())
{
arg.accept(this);
args.add(program.symbols.expressions.get(arg));
}
/**
* Resolve the constructor that will be invoked.
*/
final List<IConstructor> ctors = program.typesystem.utils.resolveCtors(module.type,
type,
args);
/**
* If no applicable constructor exists, issue a warning.
*/
if (ctors.isEmpty())
{
// This will throw an exception.
program.checker.reportNoSuchConstructor(object, type, args);
}
/**
* Remember the resolved constructor, because the code-generator will need it.
*/
final IConstructor ctor = (IConstructor) ctors.get(0);
program.symbols.calls.put(object, ctor);
infer(object, (IExpressionType) type);
}
@Override
public void visit(final CallMethodExpression object)
{
/**
* Visit and type-check the owner expression.
*/
object.getOwner().accept(this);
final IExpressionType type = program.symbols.expressions.get(object.getOwner());
// This will throw an exception, if the type is not a declared-type.
program.checker.requireDeclaredType(object, type);
// This never fails.
final IDeclaredType owner_type = (IDeclaredType) type;
/**
* Visit and type-check the argument expressions.
* At the same time, build a list containing the types of the arguments.
*/
final List<IExpressionType> args = Lists.newLinkedList();
for (IExpression arg : object.getArguments())
{
arg.accept(this);
args.add(program.symbols.expressions.get(arg));
}
/**
* Resolve the method.
*/
final String name = object.getName().getName();
final List<IMethod> methods = program.typesystem.utils.resolveMethods(module.type,
owner_type,
name,
args);
/**
* If no acceptable method overload was found, issue an error.
*/
if (methods.isEmpty())
{
// This will throw an exception.
program.checker.reportNoSuchMethod(object,
false,
owner_type,
object.getName().getName(),
args);
}
/**
* Remember the selected method overload, because the code-generator will need it.
*/
final IMethod method = (IMethod) methods.get(0);
program.symbols.calls.put(object, method);
/**
* The return-type of a call-expression is the return-type
* of the selected method overload.
*/
infer(object, method.getReturnType());
}
@Override
public void visit(final SetFieldExpression object)
{
/**
* Visit the owner expression.
*/
object.getOwner().accept(this);
/**
* Visit the value expression.
*/
object.getValue().accept(this);
/**
* Resolve the field.
*/
final IField field = findField(object, object.getOwner(), object.getName().getName());
/**
* Ensure that the field is not readonly.
*/
program.checker.requireNonFinalFieldAssignment(object, field);
/**
* Ensure that the value can actually be assigned to the field.
*/
program.checker.checkAssign(object, field.getType(), object.getValue());
/**
* The return-type of the expression is void.
*/
infer(object, program.typesystem.utils.VOID);
}
@Override
public void visit(final GetFieldExpression object)
{
/**
* Visit the owner expression.
*/
object.getOwner().accept(this);
/**
* Resolve the field.
*/
final IField field = findField(object, object.getOwner(), object.getName().getName());
/**
* The return-type of the expression is the type of the field.
*/
infer(object, field.getType());
}
@Override
public void visit(final InstanceOfExpression object)
{
/**
* Resolve the type.
*/
final IDeclaredType type = module.imports.resolveDeclaredType(object.getType());
/**
* Visit the value expression.
*/
object.getValue().accept(this);
/**
* Get the type of the value expression.
*/
final IExpressionType value = program.symbols.expressions.get(object.getValue());
/**
* The type of the value expression must be a declared-type.
*/
program.checker.requireDeclaredType(object, value);
/**
* The operation must be viable.
*/
final boolean is_null = value.isNullType();
final boolean case1 = value.isSubtypeOf(type);
final boolean case2 = type.isSubtypeOf(value);
if (is_null || !(case1 || case2))
{
// This will throw an exception.
program.checker.reportNonViableInstanceOf(object, value, type);
}
/**
* The return-type of an instance-of expression is boolean.
*/
infer(object, program.typesystem.utils.PRIMITIVE_BOOLEAN);
}
@Override
public void visit(final TernaryConditionalExpression object)
{
/**
* Visit ant type-check the condition expression.
*/
object.getCondition().accept(this);
program.checker.checkCondition(object.getCondition());
/**
* Visit and type-check the true-case expression.
*/
object.getCaseTrue().accept(this);
// The true-case expression must produce a value.
program.checker.requireArguments(Collections.singleton(object.getCaseTrue()));
/**
* Visit and type-check the false-case expression.
*/
object.getCaseFalse().accept(this);
// The false-case expression must produce a value.
program.checker.requireArguments(Collections.singleton(object.getCaseFalse()));
/**
* The operands must be compatible.
*/
final IExpressionType left = program.symbols.expressions.get(object.getCaseTrue());
final IExpressionType right = program.symbols.expressions.get(object.getCaseFalse());
// The type of one of the operands should be wider than the other.
// For example:
// (if Boolean then String else Object) ==> Object is Wider
// (if Boolean then Object else String) ==> Object is Wider
// Error Case Example:
// (if Boolean then Integer else Long) ==> Neither operand is wider than the other.
// In the error case, this variable will be assigned null.
final IExpressionType widest = program.typesystem.utils.widestType(left, right);
/**
* If neither operand is wider, then issue an error.
*/
if (widest == null)
{
// This will throw an exception.
program.checker.reportIncompatibleOperands(object, left, right);
}
/**
* The return-type is the ternary-conditional-expression is the widest operand type.
*/
infer(object, widest);
}
@Override
public void visit(final LocalsExpression object)
{
/**
* Get the names of the variables that will be captured.
*/
final Set<String> visible = Sets.newTreeSet();
for (String variable : allocator.getVariables())
{
// The variable must be in-scope.
final boolean usable = allocator.isUsable(variable);
// The variable cannot be a temporary.
final boolean non_temp = !allocator.isTemporary(variable);
if (usable && non_temp)
{
visible.add(variable);
}
}
/**
* The code-generator will needs the names of the variables to capture.
*/
program.symbols.locals.put(object, visible);
/**
* The return-type of a locals-expression is always LocalsMap.
*/
infer(object, program.typesystem.utils.LOCALS_MAP);
}
@Override
public void visit(final OnceExpression object)
{
/**
* Visit and type-check the expression that produces the value to memoize.
*/
object.getValue().accept(this);
program.checker.requireReferenceType(object.getValue());
program.checker.requireNonNull(object.getValue());
/**
* The return-type of a once-expression is the type of the memoized value.
*/
infer(object, program.symbols.expressions.get(object.getValue()));
}
@Override
public void visit(final AsOperation object)
{
// Visit the operand.
object.getValue().accept(this);
// Get the type of the operand.
final IExpressionType input = program.symbols.expressions.get(object.getValue());
// Get the output type.
final IReturnType output = module.imports.resolveReturnType(object.getType());
// Obtain a description of the conversion being performed.
// This will be null, if the conversion is impossible.
final Conversion conversion = Conversion.findConversion(program.typesystem, input, output);
/**
* If the conversion is impossible, issue an error.
*/
if (conversion == null)
{
// This call will throw an exception.
program.checker.reportNoSuchAsConversion(object, output);
}
// Remember the conversion, because the code-generator will need it.
program.symbols.conversions.put(object, conversion);
// A conversion operation returns the return-type of the operation.
infer(object, output);
}
@Override
public void visit(final IsOperation object)
{
// Visit the operand.
object.getValue().accept(this);
// Get the type of the operand.
final IExpressionType input = program.symbols.expressions.get(object.getValue());
// Get the output type.
final IReturnType output = module.imports.resolveReturnType(object.getType());
// Obtain a description of the conversion being performed.
// This will be null, if the conversion is impossible.
final Conversion conversion = Conversion.findConversion(program.typesystem, input, output);
/**
* If the conversion is impossible, issue an error.
*/
if (conversion == null)
{
// This call will throw an exception.
program.checker.reportNoSuchIsConversion(object, output);
}
// Remember the conversion, because the code-generator will need it.
program.symbols.conversions.put(object, conversion);
// A conversion operation returns the return-type of the operation.
infer(object, output);
}
@Override
public void visit(final NegateOperation object)
{
// Visit the operand and perform type-checking.
unaryOperation(object,
"negate",
object.getOperand());
}
@Override
public void visit(final NotOperation object)
{
// Visit the operand and perform type-checking.
unaryOperation(object,
"not",
object.getOperand());
}
@Override
public void visit(final DivideOperation object)
{
// Visit the operands and perform type-checking.
binaryOperation(object,
"divide",
object.getLeftOperand(),
object.getRightOperand());
}
@Override
public void visit(final ModuloOperation object)
{
// Visit the operands and perform type-checking.
binaryOperation(object,
"modulo",
object.getLeftOperand(),
object.getRightOperand());
}
@Override
public void visit(final MultiplyOperation object)
{
// Visit the operands and perform type-checking.
binaryOperation(object,
"multiply",
object.getLeftOperand(),
object.getRightOperand());
}
@Override
public void visit(final AddOperation object)
{
// Visit the operands and perform type-checking.
binaryOperation(object,
"add",
object.getLeftOperand(),
object.getRightOperand());
}
@Override
public void visit(final SubtractOperation object)
{
// Visit the operands and perform type-checking.
binaryOperation(object,
"subtract",
object.getLeftOperand(),
object.getRightOperand());
}
@Override
public void visit(final ConcatOperation object)
{
// Visit the operands and perform type-checking.
binaryOperation(object,
"concat",
object.getLeftOperand(),
object.getRightOperand());
}
@Override
public void visit(final LessThanOperation object)
{
// Visit the operands and perform type-checking.
binaryOperation(object,
"lessThan",
object.getLeftOperand(),
object.getRightOperand());
}
@Override
public void visit(final LessThanOrEqualsOperation object)
{
// Visit the operands and perform type-checking.
binaryOperation(object,
"lessThanOrEquals",
object.getLeftOperand(),
object.getRightOperand());
}
@Override
public void visit(final GreaterThanOperation object)
{
// Visit the operands and perform type-checking.
binaryOperation(object,
"greaterThan",
object.getLeftOperand(),
object.getRightOperand());
}
@Override
public void visit(final GreaterThanOrEqualsOperation object)
{
// Visit the operands and perform type-checking.
binaryOperation(object,
"greaterThanOrEquals",
object.getLeftOperand(),
object.getRightOperand());
}
@Override
public void visit(final EqualsOperation object)
{
// Visit the operands and perform type-checking.
binaryOperation(object,
"equals",
object.getLeftOperand(),
object.getRightOperand());
}
@Override
public void visit(final NotEqualsOperation object)
{
// Visit the operands and perform type-checking.
binaryOperation(object,
"notEquals",
object.getLeftOperand(),
object.getRightOperand());
}
@Override
public void visit(final IdentityEqualsOperation object)
{
// Visit the operands.
object.getLeftOperand().accept(this);
object.getRightOperand().accept(this);
// Perform type-checking.
program.checker.requireReferenceType(object.getLeftOperand());
program.checker.requireReferenceType(object.getRightOperand());
// This type of operator always returns boolean.
infer(object, program.typesystem.utils.PRIMITIVE_BOOLEAN);
// Remember the method that is needed to implement this operator.
program.symbols.calls.put(object, IDENTITY_EQUALITY);
}
@Override
public void visit(final IdentityNotEqualsOperation object)
{
// Visit the operands.
object.getLeftOperand().accept(this);
object.getRightOperand().accept(this);
// Perform type-checking.
program.checker.requireReferenceType(object.getLeftOperand());
program.checker.requireReferenceType(object.getRightOperand());
// This type of operator always returns boolean.
infer(object, program.typesystem.utils.PRIMITIVE_BOOLEAN);
// Remember the method that is needed to implement this operator.
program.symbols.calls.put(object, IDENTITY_INEQUALITY);
}
@Override
public void visit(final AndOperation object)
{
// Visit and type-check the operands.
condition(object.getLeftOperand());
condition(object.getRightOperand());
// This type of operator always returns a boolean value.
infer(object, program.typesystem.utils.PRIMITIVE_BOOLEAN);
}
@Override
public void visit(final OrOperation object)
{
// Visit and type-check the operands.
condition(object.getLeftOperand());
condition(object.getRightOperand());
// This type of operator always returns a boolean value.
infer(object, program.typesystem.utils.PRIMITIVE_BOOLEAN);
}
@Override
public void visit(final XorOperation object)
{
// Visit and type-check the operands.
condition(object.getLeftOperand());
condition(object.getRightOperand());
// This type of operator always returns a boolean value.
infer(object, program.typesystem.utils.PRIMITIVE_BOOLEAN);
}
@Override
public void visit(final ImpliesOperation object)
{
// Visit and type-check the operands.
condition(object.getLeftOperand());
condition(object.getRightOperand());
// This type of operator always returns a boolean value.
infer(object, program.typesystem.utils.PRIMITIVE_BOOLEAN);
}
@Override
public void visit(final NullCoalescingOperation object)
{
// Visit the operands.
object.getLeftOperand().accept(this);
object.getRightOperand().accept(this);
// Retrieve the types of the operands.
final IExpressionType left = program.symbols.expressions.get(object.getLeftOperand());
final IExpressionType right = program.symbols.expressions.get(object.getRightOperand());
// Noth operands must be references.
program.checker.requireReferenceType(object.getLeftOperand());
program.checker.requireReferenceType(object.getRightOperand());
// The type of one of the operands should be wider than the other.
// For example:
// (String ?? Object) ==> Object is Wider
// (Object ?? String) ==> Object is Wider
// Error Case Example:
// (Integer ?? Long) ==> Neither operand is wider than the other.
// In the error case, this variable will be assigned null.
final IExpressionType widest = program.typesystem.utils.widestType(left, right);
// If neither operand is wider, then issue an error.
if (widest == null)
{
program.checker.reportIncompatibleOperands(object, left, right);
}
// A null-coalescing operator returns the widest of its operand types.
infer(object, left);
}
}