StatementTypeChecker.java
package com.mackenziehigh.autumn.lang.compiler.compilers;
import autumn.lang.compiler.ast.commons.IExpression;
import autumn.lang.compiler.ast.commons.IStatement;
import autumn.lang.compiler.ast.nodes.*;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IClassType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IExpressionType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IReferenceType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IVariableType;
import com.mackenziehigh.autumn.lang.compiler.utils.DelegateToHandler;
import com.mackenziehigh.autumn.lang.compiler.utils.TopoSorter;
import java.util.List;
import java.util.Set;
/**
* An instance of this class performs type usage checking on a statement.
*
* @author Mackenzie High
*/
final class StatementTypeChecker
extends ExpressionTypeChecker
{
private int loop_nesting_level = 0;
private final FunctionCompiler function;
public StatementTypeChecker(final FunctionCompiler function)
{
super(function, function.allocator);
this.function = function;
}
@Override
public void visit(final IfStatement object)
{
// Visit the conditional case that is always present.
object.getMainCase().accept(this);
// Visit the elif-cases, if there are any elif-clauses present.
if (object.getElifCases() != null)
{
for (ConditionalCase x : object.getElifCases())
{
x.accept(this);
}
}
// Visit the else-clause, if there is an else-clause present.
if (object.getElseCase() != null)
{
object.getElseCase().accept(this);
}
}
@Override
public void visit(final ConditionalCase object)
{
// Visit and type-check the condition.
condition(object.getCondition());
// Visit the body.
object.getBody().accept(this);
}
@Override
public void visit(final WhenStatement object)
{
// Visit and type-check the condition.
condition(object.getCondition());
// Visit the body.
object.getBody().accept(this);
}
@Override
public void visit(final GotoStatement object)
{
/**
* The checking of the labels will be done later.
* This prevents the need for another compiler-pass.
*/
function.labels.defer(object);
}
@Override
public void visit(final MarkerStatement object)
{
/**
* The checking of the labels will be done later.
* This prevents the need for another compiler-pass.
*/
function.labels.defer(object);
}
@Override
public void visit(final BranchStatement object)
{
/**
* The checking of the labels will be done later.
* This prevents the need for another compiler-pass.
*/
function.labels.defer(object);
/**
* However, we can check the index now.
*/
object.getIndex().accept(this);
program.checker.requireInteger(object.getIndex());
}
@Override
public void visit(final ForeverStatement object)
{
/**
* Visit the body.
*/
++loop_nesting_level;
{
object.getBody().accept(this);
}
--loop_nesting_level;
}
@Override
public void visit(final WhileStatement object)
{
/**
* Visit and type-check the condition.
*/
condition(object.getCondition());
/**
* Visit the body.
*/
++loop_nesting_level;
{
object.getBody().accept(this);
}
--loop_nesting_level;
}
@Override
public void visit(final UntilStatement object)
{
/**
* Visit and type-check the condition.
*/
condition(object.getCondition());
/**
* Visit the body.
*/
++loop_nesting_level;
{
object.getBody().accept(this);
}
--loop_nesting_level;
}
@Override
public void visit(final DoWhileStatement object)
{
/**
* Visit and type-check the condition.
*/
condition(object.getCondition());
/**
* Visit the body.
*/
++loop_nesting_level;
{
object.getBody().accept(this);
}
--loop_nesting_level;
}
@Override
public void visit(final DoUntilStatement object)
{
/**
* Visit and type-check the condition.
*/
condition(object.getCondition());
/**
* Visit the body.
*/
++loop_nesting_level;
{
object.getBody().accept(this);
}
--loop_nesting_level;
}
@Override
public void visit(final ForStatement object)
{
/**
* Visit and type-check the initializer.
*
* Warning: Do *not* move this code into the following try-catch.
*/
object.getInitializer().accept(this);
program.checker.requireInteger(object.getInitializer());
try
{
/**
* A for-loop defines a nested scope that covers the condition, modifier, and body.
*/
allocator.enterScope();
/**
* Declare the control variable.
*/
super.declareVar(object.getVariable(), program.typesystem.utils.PRIMITIVE_INT, false);
/**
* Visit and type-check the condition.
*/
condition(object.getCondition());
/**
* Visit and type-check the modifier.
*/
object.getNext().accept(this);
program.checker.requireInteger(object.getNext());
/**
* Visit the body.
*/
++loop_nesting_level;
{
object.getBody().accept(this);
}
--loop_nesting_level;
}
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 ForeachStatement object)
{
/**
* Visit and type-check the iterable.
*
* Warning: Do *not* move this code into the following try-catch.
*/
object.getIterable().accept(this);
program.checker.requireIterable(object.getIterable());
try
{
/**
* A foreach-loop defines a nested scope that covers body.
*/
allocator.enterScope();
/**
* Declare the variable and type-check the type.
*/
final Variable variable = object.getVariable();
final IReferenceType type = function.module.imports.resolveReferenceType(object.getType());
super.declareVar(variable, type, false);
/**
* Visit the body.
*/
++loop_nesting_level;
{
object.getBody().accept(this);
}
--loop_nesting_level;
}
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 BreakStatement object)
{
/**
* Ensure that the break-statement is actually inside of a loop.
*/
if (loop_nesting_level == 0)
{
program.checker.reportBreakOutsideOfLoop(object);
}
}
@Override
public void visit(final ContinueStatement object)
{
/**
* Ensure that the continue-statement is actually inside of a loop.
*/
if (loop_nesting_level == 0)
{
program.checker.reportContinueOutsideOfLoop(object);
}
}
@Override
public void visit(final RedoStatement object)
{
/**
* Ensure that the redo-statement is actually inside of a loop.
*/
if (loop_nesting_level == 0)
{
program.checker.reportRedoOutsideOfLoop(object);
}
}
@Override
public void visit(final VarStatement object)
{
/**
* Visit and type-check the expression.
*/
object.getValue().accept(this);
/**
* Declare the assignee.
*/
final Variable assignee = object.getVariable();
final IExpression value = object.getValue();
final IExpressionType type = program.symbols.expressions.get(value);
super.declareVar(assignee, type, true);
}
@Override
public void visit(final ValStatement object)
{
/**
* Visit and type-check the expression.
*/
object.getValue().accept(this);
/**
* Declare the assignee.
*/
final Variable assignee = object.getVariable();
final IExpression value = object.getValue();
final IExpressionType type = program.symbols.expressions.get(value);
super.declareVar(assignee, type, false);
}
@Override
public void visit(final LetStatement object)
{
/**
* Generate the bytecode that produces the value.
*/
object.getValue().accept(this);
/**
* The variable must have been declared somewhere.
*/
program.checker.checkDeclared(function.allocator, object.getVariable());
/**
* The variable cannot be readonly.
*/
final boolean readonly = !function.vars.allocator().isReadOnly(object.getVariable().getName());
program.checker.requireMutableVariable(object.getVariable(), readonly);
/**
* The assignment must actually be possible type wise.
*/
final IVariableType expected = function.allocator.typeOf(object.getVariable().getName());
program.checker.checkAssign(object, expected, object.getValue());
}
@Override
public void visit(final LambdaStatement object)
{
/**
* Get the type of the lambda.
*/
final IClassType functor_type = module.imports.resolveClassType(object.getType());
/**
* Declare the lambda variable.
*/
super.declareVar(object.getVariable(), functor_type, false);
try
{
/**
* A lambda-statement defines a nested scope that covers its parameters and body.
*/
allocator.enterScope();
/**
* Create the lambda itself.
*/
final LambdaCompiler lambda = new LambdaCompiler(function, object);
/**
* Visit and type-check the lambda's body.
*/
lambda.performTypeUsageChecking();
/**
* Save the lambda-compiler, because it will generate the lambda's bytecode later.
*/
program.symbols.lambdas.put(object, lambda);
}
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 DelegateStatement object)
{
/**
* Get the assignee variable.
*/
final Variable assignee = object.getVariable();
/**
* Get the name of the function that will be delegated to.
*/
final String name = object.getMethod().getName();
/**
* Get the type of the delegate itself.
*/
final IClassType functor_type = module.imports.resolveDefinedFunctorType(object.getType());
/**
* The functor-type must also be a class-type.
*/
program.checker.requireClassType(object, functor_type);
/**
* Get the type of the module that contains the function that will be delegated to.
*/
final IClassType owner_type = module.imports.resolveModuleType(object.getOwner());
/**
* Resolve the method that will be delegated to.
*/
final DelegateToHandler mapping = DelegateToHandler.find(functor_type, owner_type, name);
if (mapping.error == DelegateToHandler.Errors.NO_SUCH_METHOD)
{
// This will throw an exception.
program.checker.reportNoSuchMethod(object, owner_type, name);
}
else if (mapping.error == DelegateToHandler.Errors.OVERLOADED_METHOD)
{
// This will throw an exception.
program.checker.reportOverloadedMethod(object, owner_type, name);
}
else if (mapping.error == DelegateToHandler.Errors.INCOMPATIBLE_METHOD)
{
// This will throw an exception.
program.checker.reportIncompatibleDelegate(object, mapping.delegate, mapping.handler);
}
/**
* Declare the assignee variable as a readonly local variable.
*/
super.declareVar(assignee, functor_type, false);
/**
* Remember the delegate-method, because it will be needed during code generation.
*/
program.symbols.delegates.put(object, mapping.handler);
}
@Override
public void visit(final SequenceStatement object)
{
allocator.enterScope();
for (IStatement s : object.getElements())
{
/**
* The following code must be in a try-catch.
* Otherwise, an exception would mess up the scope management.
*/
try
{
s.accept(this);
}
catch (TypeCheckFailed ex)
{
// Pass in order to report as many errors as possible.
}
}
allocator.exitScope();
}
@Override
public void visit(final ExpressionStatement object)
{
/**
* Visit and type-check the expression itself.
*/
object.getExpression().accept(this);
}
@Override
public void visit(final NopStatement object)
{
// Pass, because there is nothing to check.
}
@Override
public void visit(final TryCatchStatement object)
{
/**
* Visit and type-check the body.
*/
object.getBody().accept(this);
/**
* Visit and type-check all of the handlers.
*/
object.getHandlers().accept(this);
/**
* Sort the handlers based on the type of exception they catch.
* For example, a handler that catches Exception should come before
* a handler that catches Throwable, because Exception is more specific
* than Throwable.
*/
final TopoSorter<ExceptionHandler> sorter = new TopoSorter<ExceptionHandler>()
{
@Override
public boolean isLess(final ExceptionHandler left,
final ExceptionHandler right)
{
final IType left_type = function.module.imports.resolveReturnType(left.getType());
final IType right_type = function.module.imports.resolveReturnType(right.getType());
return left_type.isSubtypeOf(right_type);
}
};
sorter.addAll(object.getHandlers());
final List<ExceptionHandler> sorted = ImmutableList.copyOf(sorter.elements());
/**
* Remember the sorted handlers, so the code-generator can generate them in sorted order.
*/
program.symbols.handlers.put(object, sorted);
/**
* Detect duplicate exception handlers.
*/
final Set<IExpressionType> types = Sets.newHashSet();
for (ExceptionHandler handler : sorted)
{
final IExpressionType type = function.module.imports.resolveReturnType(handler.getType());
if (types.contains(type))
{
program.checker.reportDuplicateExceptionHandler(handler, type);
}
types.add(type);
}
}
@Override
public void visit(final ExceptionHandler object)
{
/**
* Get the type of exception that the handler will handle.
*/
final IExpressionType type = function.module.imports.resolveReturnType(object.getType());
try
{
/**
* The handler defines a scope that covers the body of the handler.
*/
allocator.enterScope();
/**
* The handler can only be used to catch exception-types.
*/
program.checker.requireType(object.getType(), type);
program.checker.requireThrowable(object.getType(), type);
/**
* Declare the exception variable.
*/
super.declareVar(object.getVariable(), type, false);
/**
* Visit and type-check the body of the handler.
*/
object.getBody().accept(this);
}
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 ThrowStatement object)
{
object.getValue().accept(this);
// A throw-statement can only throw throwable objects.
program.checker.requireThrowable(object.getValue());
}
@Override
public void visit(final AssertStatement object)
{
object.getCondition().accept(this);
// The condition must produce a some sort of boolean.
condition(object.getCondition());
// The message, if present, must produce a string.
if (object.getMessage() != null)
{
object.getMessage().accept(this);
program.checker.requireString(object.getMessage());
}
}
@Override
public void visit(final AssumeStatement object)
{
object.getCondition().accept(this);
// The condition must produce a some sort of boolean.
condition(object.getCondition());
// The message, if present, must produce a string.
if (object.getMessage() != null)
{
object.getMessage().accept(this);
program.checker.requireString(object.getMessage());
}
}
@Override
public void visit(final ReturnVoidStatement object)
{
/**
* Only a function whose return-type is void can contain a return-void statement.
*/
program.checker.requireVoid(object, function.type.getReturnType());
}
@Override
public void visit(final ReturnValueStatement object)
{
/**
* Visit and type-check the expression that produces the value to return.
*/
object.getValue().accept(this);
/**
* The expression must return a non void value.
*/
program.checker.requireNonVoid(object.getValue());
/**
* The type of the expression must be assignable to the return-type of the function.
*/
program.checker.checkReturn(object, function.type.getReturnType(), object.getValue());
}
@Override
public void visit(final RecurStatement object)
{
/**
* The number of arguments must match the number of parameters.
*/
if (function.type.getParameters().size() != object.getArguments().size())
{
// This will throw an exception.
program.checker.reportBadArgumentCount(object, function.type.getParameters().size());
}
/**
* Each argument must be assignable to the related parameter.
*/
for (int i = 0; i < object.getArguments().size(); i++)
{
/**
* Get the argument expression.
*/
final IExpression argument = object.getArguments().get(i);
/**
* Get the type of the related parameter.
*/
final IVariableType parameter = function.type.getParameters().get(i).getType();
/**
* Visit and type-check the argument.
*/
argument.accept(this);
/**
* The argument must be assignable to the parameter.
*/
program.checker.checkAssign(object, parameter, argument);
}
}
}