mirror of https://github.com/tbklang/tlang.git
🧠️ Feature: Universal coercion and type enforcer (#9)
* TypeChecker - Added `bool isSameType2(Value v1, Value v2, bool attemptCoercion = false)` for future implementation of universal coercion as per #115 * TypeChecker - Renamed `isSameType2` to `typeEnforce` - Updated `typeEnforce`'s default parameter documentation from `false` to `attemptCoercion` (as it should have been in the beginning) * TypeCheckerException - Save the `TypecheckError` coming in as `errType` and make it available via `getError()` TypemMismatchException - Save the original (expected) and attempted types and make them available via `getExpectedType()` and `getATtemptedType()` respectively * TypeChecker - Updated `typeEnforce` from taking in `Value v1, Value v2, bool` to `Type t1, Value v2, bool`. - `typeEnforce()` will now extract the `Type` of `Value v2` and call `isSameType(t1, t2)`, if that fails and coercion is allowed then it is attempted, however if that fails then it causes an exception to be thrown. In the case coercion is not allowed, then a `TypeMismatchException` is thrown Unit tests - Tested the new `typeEnforce(Type t1, Value v2, bool)` and it seems to work, both a case of failing matching (coercion disallowed) and working coercion (coercion allowed) * TypeChecker - Documented existing unittest for `typeEnforce(Type, Value, bool)` - Added new unit test for `typeEnforce(Type, Value, bool)` which tests when the types ARE the same * TypeChecker - Cleaned up `typeEnforce(Type, Value, bool)` * TypeChecker - Added a work-in-progress unit test to test how I would use `typeEnforce(Type t1, Value v2, bool coercion = false)` in practice - Added TODOs in `attemptCoercion(Type, Type)` where I must add support * TypeChecker - Finished the unit test testing out the usage for `typeEnforce(Type, Value, bool coerce = false)` - Added TODOs to `attemptCoercion(Type, Value)` for the changes required to it * TypeChecker - Removed incorrect TODOs from `attemptCoerce(Type, Value)` and updated the message when the coercion fails Unit tests - Updated first unit test for `typeEnforce()` to test failing coercion on a non-`LiteralValue` instruction - Added a unit test where `typeEnforce()` WILL pass as it coerces a `LiteralValue` instruction * Exceptions (`typechecker`) - Added new exception type `CoercionException` to be thrown whenever a coercion cannot take place. * TypeChecker - Ensure that `attemptCoercion(Type, Value)` only throws instances of `CoercionException` * Unit tests - Fixed failing-coercion check by catching the correct exception when it fails `CoercionException` instead of `TypeMismatchException`) * TypeChecker - Added documentation for `isSameType(Type t1, Type t2)` * TypeChecker - Updated documentation for `isCoercibleRange(Type, Value)` - Updated `attemptCoercion(Type, Value)` with new documentation and renamed parameters * Unit tests (typechecker) - Added comments * TypeChecker - Removed now-completed TODO in `typeEnforce(Type t1, Value v2, bool allowCoercion = false)` * TypeChecker - Removed unused `typeStatus` variable in `typeEnforce(Type, Value, bool)` * TypeChecker - Variable declarations (with assignments) now use the `typeEnforce()` method with coercion allowed in order to do the type checking and coercion changes - Added a comment explaining a certain branch of `attemptCoercion(Type, Value)` * TypeChecker - If the to-type and provided-type are both numerical then use a size-based test Test cases - Added two test cases which test `typeEnforce()` on incoming `Value`-based instructions as part of variable declarations * Test cases - Fixed negative test case - it MUST have an error and that should be seen as a pass * TypeChecker (unit tests) - Disabled invalid unit test (marked for re-writing) - I should re-write the below. It is now incorrect as I DO ALLOW coercion of non literal-based instructions now - so it fails because it is using an older specification of TLang * TypeChecker - Migrated the type checking of standalone variable assignments to using `typeEnforce()` Test cases - Added positive and negative test cases * - Updated `.gitignore` * Feature/type enforcer cast instr emit (#13) * TypeChecker - `typeEnforce()` now will not change the type of `Value`-based instruction `v2` but rather return, on successful coercion set a `ref`-based argument to a new instance of a `CastedValueInstruction`, if coercion fails or was disabled and types mismatched then an exeption is thrown as normal. - If the types are an exact same match, a-la `isSameType(Type, Type)`, then this `ref` value is set to `v2` (makes programming easy) else we would have no way to know - `attemptCoerce()` now, to go with the above changes to `typeEnforce()`, returns a `CatsedValueInstruction` to the to-type on successful coercion, else an exception is thrown as usual - Updated two cases of `typeEnforce()` usage to the new method signature, also now add a sanity check assertion that the types now DO match as they should * TypeChecker - We need not set it again, look the value we use when we CALL `typeEnforce()` is that of the `fromInstruction` and if no changes occur we still have it, it is fine - if it changes via the call to `typeEnforce()` via the `ref` based argument thne same old - No need for us to set it here in the event of no changes, we are writing back the exact same Instruction/object-reference * TypeChecker (unit tests) - Upgraded to the new `typeEnforcer()` method signature * TypeChecker - Improved documentation for `typeEnforce()` * TypeChecker - Added TODO regarding pointer coercion with integers in `Pointer + Integer` case (for pointer airthmetic) * TypeChecker - Added a new branch which currently throws an exception as it is unimplememted - This branch (above) is in `attemptCoercion()` and is to handle the coercion of `Integer` to `Pointer` for pointer arithmetic - When doing the typechecking/codegen for `BinaryOp`, disable the pointer coercion call to `attemptPointerAriehmeticCoercion()`, instead now make calls in those cases they apply, to `typeEnforce()` - The above stuff is still broken, not yet implemented. * TypeChecker - Cannot use cast as that can return false positives for an all pointer case as all `Pointer`s are `Integer`s - Added `isPointerType(Type)` to check the above - Added then also `isIntegralTypeButNotPointer(Type)` which checks for an `Integer` type but excluding if it is a `Pointer` - Updated the checks in the `BinaryOperator` branch of `typeCheckThing(DNode)` to do this * TypeChecker - Need to do the `Pointer` checks first in `attemptCoercion(Type, Value)` * TypeChecker - `attemptCoercion(Type, Value)` now returns a `CastedValueInstruction` to cast the `Integer` type to the `Pointer` type * TypeCHecker - Catch mis use of type enforcement by using `isIntegralTypeButNotPointer(Type)` and isPointerType`(Type)` for the previous commit * TypeChecker - Refresh the types after the potential calls to `typeEnforce(..., ..., ..., ...)` * Pipeline - Use `set -e` for `simple_pointer.t` test in emit stage * Pipelines (emit stage) - Previous compilation may have succeeded, meaning ./tlang.out never gets updated and exits fine with 0, but we only use the last commands exit status to check for a pass for a test. - By setting this if COMPILATION fails then we exit with its code and the test status is set via that * Pipelines - Removed the `set -e` code as the correct `Exception` now causes a non-zero exit code from the changes made in `varass_vardec_dependency` * DGen - Added notice for issue #140 * TypeChecker - Made `isIntegralTypeButNotPointer(Type)` public - Made `isPointerType(Type)` public * Instructions - `CastedValueInstruction` now is unrelaxed by default but can be set (tis aids in how it can be emitted later for issue #140) * DGen - Added some checks for certain conditions whereby pointer coercion requires relaxing the casted operands (coerced operands) * DGen - Relax `CastedValueInstruction`(s) when appropriate in `BinaryOpInstr` handling code - Removed panics * DGen - Added relaxation support to the code emitting code for `CastedValueInstruction` * DGen - make debug messages for when relaxation occurs for `CastedValueInstruction` emitting more clear * TypeChecker - Implemented `biggerOfTheTwo(Integer, Integer)` which determines the biggest of the two `Integer`-based types and returns that one. * TypeChecker - Fixed incorrect variable name in `biggerOfTheTwo(Integer, Integer)` * TypeChecker - Throw an error in the case where a `BinaryOperatorExpression` occurs with non-`Integer`-based instructions (at least for now) * TypeChecker - If both types are `Integral` (but not `Pointer`) then smaller coerces to bigger, if they however are equal then signed coerces to unsigned * TypeChecker - Removed now irrelevant comment * TypeChecker - Don't throw exception here, rather let the `isSameType(Type, Type)` check handle that - We still keep the warning we print about missing cases implementation-wise * TypeChecker - Fixed explanation * TypeChecker - Marked related issue * TypeChecker - Implemented ` isStackArrayType(Type typeIn)` - WIP: Added a check for handling `StackArray -> Pointer` coercion to `attemptCoercion(Type, Value)` * TypeChecker - `attemptCoercion(Type, Value)` will now ensure firstly that the `StackArray`'s component type matches that of the `Pointer`'s referred type, if not throw an exception, if so, then return a `CastedValueInstruction` * TypeChecker - Print out a debug message when attempting to coerce a `StackArray` to a `Pointer` - Fixed the error message thrown when a `StackArray` could not be coerced to a `Pointer` due to the component type != ptr's referred type - `FunctionCall` handling now has the `canCoerceStackArray()` code disabled and uses the `typeEnforce()` method * TypeChecker - Type checking code for `FunctionCall` * TypeCheck - Completed TODO comment * TypeChecker - Added a TODO * TypeChecker - Added FIXME where the `typeEnforce()` call need to be made for the `ReturnStmt`'s return expression's type to match or be checked-against the containing `Function`'s * TypeChecker - `ReturnStmt` now uses `typeEnforce()` * Test cases - Added two new checks for checking the return type of a function and matching a `ReturnStmt`'s expression's type to it * TypeChecker - Removed assertion check, rather let the exception thrown handle the error - Only after we know the finally-parenting `Container` is a `Function` (should we reference `funcContainer` * Test cases - Removed explicit cast from `simple_function_recursion_factorial.t` * TypeChecker - If we have a `LiteralValue` and a non-`LiteralValue` then coerce the `LiteralValue` towards the non`-LiteralValue` via `typeEnforce()` - This should allow the correct range checking of literal values within the range of the to-type and not require annoying explicit casts * Test cases - Removed now-unneeded explicit casts on literal values in `simple_function_recursion_factorial.t` * TypeChecker - Added comment describing the process used - Removed now-completed TODO * TypeChecker - Removed some dead code - Removed now-completed FIXME/TODO * TypeChecker - Removed old type checking code for variable declarations with assignments - Removed old type checking code for standalone variable assignments
This commit is contained in:
parent
239832a74a
commit
b47a651caf
|
@ -323,6 +323,51 @@ jobs:
|
|||
run: ./tlang typecheck source/tlang/testing/simple_literals5.t
|
||||
- name: Simple literals 6
|
||||
run: ./tlang typecheck source/tlang/testing/simple_literals6.t
|
||||
|
||||
|
||||
|
||||
# All the universal coercion tests are below
|
||||
#
|
||||
# Over time those above will be ported over to it and will
|
||||
# infact make part of the test suite of typeEnforce()
|
||||
- name: Simple Coerce Literal Good (to variable declaration)
|
||||
run: ./tlang typecheck source/tlang/testing/universal_coerce/simple_coerce_literal_good.t
|
||||
- name: Simple Coerce Literal Bad [Size loss] (to variable declaration)
|
||||
run:
|
||||
set +e
|
||||
./tlang typecheck source/tlang/testing/universal_coerce/simple_coerce_literal_bad.t
|
||||
if [ $? = 255 ]
|
||||
then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Simple Coerce Literal Good (standalone variable assignment)
|
||||
run: ./tlang typecheck source/tlang/testing/universal_coerce/simple_coerce_literal_good_stdalo.t
|
||||
- name: Simple Coerce Literal Bad [Size loss] (standalone variable assignment)
|
||||
run:
|
||||
set +e
|
||||
./tlang typecheck source/tlang/testing/universal_coerce/simple_coerce_literal_bad_stdalon.t
|
||||
if [ $? = 255 ]
|
||||
then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Function return expression coercion (good)
|
||||
run: ./tlang typecheck source/tlang/testing/simple_function_return_type_check_good.t
|
||||
- name: Function return expression coercion (bad)
|
||||
run:
|
||||
set +e
|
||||
./tlang typecheck source/tlang/testing/simple_function_return_type_check_bad.t
|
||||
if [ $? = 255 ]
|
||||
then
|
||||
exit 0
|
||||
else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
emit:
|
||||
needs: [build, unittests]
|
||||
|
@ -336,8 +381,9 @@ jobs:
|
|||
name: tbin
|
||||
|
||||
- name: Chmod compiler
|
||||
run: chmod +x tlang
|
||||
|
||||
run: chmod +x tlang
|
||||
|
||||
|
||||
- name: Simple functions
|
||||
run: |
|
||||
./tlang compile source/tlang/testing/simple_functions.t
|
||||
|
|
|
@ -274,6 +274,52 @@ public final class DCodeEmitter : CodeEmitter
|
|||
|
||||
// TODO: I like having `lhs == rhs` for `==` or comparators but not spaces for `lhs+rhs`
|
||||
|
||||
/**
|
||||
* C compiler's do this thing where:
|
||||
*
|
||||
* If `<a>` is a pointer and `<b>` is an integer then the
|
||||
* following pointer arithmetic is allowed:
|
||||
*
|
||||
* int* a = (int*)2;
|
||||
* a = a + b;
|
||||
*
|
||||
* But it's WRONG if you do
|
||||
*
|
||||
* a = a + (int*)b;
|
||||
*
|
||||
* Even though it makes logical sense coercion wise.
|
||||
*
|
||||
* Therefore we need to check such a case and yank
|
||||
* the cast out me thinks.
|
||||
*
|
||||
* See issue #140 (https://deavmi.assigned.network/git/tlang/tlang/issues/140#issuecomment-1892)
|
||||
*/
|
||||
Type leftHandOpType = (cast(Value)binOpInstr.lhs).getInstrType();
|
||||
Type rightHandOpType = (cast(Value)binOpInstr.rhs).getInstrType();
|
||||
|
||||
if(typeChecker.isPointerType(leftHandOpType))
|
||||
{
|
||||
// Sanity check the other side should have been coerced to CastedValueInstruction
|
||||
CastedValueInstruction cvInstr = cast(CastedValueInstruction)binOpInstr.rhs;
|
||||
assert(cvInstr);
|
||||
|
||||
gprintln("CastedValueInstruction relax setting: Da funk RIGHT ");
|
||||
|
||||
// Relax the CV-instr to prevent it from emitting explicit cast code
|
||||
cvInstr.setRelax(true);
|
||||
}
|
||||
else if(typeChecker.isPointerType(rightHandOpType))
|
||||
{
|
||||
// Sanity check the other side should have been coerced to CastedValueInstruction
|
||||
CastedValueInstruction cvInstr = cast(CastedValueInstruction)binOpInstr.lhs;
|
||||
assert(cvInstr);
|
||||
|
||||
gprintln("CastedValueInstruction relax setting: Da funk LEFT ");
|
||||
|
||||
// Relax the CV-instr to prevent it from emitting explicit cast code
|
||||
cvInstr.setRelax(true);
|
||||
}
|
||||
|
||||
return transform(binOpInstr.lhs)~to!(string)(getCharacter(binOpInstr.operator))~transform(binOpInstr.rhs);
|
||||
}
|
||||
/* FuncCallInstr */
|
||||
|
@ -537,6 +583,19 @@ public final class DCodeEmitter : CodeEmitter
|
|||
|
||||
string emit;
|
||||
|
||||
|
||||
/**
|
||||
* Issue #140
|
||||
*
|
||||
* If relaxed then just emit the uncasted instruction
|
||||
*/
|
||||
if(castedValueInstruction.isRelaxed())
|
||||
{
|
||||
/* The original expression */
|
||||
emit ~= transform(uncastedInstruction);
|
||||
return emit;
|
||||
}
|
||||
|
||||
/* Handling of primitive types */
|
||||
if(cast(Primitive)castingTo)
|
||||
{
|
||||
|
|
|
@ -535,10 +535,22 @@ public final class CastedValueInstruction : Value
|
|||
/* The uncasted original instruction that must be executed-then-trimmed (casted) */
|
||||
private Value uncastedValue;
|
||||
|
||||
/**
|
||||
* Used in code emitting, this is related to
|
||||
* #140. Really just a C+DGen thing.
|
||||
*
|
||||
* Signals that we shouldn't emit any special
|
||||
* casting syntax in the underlying emitter.
|
||||
*/
|
||||
private bool relax;
|
||||
|
||||
this(Value uncastedValue, Type castToType)
|
||||
{
|
||||
this.uncastedValue = uncastedValue;
|
||||
this.type = castToType;
|
||||
|
||||
// Relaxing is disabled by default
|
||||
this.relax = false;
|
||||
}
|
||||
|
||||
public Value getEmbeddedInstruction()
|
||||
|
@ -550,6 +562,16 @@ public final class CastedValueInstruction : Value
|
|||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
public bool isRelaxed()
|
||||
{
|
||||
return relax;
|
||||
}
|
||||
|
||||
public void setRelax(bool relax)
|
||||
{
|
||||
this.relax = relax;
|
||||
}
|
||||
}
|
||||
|
||||
public final class ArrayIndexInstruction : Value
|
||||
|
|
|
@ -358,9 +358,297 @@ public final class TypeChecker
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* There are several types and comparing them differs
|
||||
*/
|
||||
/**
|
||||
* 🧠️ Feature: Universal coercion and type enforcer
|
||||
*
|
||||
* This tests two DIFFERENT types to see if they are:
|
||||
*
|
||||
* 1. The same type (and if not, don't attempt coercion)
|
||||
* 2. The same type (and if not, ATTEMPT coercion)
|
||||
*/
|
||||
unittest
|
||||
{
|
||||
import tlang.compiler.symbols.typing.core;
|
||||
|
||||
TypeChecker tc = new TypeChecker(null);
|
||||
|
||||
/* To type is `t1` */
|
||||
Type t1 = getBuiltInType(tc, "uint");
|
||||
assert(t1);
|
||||
|
||||
/* We will comapre `t2` to `t1` */
|
||||
Type t2 = getBuiltInType(tc, "ubyte");
|
||||
assert(t2);
|
||||
Value v2 = new LiteralValue("25", t2);
|
||||
|
||||
// Ensure instruction v2's type is `ubyte`
|
||||
assert(tc.isSameType(t2, v2.getInstrType()));
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
// Try type match them, if initially fails then try coercion
|
||||
// ... (This should FAIL due to type mismatch and coercion disallowed)
|
||||
tc.typeEnforce(t1, v2, v2, false);
|
||||
assert(false);
|
||||
}
|
||||
catch(TypeMismatchException mismatch)
|
||||
{
|
||||
Type expectedType = mismatch.getExpectedType();
|
||||
Type attemptedType = mismatch.getAttemptedType();
|
||||
assert(tc.isSameType(expectedType, getBuiltInType(tc, "uint")));
|
||||
assert(tc.isSameType(attemptedType, getBuiltInType(tc, "ubyte")));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Try type match them, if initially fails then try coercion
|
||||
// ... (This should pass due to its coercibility)
|
||||
tc.typeEnforce(t1, v2, v2, true);
|
||||
|
||||
// This should have updated `v2`'s type to type `t1`
|
||||
t2 = v2.getInstrType();
|
||||
assert(tc.isSameType(t1, t2));
|
||||
}
|
||||
|
||||
/**
|
||||
* 🧠️ Feature: Universal coercion and type enforcer
|
||||
*
|
||||
* This tests two EQUAL/SAME types to see if they are:
|
||||
*
|
||||
* 1. The same type
|
||||
*/
|
||||
unittest
|
||||
{
|
||||
import tlang.compiler.symbols.typing.core;
|
||||
|
||||
TypeChecker tc = new TypeChecker(null);
|
||||
|
||||
/* To type is `t1` */
|
||||
Type t1 = getBuiltInType(tc, "uint");
|
||||
assert(t1);
|
||||
|
||||
/* We will comapre `t2` to `t1` */
|
||||
Type t2 = getBuiltInType(tc, "uint");
|
||||
assert(t2);
|
||||
Value v2 = new LiteralValue("25", t2);
|
||||
|
||||
// Ensure instruction v2's type is `uint`
|
||||
assert(tc.isSameType(t2, v2.getInstrType()));
|
||||
|
||||
|
||||
// This should not fail (no coercion needed in either)
|
||||
tc.typeEnforce(t1, v2, v2, false);
|
||||
tc.typeEnforce(t1, v2, v2, true);
|
||||
}
|
||||
|
||||
// FIXME: I should re-write the below. It is now incorrect
|
||||
// ... as I DO ALLOW coercion of non literal-based instructions
|
||||
// ... now - so it fails because it is using an older specification
|
||||
// ... of TLang
|
||||
// /**
|
||||
// * 🧠️ Feature: Universal coercion and type enforcer
|
||||
// *
|
||||
// * This tests a failing case (read for details)
|
||||
// */
|
||||
// unittest
|
||||
// {
|
||||
// /**
|
||||
// * Create a simple program with
|
||||
// * a function that returns an uint
|
||||
// * and a variable of type ubyte
|
||||
// */
|
||||
// Module testModule = new Module("myModule");
|
||||
// TypeChecker tc = new TypeChecker(testModule);
|
||||
|
||||
// /* Add the variable */
|
||||
// Variable myVar = new Variable("ubyte", "myVar");
|
||||
// myVar.parentTo(testModule);
|
||||
// testModule.addStatement(myVar);
|
||||
|
||||
// /* Add the function with a return expression */
|
||||
// VariableExpression retExp = new VariableExpression("myVar");
|
||||
// ReturnStmt retStmt = new ReturnStmt(retExp);
|
||||
// Function myFunc = new Function("function", "uint", [retStmt], []);
|
||||
// retStmt.parentTo(myFunc);
|
||||
// testModule.addStatement(myFunc);
|
||||
// myFunc.parentTo(testModule);
|
||||
|
||||
|
||||
// /* Now let's play with this as if the code-queue processor was present */
|
||||
|
||||
|
||||
// /* Create a variable fetch instruction for the `myVar` variable */
|
||||
// Value varFetch = new FetchValueVar("myVar", 1);
|
||||
// varFetch.setInstrType(tc.getType(testModule, myVar.getType()));
|
||||
|
||||
// /**
|
||||
// * Create a ReturnInstruction now based on `function`'s return type
|
||||
// *
|
||||
// * 1) The ay we did this when we only have the `ReturnStmt` on the code-queue
|
||||
// * is by finding the ReturnStmt's parent (the Function) and getting its type.
|
||||
// *
|
||||
// * 2) We must now "pop" the `varFetch` instruction from the stack and compare types.
|
||||
// *
|
||||
// * 3) If the type enforcement is fine, then let's check that they are equal
|
||||
// *
|
||||
// */
|
||||
|
||||
// // 1)
|
||||
// Function returnStmtContainer = cast(Function)retStmt.parentOf();
|
||||
// Type funcReturnType = tc.getType(testModule, returnStmtContainer.getType());
|
||||
|
||||
// // 2) The enforcement will fail as coercion of non-literals is NOT allowed
|
||||
// try
|
||||
// {
|
||||
// tc.typeEnforce(funcReturnType, varFetch, true);
|
||||
// assert(false);
|
||||
// }
|
||||
// catch(CoercionException e)
|
||||
// {
|
||||
// assert(true);
|
||||
// }
|
||||
|
||||
|
||||
// // 3) The types should not be the same
|
||||
// assert(!tc.isSameType(funcReturnType, varFetch.getInstrType()));
|
||||
// }
|
||||
|
||||
/**
|
||||
* 🧠️ Feature: Universal coercion and type enforcer
|
||||
*
|
||||
* This tests a passing case (read for details)
|
||||
*/
|
||||
unittest
|
||||
{
|
||||
/**
|
||||
* Create a simple program with
|
||||
* a function that returns an uint
|
||||
* and an expression of type ubyte
|
||||
*/
|
||||
Module testModule = new Module("myModule");
|
||||
TypeChecker tc = new TypeChecker(testModule);
|
||||
|
||||
|
||||
/* Add the function with a return expression */
|
||||
NumberLiteral retExp = new IntegerLiteral("21", IntegerLiteralEncoding.UNSIGNED_INTEGER);
|
||||
ReturnStmt retStmt = new ReturnStmt(retExp);
|
||||
Function myFunc = new Function("function", "uint", [retStmt], []);
|
||||
retStmt.parentTo(myFunc);
|
||||
testModule.addStatement(myFunc);
|
||||
myFunc.parentTo(testModule);
|
||||
|
||||
|
||||
/* Now let's play with this as if the code-queue processor was present */
|
||||
|
||||
|
||||
/* Create a new LiteralValue instruction with our literal and of type `ubyte` */
|
||||
Type literalType = tc.getType(testModule, "ubyte");
|
||||
Value literalValue = new LiteralValue(retExp.getNumber(), literalType);
|
||||
|
||||
/**
|
||||
* Create a ReturnInstruction now based on `function`'s return type
|
||||
*
|
||||
* 1) The ay we did this when we only have the `ReturnStmt` on the code-queue
|
||||
* is by finding the ReturnStmt's parent (the Function) and getting its type.
|
||||
*
|
||||
* 2) We must now "pop" the `literalValue` instruction from the stack and compare types.
|
||||
*
|
||||
* 3) If the type enforcement is fine, then let's check that they are equal
|
||||
*
|
||||
*/
|
||||
|
||||
// 1)
|
||||
Function returnStmtContainer = cast(Function)retStmt.parentOf();
|
||||
Type funcReturnType = tc.getType(testModule, returnStmtContainer.getType());
|
||||
|
||||
// 2)
|
||||
tc.typeEnforce(funcReturnType, literalValue, literalValue, true);
|
||||
|
||||
// 3)
|
||||
assert(tc.isSameType(funcReturnType, literalValue.getInstrType()));
|
||||
}
|
||||
|
||||
/**
|
||||
* For: 🧠️ Feature: Universal coercion
|
||||
*
|
||||
* Given a Type `t1` and a `Value`-based instruction, if the
|
||||
* type of the `Value`-based instruction is the same as that
|
||||
* of the provided type, `t1`, then the function returns cleanly
|
||||
* without throwing any exceptions and will not fill in the `ref`
|
||||
* argument.
|
||||
*
|
||||
* If the types do NOT match then and cerocion is disallowed then
|
||||
* an exception is thrown.
|
||||
*
|
||||
* If the types do NOT match and coercion is allowed then coercion
|
||||
* is attempted. If coercion fails an exception is thrown, else
|
||||
* it will place a `CastedValueInstruction` into the memory referrred
|
||||
* to by the `ref` parameter. It is this instruction that will contain
|
||||
* the action to cast the instruction to the coerced type.
|
||||
*
|
||||
* In the case that coercion is disabled then mismatched types results
|
||||
* in false being returned.
|
||||
*
|
||||
* Params:
|
||||
* t1 = To-type (will coerce towards if requested)
|
||||
* v2 = the `Value`-instruction
|
||||
* ref coerceInstruction = the place to store the `CastedValueInstruction` in if coercion succeeds
|
||||
* (this will just be `v2` itself if the types are the same exactly)
|
||||
* allowCoercion = whether or not at attempt coercion on initial type mismatch (default: `false`)
|
||||
*
|
||||
* Throws:
|
||||
* TypeMismatchException if coercion is disallowed and the types are not equal
|
||||
* Throws:
|
||||
* CoercionException if the types were not equal to begin with, coercion was allowed
|
||||
* but failed to coerce
|
||||
*/
|
||||
private void typeEnforce(Type t1, Value v2, ref Value coerceInstruction, bool allowCoercion = false)
|
||||
{
|
||||
/* Debugging */
|
||||
string dbgHeader = "typeEnforce(t1="~t1.toString()~", v2="~v2.toString()~", attemptCoerce="~to!(string)(allowCoercion)~"): ";
|
||||
gprintln(dbgHeader~"Entering");
|
||||
scope(exit)
|
||||
{
|
||||
gprintln(dbgHeader~"Leaving");
|
||||
}
|
||||
|
||||
/* Extract the original types of `v2` */
|
||||
Type t2 = v2.getInstrType();
|
||||
|
||||
|
||||
/* Check if the types are equal */
|
||||
if(isSameType(t1, t2))
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
/* If the types are NOT the same */
|
||||
else
|
||||
{
|
||||
/* If coercion is allowed */
|
||||
if(allowCoercion)
|
||||
{
|
||||
/* If coerion fails, it would throw an exception */
|
||||
CastedValueInstruction coerceCastInstr = attemptCoercion(t1, v2);
|
||||
coerceInstruction = coerceCastInstr;
|
||||
}
|
||||
/* If coercion is not allowed, then we failed */
|
||||
else
|
||||
{
|
||||
throw new TypeMismatchException(this, t1, t2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the two types for equality
|
||||
*
|
||||
* Params:
|
||||
* type1 = the first type
|
||||
* type2 = the second type
|
||||
*
|
||||
* Returns: true if the types are equal, false otherwise
|
||||
*/
|
||||
private bool isSameType(Type type1, Type type2)
|
||||
{
|
||||
bool same = false;
|
||||
|
@ -410,9 +698,8 @@ public final class TypeChecker
|
|||
* 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
|
||||
* toType = the type to try coercing towards
|
||||
* literalInstr = the literal to apply a range check to
|
||||
*/
|
||||
private bool isCoercibleRange(Type toType, Value literalInstr)
|
||||
{
|
||||
|
@ -629,64 +916,133 @@ public final class TypeChecker
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the provided type refers to a `StackArray`
|
||||
*
|
||||
* Params:
|
||||
* typeIn = the `Type` to test
|
||||
* Returns: `true` if it refers to a `StackArray`,
|
||||
* `false` otherwise
|
||||
*/
|
||||
private bool isStackArrayType(Type typeIn)
|
||||
{
|
||||
return cast(StackArray)typeIn !is null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to perform coercion of the provided Value-instruction
|
||||
* with respect to the provided variable type.
|
||||
* with respect to the provided to-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
|
||||
* toType = the type to attempt coercing the instruction to
|
||||
* providedInstruction = instruction to coerce
|
||||
* Throws:
|
||||
* CoercionException if we cannot coerce to the given to-type
|
||||
* Returns:
|
||||
* the `CastedValueInstruction` on success
|
||||
*/
|
||||
private void attemptCoercion(Type variableType, Value assignmentInstruction)
|
||||
private CastedValueInstruction attemptCoercion(Type toType, Value providedInstruction)
|
||||
{
|
||||
gprintln("VibeCheck?");
|
||||
|
||||
/* Extract the type of the assignment instruction */
|
||||
Type assignmentType = assignmentInstruction.getInstrType();
|
||||
/* Extract the type of the provided instruction */
|
||||
Type providedType = providedInstruction.getInstrType();
|
||||
|
||||
|
||||
/**
|
||||
* ==== Stack-array to pointer coercion ====
|
||||
*
|
||||
* If the provided-type is a `StackArray`
|
||||
* and the to-type is a `Pointer`.
|
||||
*
|
||||
* if this is the case we must cast the `StackArray`
|
||||
* to a new `Pointer` type of its component type
|
||||
*/
|
||||
if(isStackArrayType(providedType) && isPointerType(toType))
|
||||
{
|
||||
// Extract the pointer (to-type's) referred type
|
||||
Pointer toTypePointer = cast(Pointer)toType;
|
||||
Type toTypeReferred = toTypePointer.getReferredType();
|
||||
|
||||
// Extract the stack array's component type
|
||||
StackArray providedTypeStkArr = cast(StackArray)providedType;
|
||||
Type stackArrCompType = providedTypeStkArr.getComponentType();
|
||||
|
||||
// We still need the component type to match the to-type's referred type
|
||||
if(isSameType(stackArrCompType, toTypeReferred))
|
||||
{
|
||||
gprintln("Stack-array ('"~providedInstruction.toString()~"' coercion from type '"~providedType.getName()~"' to type of '"~toType.getName()~"' allowed :)");
|
||||
|
||||
// Return a cast instruction to the to-type
|
||||
return new CastedValueInstruction(providedInstruction, toType);
|
||||
}
|
||||
// If not, error, impossible to coerce
|
||||
else
|
||||
{
|
||||
throw new CoercionException(this, toType, providedType, "Cannot coerce a stack array with component type not matching the provided to-type of the pointer type trying to be coerced towards");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* ==== Pointer coerion check first ====
|
||||
*
|
||||
* If the to-type is a Pointer
|
||||
* If the incoming provided-type is an Integer (non-pointer though)
|
||||
*
|
||||
* This is the case where an Integer [non-pointer though] (provided-type)
|
||||
* must be coerced to a Pointer (to-type)
|
||||
*/
|
||||
else if(isIntegralTypeButNotPointer(providedType) && isPointerType(toType))
|
||||
{
|
||||
// throw new CoercionException(this, toType, providedType, "Yolo baggins, we still need to implement dis");
|
||||
|
||||
// Return a cast instruction to the to-type
|
||||
return new CastedValueInstruction(providedInstruction, toType);
|
||||
}
|
||||
// If it is a LiteralValue (integer literal) (support for issue #94)
|
||||
if(cast(LiteralValue)assignmentInstruction)
|
||||
else if(cast(LiteralValue)providedInstruction)
|
||||
{
|
||||
// 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
|
||||
bool isIntegral = !(cast(Integer)toType is null); // Integrality check
|
||||
|
||||
if(isIntegral)
|
||||
{
|
||||
bool isCoercible = isCoercibleRange(variableType, assignmentInstruction); // TODO: Range check
|
||||
bool isCoercible = isCoercibleRange(toType, providedInstruction); // 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);
|
||||
// providedInstruction.setInstrType(toType);
|
||||
|
||||
// Return a cast instruction to the to-type
|
||||
return new CastedValueInstruction(providedInstruction, toType);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new TypeMismatchException(this, variableType, assignmentType, "Not coercible (range violation)");
|
||||
throw new CoercionException(this, toType, providedType, "Not coercible (range violation)");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new TypeMismatchException(this, variableType, assignmentType, "Not coercible (lacking integral var type)");
|
||||
throw new CoercionException(this, toType, providedType, "Not coercible (lacking integral var type)");
|
||||
}
|
||||
|
||||
}
|
||||
// If it is a LiteralValueFloat (support for issue #94)
|
||||
else if(cast(LiteralValueFloat)assignmentInstruction)
|
||||
else if(cast(LiteralValueFloat)providedInstruction)
|
||||
{
|
||||
gprintln("Coercion not yet supported for floating point literals", DebugType.ERROR);
|
||||
assert(false);
|
||||
}
|
||||
// Unary operator (specifically with a minus)
|
||||
else if(cast(UnaryOpInstr)assignmentInstruction)
|
||||
else if(cast(UnaryOpInstr)providedInstruction)
|
||||
{
|
||||
UnaryOpInstr unaryOpInstr = cast(UnaryOpInstr)assignmentInstruction;
|
||||
UnaryOpInstr unaryOpInstr = cast(UnaryOpInstr)providedInstruction;
|
||||
|
||||
if(unaryOpInstr.getOperator() == SymbolType.SUB)
|
||||
{
|
||||
|
@ -695,7 +1051,7 @@ public final class TypeChecker
|
|||
// If it is a negative LiteralValue (integer literal)
|
||||
if(cast(LiteralValue)operandInstr)
|
||||
{
|
||||
bool isIntegral = !(cast(Integer)variableType is null);
|
||||
bool isIntegral = !(cast(Integer)toType is null);
|
||||
|
||||
if(isIntegral)
|
||||
{
|
||||
|
@ -703,18 +1059,21 @@ public final class TypeChecker
|
|||
|
||||
|
||||
|
||||
bool isCoercible = isCoercibleRange(variableType, assignmentInstruction); // TODO: Range check
|
||||
bool isCoercible = isCoercibleRange(toType, providedInstruction); // 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);
|
||||
// providedInstruction.setInstrType(toType);
|
||||
|
||||
// Return a cast instruction to the to-type
|
||||
return new CastedValueInstruction(providedInstruction, toType);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new TypeMismatchException(this, variableType, assignmentType, "Not coercible (range violation)");
|
||||
throw new CoercionException(this, toType, providedType, "Not coercible (range violation)");
|
||||
}
|
||||
|
||||
|
||||
|
@ -723,6 +1082,11 @@ public final class TypeChecker
|
|||
// gprintln("Please implement coercing checking for negative integer literals", DebugType.ERROR);
|
||||
// assert(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
gprintln("Yo, 'fix me', just throw an exception thing ain't integral, too lazy to write it now", DebugType.ERROR);
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
// If it is a negative LiteralValueFloat (floating-point literal)
|
||||
else if(cast(LiteralValueFloat)operandInstr)
|
||||
|
@ -733,17 +1097,59 @@ public final class TypeChecker
|
|||
// If anything else is embedded
|
||||
else
|
||||
{
|
||||
throw new TypeMismatchException(this, variableType, assignmentType, "Not coercible (lacking integral var type)");
|
||||
throw new CoercionException(this, toType, providedType, "Not coercible (lacking integral var type)");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new TypeMismatchException(this, variableType, assignmentType, "Cannot coerce a non minus unary operation");
|
||||
throw new CoercionException(this, toType, providedType, "Cannot coerce a non minus unary operation");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* If we arrive at this case then it is not any special literal
|
||||
* handling, rather we need to check promotion rules and on
|
||||
* cast-shortening - we raise an error
|
||||
*/
|
||||
else
|
||||
{
|
||||
throw new TypeMismatchException(this, variableType, assignmentType, "Not coercible (lacking integral var type)");
|
||||
/**
|
||||
* If the incoming type is `Number`
|
||||
* and the `toType` is `Number`
|
||||
*/
|
||||
if(cast(Number)providedType && cast(Number)toType)
|
||||
{
|
||||
Number providedNumericType = cast(Number)providedType;
|
||||
Number toNumericType = cast(Number)toType;
|
||||
|
||||
/**
|
||||
* If the provided type is less than or equal
|
||||
* in size to that of the to-type
|
||||
*/
|
||||
if(providedNumericType.getSize() <= toNumericType.getSize())
|
||||
{
|
||||
// providedInstruction.setInstrType(toType);
|
||||
// Return a cast instruction to the to-type
|
||||
return new CastedValueInstruction(providedInstruction, toType);
|
||||
}
|
||||
/**
|
||||
* If the incoming type is bigger than the toType
|
||||
*
|
||||
* E.g.
|
||||
* ```
|
||||
* long i = 2;
|
||||
* byte i1 = i;
|
||||
* ```
|
||||
*/
|
||||
else
|
||||
{
|
||||
throw new CoercionException(this, toType, providedType, "Loss of size would occur");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
gprintln("Mashallah why are we here? BECAUSE we should just use ze-value-based genral case!: "~providedInstruction.classinfo.toString());
|
||||
throw new CoercionException(this, toType, providedType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -883,6 +1289,70 @@ public final class TypeChecker
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given `Type` is a pointer-type
|
||||
*
|
||||
* Params:
|
||||
* typeIn = the `Type` to check
|
||||
* Returns: `true` if the type is a `Pointer`,
|
||||
* `false` otherwise
|
||||
*/
|
||||
public bool isPointerType(Type typeIn)
|
||||
{
|
||||
return typeid(typeIn) == typeid(Pointer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given `Type` is an integral type
|
||||
* (a kind-of `Integer`) HOWEVER that it is not
|
||||
* a `Pointer` (recall all ptrs are integers)
|
||||
*
|
||||
* Params:
|
||||
* typeIn = the `Type` to check
|
||||
* Returns: `true` if integral (not pointer) type,
|
||||
* `false` otherwise
|
||||
*/
|
||||
public bool isIntegralTypeButNotPointer(Type typeIn)
|
||||
{
|
||||
return cast(Integer)typeIn && !isPointerType(typeIn);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines the biggest of the two `Integer`-based types
|
||||
* and returns that one.
|
||||
*
|
||||
* If neither is bigger than the other then the first is
|
||||
* returned.
|
||||
*
|
||||
* Please do not pass in `Pointer` types here - NOT the
|
||||
* intended usage (even though allowed).
|
||||
*
|
||||
* Params:
|
||||
* integralType1 = the first `Integer`-based type to test
|
||||
* integralType2 = the second `Integer`-based type to test
|
||||
* Returns: the biggest `Integer` type
|
||||
*/
|
||||
private Integer biggerOfTheTwo(Integer integralType1, Integer integralType2)
|
||||
{
|
||||
// Sanity check, please don't pass Pointer types in here
|
||||
// as that isn't the intended usage
|
||||
assert(!isPointerType(integralType1) && !isPointerType(integralType2));
|
||||
|
||||
if(integralType1.getSize() > integralType2.getSize())
|
||||
{
|
||||
return integralType1;
|
||||
}
|
||||
else if(integralType1.getSize() < integralType2.getSize())
|
||||
{
|
||||
return integralType2;
|
||||
}
|
||||
else
|
||||
{
|
||||
return integralType1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void typeCheckThing(DNode dnode)
|
||||
{
|
||||
|
@ -1053,22 +1523,123 @@ public final class TypeChecker
|
|||
Value vRhsInstr = cast(Value)popInstr();
|
||||
Value vLhsInstr = cast(Value)popInstr();
|
||||
|
||||
/**
|
||||
* Attempt to coerce the types of both instructions if one is
|
||||
* a pointer and another is an integer, else do nothing
|
||||
*/
|
||||
attemptPointerAriehmeticCoercion(vLhsInstr, vRhsInstr);
|
||||
|
||||
|
||||
Type vRhsType = vRhsInstr.getInstrType();
|
||||
Type vLhsType = vLhsInstr.getInstrType();
|
||||
|
||||
|
||||
/**
|
||||
* ==== Pointer coercion ====
|
||||
*
|
||||
* We now need to determine if our bianry operation:
|
||||
*
|
||||
* `a + b`
|
||||
*
|
||||
* Is a case where `a` is a Pointer and `b` is an Integer
|
||||
* OR if `a` is an Integer and `b` is a Pointer
|
||||
* But exclusive OR wise
|
||||
*
|
||||
* And only THEN must we coerce-cast the non-pointer one
|
||||
*
|
||||
* Last case is if the above two are not true.
|
||||
*/
|
||||
if(isPointerType(vLhsType) && isIntegralTypeButNotPointer(vRhsType)) // <a> is Pointer, <b> is Integer
|
||||
{
|
||||
// Coerce right-hand side towards left-hand side
|
||||
typeEnforce(vLhsType, vRhsInstr, vRhsInstr, true);
|
||||
}
|
||||
else if(isIntegralTypeButNotPointer(vLhsType) && isPointerType(vRhsType)) // <a> is Integer, <b> is Pointer
|
||||
{
|
||||
// Coerce the left-hand side towards the right-hand side
|
||||
typeEnforce(vRhsType, vLhsInstr, vLhsInstr, true);
|
||||
}
|
||||
// If left and right operands are integral
|
||||
else if(isIntegralTypeButNotPointer(vLhsType) && isIntegralTypeButNotPointer(vRhsType))
|
||||
{
|
||||
/**
|
||||
* If one of the instructions if a `LiteralValue` (a numeric literal)
|
||||
* and another is not then coerce the literal to the other instruction's
|
||||
* type.
|
||||
*
|
||||
* If the above is NOT true then:
|
||||
*
|
||||
* Coerce the instruction which is the smaller of the two.
|
||||
*
|
||||
* If they are equal then:
|
||||
*
|
||||
* If one is signed and the other unsigned then coerce
|
||||
* signed to unsigned
|
||||
*/
|
||||
Integer vLhsTypeIntegral = cast(Integer)vLhsType;
|
||||
assert(vLhsTypeIntegral);
|
||||
Integer vRhsTypeIntegral = cast(Integer)vRhsType;
|
||||
assert(vRhsTypeIntegral);
|
||||
|
||||
if(cast(LiteralValue)vLhsInstr || cast(LiteralValue)vRhsInstr)
|
||||
{
|
||||
// Type enforce left-hand instruction to right-hand instruction
|
||||
if(cast(LiteralValue)vLhsInstr && cast(LiteralValue)vRhsInstr is null)
|
||||
{
|
||||
typeEnforce(vRhsTypeIntegral, vLhsInstr, vLhsInstr, true);
|
||||
}
|
||||
// Type enforce right-hand instruction to left-hand instruction
|
||||
else if(cast(LiteralValue)vLhsInstr is null && cast(LiteralValue)vRhsInstr)
|
||||
{
|
||||
typeEnforce(vLhsTypeIntegral, vRhsInstr, vRhsInstr, true);
|
||||
}
|
||||
// Both are literal values
|
||||
else
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
else if(vLhsTypeIntegral.getSize() < vRhsTypeIntegral.getSize())
|
||||
{
|
||||
typeEnforce(vRhsTypeIntegral, vLhsInstr, vLhsInstr, true);
|
||||
}
|
||||
else if(vLhsTypeIntegral.getSize() > vRhsTypeIntegral.getSize())
|
||||
{
|
||||
typeEnforce(vLhsTypeIntegral, vRhsInstr, vRhsInstr, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(vLhsTypeIntegral.isSigned() && !vRhsTypeIntegral.isSigned())
|
||||
{
|
||||
typeEnforce(vRhsTypeIntegral, vLhsInstr, vLhsInstr, true);
|
||||
}
|
||||
else if(!vLhsTypeIntegral.isSigned() && vRhsTypeIntegral.isSigned())
|
||||
{
|
||||
typeEnforce(vLhsTypeIntegral, vRhsInstr, vRhsInstr, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Do nothing if they are the same type
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// See issue #141: Binary Operators support for non-Integer types (https://deavmi.assigned.network/git/tlang/tlang/issues/141)
|
||||
gprintln("FIXME: We need to add support for this, class equality, and others like floats", DebugType.ERROR);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* TODO
|
||||
* Types must either BE THE SAME or BE COMPATIBLE
|
||||
*/
|
||||
* Refresh types as instructions may have changed in
|
||||
* the above enforcement call
|
||||
*/
|
||||
vRhsType = vRhsInstr.getInstrType();
|
||||
vLhsType = vLhsInstr.getInstrType();
|
||||
|
||||
/**
|
||||
* We now will check to make sure the types
|
||||
* match, if not an error is thrown.
|
||||
*
|
||||
* We will also then set the instruction's
|
||||
* type to one of the two (they're the same
|
||||
* so it isn't as if it matters). But the
|
||||
* resulting instruction should be of the type
|
||||
* of its components - that's the logic.
|
||||
*/
|
||||
Type chosenType;
|
||||
if(isSameType(vLhsType, vRhsType))
|
||||
{
|
||||
|
@ -1262,41 +1833,30 @@ public final class TypeChecker
|
|||
Type coercionScratchType;
|
||||
|
||||
|
||||
/* Match up types */
|
||||
//if(argType == parmType)
|
||||
if(isSameType(argType, parmType))
|
||||
{
|
||||
gprintln("Match type");
|
||||
|
||||
/* Add the instruction into the FunctionCallInstr */
|
||||
funcCallInstr.setEvalInstr(parmCount, valueInstr);
|
||||
gprintln(funcCallInstr.getEvaluationInstructions());
|
||||
}
|
||||
/* Stack-array argument to pointer parameter coercion check */
|
||||
else if(canCoerceStackArray(parmType, argType, coercionScratchType))
|
||||
{
|
||||
// TODO: Add stack coercion check here
|
||||
gprintln("Stack-based array has been coerced for function call");
|
||||
/**
|
||||
* We need to enforce the `valueInstr`'s' (the `Value`-based
|
||||
* instruction being passed as an argument) type to be that
|
||||
* of the `parmType` (the function's parameter type)
|
||||
*/
|
||||
typeEnforce(parmType, valueInstr, valueInstr, true);
|
||||
|
||||
// Update the fetch-var instruction's type to the coerced
|
||||
// TODO: Should we have applied this technically earlier then fallen through to
|
||||
// ... the branch above? That would have worked and been neater - we should do
|
||||
// ... that to avoid duplicating any code
|
||||
valueInstr.setInstrType(coercionScratchType);
|
||||
/**
|
||||
* Refresh the `argType` as `valueInstr` may have been
|
||||
* updated and we need the new type
|
||||
*/
|
||||
argType = valueInstr.getInstrType();
|
||||
|
||||
|
||||
/* Add the instruction into the FunctionCallInstr */
|
||||
funcCallInstr.setEvalInstr(parmCount, valueInstr);
|
||||
gprintln(funcCallInstr.getEvaluationInstructions());
|
||||
}
|
||||
else
|
||||
{
|
||||
printCodeQueue();
|
||||
gprintln("Wrong actual argument type for function call", DebugType.ERROR);
|
||||
gprintln("Cannot pass value of type '"~argType.getName()~"' to function accepting '"~parmType.getName()~"'", DebugType.ERROR);
|
||||
|
||||
throw new TypeMismatchException(this, parmType, argType, "The actual argument's type does not match that of the function's parameter type");
|
||||
}
|
||||
// Sanity check
|
||||
assert(isSameType(argType, parmType));
|
||||
|
||||
|
||||
/* Add the instruction into the FunctionCallInstr */
|
||||
funcCallInstr.setEvalInstr(parmCount, valueInstr);
|
||||
gprintln(funcCallInstr.getEvaluationInstructions());
|
||||
|
||||
/* Decrement the parameter index (right-to-left, so move to left) */
|
||||
parmCount--;
|
||||
}
|
||||
else
|
||||
|
@ -1594,20 +2154,14 @@ public final class TypeChecker
|
|||
Type assignmentType = assignmentInstr.getInstrType();
|
||||
|
||||
|
||||
// TODO: We should add a typecheck here where we update the type of the valInstr if it is of
|
||||
// ... type NumberLiteral and coerce it to the variable referred to by the VariableAssignment
|
||||
// ... see issue #94 part on "Coercion"
|
||||
// If the types match then everything is fine
|
||||
if(isSameType(variableDeclarationType, assignmentType))
|
||||
{
|
||||
gprintln("Variable's declared type ('"~to!(string)(variableDeclarationType)~"') matches that of assignment expression's type ('"~to!(string)(assignmentType)~"')");
|
||||
}
|
||||
// If the types do not match
|
||||
else
|
||||
{
|
||||
// Then attempt coercion
|
||||
attemptCoercion(variableDeclarationType, assignmentInstr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Here we can call the `typeEnforce` with the popped
|
||||
* `Value` instruction and the type to coerce to
|
||||
* (our variable's type)
|
||||
*/
|
||||
typeEnforce(variableDeclarationType, assignmentInstr, assignmentInstr, true);
|
||||
assert(isSameType(variableDeclarationType, assignmentInstr.getInstrType())); // Sanity check
|
||||
}
|
||||
|
||||
/* Generate a variable declaration instruction and add it to the codequeue */
|
||||
|
@ -1664,16 +2218,15 @@ public final class TypeChecker
|
|||
assert(assignmentType);
|
||||
|
||||
|
||||
if(isSameType(variableDeclarationType, 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);
|
||||
}
|
||||
/**
|
||||
* Here we will do the enforcing of the types
|
||||
*
|
||||
* Will will allow coercion of the provided
|
||||
* type (the value being assigned to our variable)
|
||||
* to the to-type (our Variable's declared type)
|
||||
*/
|
||||
typeEnforce(variableDeclarationType, assignmentInstr, assignmentInstr, true);
|
||||
assert(isSameType(variableDeclarationType, assignmentInstr.getInstrType())); // Sanity check
|
||||
|
||||
/* Generate a variable assignment instruction and add it to the codequeue */
|
||||
VariableAssignmentInstr vAInstr = new VariableAssignmentInstr(variableName, assignmentInstr);
|
||||
|
@ -1687,26 +2240,23 @@ public final class TypeChecker
|
|||
{
|
||||
ReturnStmt returnStatement = cast(ReturnStmt)statement;
|
||||
Function funcContainer = cast(Function)resolver.findContainerOfType(Function.classinfo, returnStatement);
|
||||
assert(funcContainer);
|
||||
string functionName = resolver.generateName(funcContainer.parentOf(), funcContainer);
|
||||
|
||||
/* Generated return instruction */
|
||||
ReturnInstruction returnInstr;
|
||||
|
||||
/**
|
||||
* Typecheck
|
||||
*
|
||||
* TODO: Add typechecking for the return expression
|
||||
* we'd need context of where we are, probably our
|
||||
* parent could be used to check which function
|
||||
* we belong to and hence do the typecheck
|
||||
/**
|
||||
* Ensure that the `ReturnStmt` is finally parented
|
||||
* by a `Function`
|
||||
*/
|
||||
if(!funcContainer)
|
||||
{
|
||||
throw new TypeCheckerException(this, TypeCheckerException.TypecheckError.GENERAL_ERROR, "A return statement can only appear in the body of a function");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extract information about the finally-parented `Function`
|
||||
*/
|
||||
string functionName = resolver.generateName(funcContainer.parentOf(), funcContainer);
|
||||
Type functionReturnType = getType(funcContainer, funcContainer.getType());
|
||||
|
||||
|
||||
|
@ -1745,17 +2295,19 @@ public final class TypeChecker
|
|||
assert(returnExpressionInstr);
|
||||
Type returnExpressionInstrType = returnExpressionInstr.getInstrType();
|
||||
|
||||
/* Ensure the expression's type matches the function's return type */
|
||||
if(isSameType(functionReturnType, returnExpressionInstrType))
|
||||
{
|
||||
/* Generate the instruction */
|
||||
returnInstr = new ReturnInstruction(returnExpressionInstr);
|
||||
}
|
||||
/* If not, then raise an error */
|
||||
else
|
||||
{
|
||||
throw new TypeCheckerException(this, TypeCheckerException.TypecheckError.GENERAL_ERROR, "Returning an expression of type '"~returnExpressionInstrType.toString()~"' does not match function's return type '"~functionReturnType.toString()~"'");
|
||||
}
|
||||
/**
|
||||
* Type check the return expression's type with that of the containing
|
||||
* function's retur type, if they don't match attempt coercion.
|
||||
*
|
||||
* On type check failure, an exception is thrown.
|
||||
*
|
||||
* On success, the `retjrnExpressionInstr` MAY be updated and then
|
||||
* we continue on.
|
||||
*/
|
||||
typeEnforce(functionReturnType, returnExpressionInstr, returnExpressionInstr, true);
|
||||
|
||||
/* Generate the instruction */
|
||||
returnInstr = new ReturnInstruction(returnExpressionInstr);
|
||||
}
|
||||
/* If not then this is an error */
|
||||
else
|
||||
|
|
|
@ -19,11 +19,14 @@ public class TypeCheckerException : TError
|
|||
GENERAL_ERROR
|
||||
}
|
||||
|
||||
private TypecheckError errType;
|
||||
|
||||
this(TypeChecker typeChecker, TypecheckError errType, string msg = "")
|
||||
{
|
||||
/* We set it after each child class calls this constructor (which sets it to empty) */
|
||||
super("TypeCheck Error ("~to!(string)(errType)~")"~(msg.length > 0 ? ": "~msg : ""));
|
||||
this.typeChecker = typeChecker;
|
||||
this.errType = errType;
|
||||
}
|
||||
|
||||
// TODO: Remove this constructor and make anything that is currently using it
|
||||
|
@ -32,10 +35,17 @@ public class TypeCheckerException : TError
|
|||
{
|
||||
this(typeChecker, TypecheckError.GENERAL_ERROR);
|
||||
}
|
||||
|
||||
public TypecheckError getError()
|
||||
{
|
||||
return errType;
|
||||
}
|
||||
}
|
||||
|
||||
public final class TypeMismatchException : TypeCheckerException
|
||||
{
|
||||
private Type originalType, attemptedType;
|
||||
|
||||
this(TypeChecker typeChecker, Type originalType, Type attemptedType, string msgIn = "")
|
||||
{
|
||||
super(typeChecker);
|
||||
|
@ -43,6 +53,46 @@ public final class TypeMismatchException : TypeCheckerException
|
|||
msg = "Type mismatch between type "~originalType.getName()~" and "~attemptedType.getName();
|
||||
|
||||
msg ~= msgIn.length > 0 ? ": "~msgIn : "";
|
||||
|
||||
this.originalType = originalType;
|
||||
this.attemptedType = attemptedType;
|
||||
}
|
||||
|
||||
public Type getExpectedType()
|
||||
{
|
||||
return originalType;
|
||||
}
|
||||
|
||||
public Type getAttemptedType()
|
||||
{
|
||||
return attemptedType;
|
||||
}
|
||||
}
|
||||
|
||||
public final class CoercionException : TypeCheckerException
|
||||
{
|
||||
private Type toType, fromType;
|
||||
|
||||
this(TypeChecker typeChecker, Type toType, Type fromType, string msgIn = "")
|
||||
{
|
||||
super(typeChecker);
|
||||
|
||||
msg = "Cannot coerce from type '"~fromType.getName()~"' to type '"~toType.getName()~"'";
|
||||
|
||||
msg ~= msgIn.length > 0 ? ": "~msgIn : "";
|
||||
|
||||
this.toType = toType;
|
||||
this.fromType = fromType;
|
||||
}
|
||||
|
||||
public Type getToType()
|
||||
{
|
||||
return toType;
|
||||
}
|
||||
|
||||
public Type getFromType()
|
||||
{
|
||||
return fromType;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,12 +2,12 @@ module simple_function_recursion_factorial;
|
|||
|
||||
ubyte factorial(ubyte i)
|
||||
{
|
||||
if(i == cast(ubyte)0)
|
||||
if(i == 0)
|
||||
{
|
||||
return cast(ubyte)1;
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
return i*factorial(i-cast(ubyte)1);
|
||||
return i*factorial(i-1);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
module simple_function_return_type_check_bad;
|
||||
|
||||
ubyte factorial(ubyte i)
|
||||
{
|
||||
return 256;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
module simple_function_return_type_check_good;
|
||||
|
||||
ubyte factorial(ubyte i)
|
||||
{
|
||||
return 1;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
module simple_coerce_literal_bad;
|
||||
|
||||
void function()
|
||||
{
|
||||
long i1 = 1;
|
||||
byte i = i1;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
module simple_coerce_literal_bad_stdalone_ass;
|
||||
|
||||
void function()
|
||||
{
|
||||
long i1 = 1;
|
||||
byte i;
|
||||
|
||||
i = i1;
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
module simple_coerce_literal_good;
|
||||
|
||||
void function()
|
||||
{
|
||||
byte i = 1UL;
|
||||
long i1 = i;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
module simple_coerce_literal_good_stdalone_ass;
|
||||
|
||||
void function()
|
||||
{
|
||||
byte i = 1UL;
|
||||
long i1;
|
||||
|
||||
i1 = i;
|
||||
}
|
Loading…
Reference in New Issue