mirror of https://github.com/tbklang/tlang.git
Exceptions
- `TypeCheckerException` now inherits from `TError` - `TypeCheckerException` now produces a neat error message using an enum `TypecheckError` - Added new sub-class `TypeMismatchException` to be used when two types do not match TypeChecker - Hoisted out the coercion code into two methods, `isCoercibleRange` and `attemptCoercion` - Make both variabel declarations (with assignments) and standlaone variable assignments call the `attemptCoercion()` method when the call to `isSameType(Type t1, Type t2)` returns `false` Test cases - Added new test case `simple_literals2.t`
This commit is contained in:
parent
c1ee7d06ba
commit
065c8d5816
|
@ -342,8 +342,150 @@ public final class TypeChecker
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a type to try coerce towards and a literal value
|
||||||
|
* instruction, this will check whether the literal itself
|
||||||
|
* is within the range whereby it may be coerced
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* variableType = the type to try coercing towards
|
||||||
|
* assignmentInstruction = the literal to apply a range
|
||||||
|
* check to
|
||||||
|
*/
|
||||||
|
private bool isCoercibleRange(Type toType, Value literalInstr)
|
||||||
|
{
|
||||||
|
// You should only be calling this on either a `LiteralValue`
|
||||||
|
// ... or a `LiteralValueFloat` instruction
|
||||||
|
// TODO: Add support for UnaryOpInstr (where the inner type is then)
|
||||||
|
// ... one of the above
|
||||||
|
assert(cast(LiteralValue)literalInstr || cast(LiteralValueFloat)literalInstr || cast(UnaryOpInstr)literalInstr);
|
||||||
|
|
||||||
|
// LiteralValue (integer literal instructions)
|
||||||
|
if(cast(LiteralValue)literalInstr)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
// LiteralValue (integer literal instructions)
|
||||||
|
else if(cast(LiteralValueFloat)literalInstr)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
// Unary operator
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UnaryOpInstr unaryOpLiteral = cast(UnaryOpInstr)literalInstr;
|
||||||
|
assert(unaryOpLiteral.getOperator() == SymbolType.SUB);
|
||||||
|
|
||||||
|
Value operandInstr = unaryOpLiteral.getOperand();
|
||||||
|
|
||||||
|
// LiteralValue (integer literal instructions) with subtraction infront
|
||||||
|
if(cast(LiteralValue)literalInstr)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
// LiteralValue (integer literal instructions) with subtraction infront
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempts to perform coercion of the provided Value-instruction
|
||||||
|
* with respect to the provided variable type.
|
||||||
|
*
|
||||||
|
* This should only be called if the types do not match.
|
||||||
|
* This will update the provided instruction's type-field
|
||||||
|
*
|
||||||
|
* Params:
|
||||||
|
* variableType = the type to attempt coercing the instruction to
|
||||||
|
* assignmentInstruction = instruction to coerce
|
||||||
|
*/
|
||||||
|
private void attemptCoercion(Type variableType, Value assignmentInstruction)
|
||||||
|
{
|
||||||
|
/* Extract the type of the assignment instruction */
|
||||||
|
Type assignmentType = assignmentInstruction.getInstrType();
|
||||||
|
|
||||||
|
// If it is a LiteralValue (integer literal) (support for issue #94)
|
||||||
|
if(cast(LiteralValue)assignmentInstruction)
|
||||||
|
{
|
||||||
|
// TODO: Add a check for if these types are both atleast integral (as in the Variable's type)
|
||||||
|
// ... THEN (TODO): Check if range makes sense
|
||||||
|
bool isIntegral = !(cast(Integer)variableType is null); // Integrality check
|
||||||
|
|
||||||
|
if(isIntegral)
|
||||||
|
{
|
||||||
|
bool isCoercible = isCoercibleRange(variableType, assignmentInstruction); // TODO: Range check
|
||||||
|
|
||||||
|
if(isCoercible)
|
||||||
|
{
|
||||||
|
// TODO: Coerce here by changing the embedded instruction's type (I think this makes sense)
|
||||||
|
// ... as during code emit that is what will be hoisted out and checked regarding its type
|
||||||
|
// NOTE: Referrring to same type should not be a problem (see #96 Question 1)
|
||||||
|
assignmentInstruction.setInstrType(variableType);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new TypeMismatchException(this, variableType, assignmentType, "Not coercible (range violation)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new TypeMismatchException(this, variableType, assignmentType, "Not coercible (lacking integral var type)");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// If it is a LiteralValueFloat (support for issue #94)
|
||||||
|
else if(cast(LiteralValueFloat)assignmentInstruction)
|
||||||
|
{
|
||||||
|
gprintln("Coercion not yet supported for floating point literals", DebugType.ERROR);
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
// Unary operator (specifically with a minus)
|
||||||
|
else if(cast(UnaryOpInstr)assignmentInstruction)
|
||||||
|
{
|
||||||
|
UnaryOpInstr unaryOpInstr = cast(UnaryOpInstr)assignmentInstruction;
|
||||||
|
|
||||||
|
if(unaryOpInstr.getOperator() == SymbolType.SUB)
|
||||||
|
{
|
||||||
|
Value operandInstr = unaryOpInstr.getOperand();
|
||||||
|
|
||||||
|
// If it is a negative LiteralValue (integer literal)
|
||||||
|
if(cast(LiteralValue)operandInstr)
|
||||||
|
{
|
||||||
|
// TODO: Implement things here
|
||||||
|
gprintln("Please implement coercing checking for negative integer literals", DebugType.ERROR);
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
// If it is a negative LiteralValueFloat (floating-point literal)
|
||||||
|
else if(cast(LiteralValueFloat)operandInstr)
|
||||||
|
{
|
||||||
|
gprintln("Coercion not yet supported for floating point literals", DebugType.ERROR);
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
// If anything else is embedded
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new TypeMismatchException(this, variableType, assignmentType, "Not coercible (lacking integral var type)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new TypeMismatchException(this, variableType, assignmentType, "Cannot coerce a non minus unary operation");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new TypeMismatchException(this, variableType, assignmentType, "Not coercible (lacking integral var type)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void typeCheckThing(DNode dnode)
|
public void typeCheckThing(DNode dnode)
|
||||||
{
|
{
|
||||||
|
@ -849,60 +991,14 @@ public final class TypeChecker
|
||||||
// If the types do not match
|
// If the types do not match
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// If it is a LiteralValue (integer literal) (support for issue #94)
|
// Then attempt coercion
|
||||||
if(cast(LiteralValue)assignmentInstr)
|
attemptCoercion(variableDeclarationType, assignmentInstr);
|
||||||
{
|
|
||||||
// TODO: Add a check for if these types are both atleast integral (as in the Variable's type)
|
|
||||||
// ... THEN (TODO): Check if range makes sense
|
|
||||||
bool isIntegral = !(cast(Integer)variableDeclarationType is null); // Integrality check
|
|
||||||
|
|
||||||
if(isIntegral)
|
|
||||||
{
|
|
||||||
bool isCoercible = true; // TODO: Range check
|
|
||||||
|
|
||||||
if(isCoercible)
|
|
||||||
{
|
|
||||||
// TODO: Coerce here by changing the embedded instruction's type (I think this makes sense)
|
|
||||||
// ... as during code emit that is what will be hoisted out and checked regarding its type
|
|
||||||
// NOTE: Referrring to same type should not be a problem (see #96 Question 1)
|
|
||||||
assignmentInstr.setInstrType(variableDeclarationType);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
gprintln("Not coercible (range violation)", DebugType.ERROR);
|
|
||||||
assert(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
gprintln("Not coercible (lacking integral var type)", DebugType.ERROR);
|
|
||||||
assert(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
// If it is a LiteralFloatingValue (support for issue #94)
|
|
||||||
else if(cast(LiteralValue)assignmentInstr)
|
|
||||||
{
|
|
||||||
gprintln("Coercion not yet supported for floating point literals", DebugType.ERROR);
|
|
||||||
assert(false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
gprintln("MISMATCH: Variable's declared type ('"~to!(string)(variableDeclarationType)~"') does not match that of assignment expression's type ('"~to!(string)(assignmentType)~"')", DebugType.ERROR);
|
|
||||||
assert(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Generate a variable declaration instruction and add it to the codequeue */
|
||||||
|
|
||||||
|
|
||||||
VariableDeclaration varDecInstr = new VariableDeclaration(variableName, 4, variableDeclarationType, assignmentInstr);
|
VariableDeclaration varDecInstr = new VariableDeclaration(variableName, 4, variableDeclarationType, assignmentInstr);
|
||||||
|
|
||||||
/* NEW CODE (9th November 2021) Set the context */
|
|
||||||
varDecInstr.setContext(variablePNode.context);
|
varDecInstr.setContext(variablePNode.context);
|
||||||
|
|
||||||
|
|
||||||
addInstrB(varDecInstr);
|
addInstrB(varDecInstr);
|
||||||
}
|
}
|
||||||
/* TODO: Add class init, see #8 */
|
/* TODO: Add class init, see #8 */
|
||||||
|
@ -932,6 +1028,11 @@ public final class TypeChecker
|
||||||
VariableAssignmentStdAlone vasa = cast(VariableAssignmentStdAlone)statement;
|
VariableAssignmentStdAlone vasa = cast(VariableAssignmentStdAlone)statement;
|
||||||
string variableName = vasa.getVariableName();
|
string variableName = vasa.getVariableName();
|
||||||
|
|
||||||
|
/* Extract information about the variable declaration of the avriable being assigned to */
|
||||||
|
Context variableContext = vasa.getContext();
|
||||||
|
Variable variable = cast(Variable)resolver.resolveBest(variableContext.container, variableName);
|
||||||
|
Type variableDeclarationType = getType(variableContext.container, variable.getType());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Codegen
|
* Codegen
|
||||||
*
|
*
|
||||||
|
@ -941,31 +1042,29 @@ public final class TypeChecker
|
||||||
*/
|
*/
|
||||||
Instruction instr = popInstr();
|
Instruction instr = popInstr();
|
||||||
assert(instr);
|
assert(instr);
|
||||||
Value valueInstr = cast(Value)instr;
|
Value assignmentInstr = cast(Value)instr;
|
||||||
assert(valueInstr);
|
assert(assignmentInstr);
|
||||||
|
|
||||||
// TODO: A popType() should be done here techncially, IF we do this then it
|
|
||||||
// ... must be pushed by VariableAssigmnetNode
|
Type assignmentType = assignmentInstr.getInstrType();
|
||||||
Type assignmentType = valueInstr.getInstrType();
|
|
||||||
assert(assignmentType);
|
assert(assignmentType);
|
||||||
|
|
||||||
|
|
||||||
gprintln("Instruction popped: "~to!(string)(instr));
|
if(isSameType(variableDeclarationType, assignmentType))
|
||||||
gprintln("Type popped: "~to!(string)(assignmentType));
|
{
|
||||||
|
gprintln("Variable's declared type ('"~to!(string)(variableDeclarationType)~"') matches that of assignment expression's type ('"~to!(string)(assignmentType)~"')");
|
||||||
|
}
|
||||||
|
// If the type's do not match
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Then attempt coercion
|
||||||
|
attemptCoercion(variableDeclarationType, assignmentInstr);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: WOAH! We should be consuming here though!!! not making!?!?
|
/* Generate a variable assignment instruction and add it to the codequeue */
|
||||||
VariableAssignmentInstr vAInstr = new VariableAssignmentInstr(variableName, valueInstr);
|
VariableAssignmentInstr vAInstr = new VariableAssignmentInstr(variableName, assignmentInstr);
|
||||||
|
|
||||||
/* Set the VariableAssigmmentInstruction's context to that of the stdalone entity */
|
|
||||||
vAInstr.setContext(vasa.getContext());
|
vAInstr.setContext(vasa.getContext());
|
||||||
|
|
||||||
addInstrB(vAInstr);
|
addInstrB(vAInstr);
|
||||||
|
|
||||||
gprintln("VariableAssignmentStdAlone", DebugType.ERROR);
|
|
||||||
|
|
||||||
gprintln("VariableAssignmentStdAlone needs some reworking", DebugType.ERROR);
|
|
||||||
// assert(false);
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Return statement (ReturnStmt)
|
* Return statement (ReturnStmt)
|
||||||
|
|
|
@ -4,17 +4,46 @@ import compiler.typecheck.core;
|
||||||
import compiler.symbols.data;
|
import compiler.symbols.data;
|
||||||
import compiler.typecheck.resolution;
|
import compiler.typecheck.resolution;
|
||||||
import std.string : cmp;
|
import std.string : cmp;
|
||||||
|
import std.conv : to;
|
||||||
|
import misc.exceptions: TError;
|
||||||
|
import compiler.symbols.typing.core;
|
||||||
|
|
||||||
public class TypeCheckerException : Exception
|
public class TypeCheckerException : TError
|
||||||
{
|
{
|
||||||
private TypeChecker typeChecker;
|
private TypeChecker typeChecker;
|
||||||
|
|
||||||
this(TypeChecker typeChecker)
|
// NOTE: See if we use, as we seem to overwrite the `msg` value
|
||||||
|
// ... in sub-classes of this
|
||||||
|
public enum TypeheckError
|
||||||
|
{
|
||||||
|
GENERAL_ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
this(TypeChecker typeChecker, TypeheckError errType, string msg = "")
|
||||||
{
|
{
|
||||||
/* We set it after each child class calls this constructor (which sets it to empty) */
|
/* We set it after each child class calls this constructor (which sets it to empty) */
|
||||||
super("");
|
super("TypeCheck Error ("~to!(string)(errType)~")"~(msg.length > 0 ? ": "~msg : ""));
|
||||||
this.typeChecker = typeChecker;
|
this.typeChecker = typeChecker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Remove this constructor and make anything that is currently using it
|
||||||
|
// ... switch to atleast specifying the errType
|
||||||
|
this(TypeChecker typeChecker)
|
||||||
|
{
|
||||||
|
this(typeChecker, TypeheckError.GENERAL_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class TypeMismatchException : TypeCheckerException
|
||||||
|
{
|
||||||
|
this(TypeChecker typeChecker, Type originalType, Type attemptedType, string msgIn = "")
|
||||||
|
{
|
||||||
|
super(typeChecker);
|
||||||
|
|
||||||
|
msg = "Type mismatch between type "~originalType.getName()~" and "~attemptedType.getName();
|
||||||
|
|
||||||
|
msg ~= msgIn.length > 0 ? ": "~msgIn : "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class CollidingNameException : TypeCheckerException
|
public final class CollidingNameException : TypeCheckerException
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
module simple_literals2;
|
||||||
|
|
||||||
|
ubyte var = 1;
|
||||||
|
|
||||||
|
int func()
|
||||||
|
{
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test()
|
||||||
|
{
|
||||||
|
var = 2;
|
||||||
|
var = func();
|
||||||
|
}
|
Loading…
Reference in New Issue