🐞️ Functions: Expressionless return and enforcing requirement (#7)

* Parser

- Added a TODO in `parseReturn()` for issue #113

* Data

- The `ReturnStmt` now has a default constructor which is for cases where one doesn't want to provide an expression (for expressionless returns)

Parser

- `parseReturn()` now supports expressionless returns

Test cases

- Added `simple_return_expressionless.t` to test expressionless return statement

* Data

- Added a method `hasReturnExpression()` to `ReturnStmt` which returns `true` if the return statement has an expression attached, `false` otherwise

* Dependency

- When processing a `ReturnStmt` only run do dependency generation for the return statement's expression IF it has one

* Instruction

- Made `ReturnInstruction` have a constructor which takes in no `Value` instruction (intended for return expression)
- Added a `hasReturnExpInstr()` to `ReturnInstruction`such that during typechecking/codegen we can check for it

* TypeChecker

- Added a TODO regarding the missing typechecking for `ReturnStmt` typechecking. Added notes on how we'd go about this.
- Fixed crash due to assuming there was always an expression on the stack that could be popped off for generating a `ReturnInstruction` (this is not the case when the return statement is expressionless)

* Tests

- Added a typecheck test for `simple_return_expressionless.t`

* TypeChecker

- Update `isSameType(Type t1, Type t2)` to check if the actual types of both `Type` objects are the same as a last resort
- Added a `NOTE` comment on how `isSameType(Type t1, Type t2)` is implemented

- Added typechecking code for `ReturnStmt` and updated the code generation with it. We now do the following:
    1. We extract the container of the `ReturnStmt` and cast it to a `Function`; if it is not a `Function` we throw an error because you cannot have a `ReturnStmt` appear in a non-`Function` container
    2. We extract the function's name relative to it container (the function's container) for use of it in error messages
    3. Next, we get the return type of the function and do the following:
        a. If the return type is `void`
            i. If the return has an expression we throw an error
            ii. If the return has NO expression we pass typechecking and generate the `ReturnInstr`
        b. If the return type is non-`void`
            i. If the return has an expression we ensure that its type matches that of the function's return type and generate the `ReturnInstr`
            ii. If the return has NO expression we raise an exception as one is expected
    4. If we pass and got here then we set the `ReturnInstr`'s context and `addInstrB(returnInstr)`

* Test cases

- Added test case `simple_return_type.t` which is here to test our return type checking

* - Updated `.gitignore`

* Parser

- Use `lexer` for all `Token`-based operations
This commit is contained in:
Tristan B. Velloza Kildaire 2023-07-11 21:43:21 +02:00 committed by GitHub
parent c31175bae7
commit c0920f2fdb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 156 additions and 20 deletions

View File

@ -115,6 +115,10 @@ jobs:
exit 1
fi
- name: Simple return (expressionless)
run: ./tlang syntaxcheck source/tlang/testing/return/simple_return_expressionless.t
typecheck:
needs: [build, unittests]
name: Typechecking tests
@ -129,6 +133,14 @@ jobs:
- name: Chmod compiler
run: chmod +x tlang
- name: Simple return (expressionless)
run: ./tlang typecheck source/tlang/testing/return/simple_return_expressionless.t
- name: Simple return (with expression)
run: ./tlang typecheck source/tlang/testing/return/simple_return_type.t
- name: Simple function call
run: ./tlang typecheck source/tlang/testing/typecheck/simple_function_call.t

4
.gitignore vendored
View File

@ -17,5 +17,7 @@ dub.selections.json
# Documentation )generated)
docs/
docs.json
desktop_dev.code-workspace
# Codium workspace files
testing.code-workspace
desktop_dev.code-workspace

View File

@ -369,10 +369,20 @@ public final class ReturnInstruction : Instruction
this.returnExprInstr = returnExprInstr;
}
this()
{
}
public Value getReturnExpInstr()
{
return returnExprInstr;
}
public bool hasReturnExpInstr()
{
return returnExprInstr !is null;
}
}
public final class IfStatementInstruction : Instruction

View File

@ -606,20 +606,31 @@ public final class Parser
/* Move from `return` onto start of expression */
lexer.nextToken();
/* Parse the expression till termination */
Expression returnExpression = parseExpression();
// TODO: Check if semicolon here (no expression) else expect expression
/* Expect a semi-colon as the terminator */
gprintln(lexer.getCurrentToken());
expect(SymbolType.SEMICOLON, lexer.getCurrentToken());
/* If the next token after `return` is a `;` then it is an expressionless return */
if(getSymbolType(lexer.getCurrentToken()) == SymbolType.SEMICOLON)
{
/* Create the ReturnStmt (without an expression) */
returnStatement = new ReturnStmt();
}
/* Else, then look for an expression */
else
{
/* Parse the expression till termination */
Expression returnExpression = parseExpression();
/* Expect a semi-colon as the terminator */
gprintln(lexer.getCurrentToken());
expect(SymbolType.SEMICOLON, lexer.getCurrentToken());
/* Create the ReturnStmt */
returnStatement = new ReturnStmt(returnExpression);
}
/* Move off of the terminator */
lexer.nextToken();
/* Create the ReturnStmt */
returnStatement = new ReturnStmt(returnExpression);
return returnStatement;
}

View File

@ -966,6 +966,11 @@ public final class ReturnStmt : Statement
{
this.returnExpression = returnExpression;
this();
}
this()
{
/* Statement level weighting is 2 */
weight = 2;
}
@ -974,6 +979,11 @@ public final class ReturnStmt : Statement
{
return returnExpression;
}
public bool hasReturnExpression()
{
return returnExpression !is null;
}
}
/**

View File

@ -1674,19 +1674,89 @@ public final class TypeChecker
else if(cast(ReturnStmt)statement)
{
ReturnStmt returnStatement = cast(ReturnStmt)statement;
Function funcContainer = cast(Function)statement.parentOf();
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
*/
if(!funcContainer)
{
throw new TypeCheckerException(this, TypeCheckerException.TypecheckError.GENERAL_ERROR, "A return statement can only appear in the body of a function");
}
Type functionReturnType = getType(funcContainer, funcContainer.getType());
/**
* Codegen
*
* (1 and 2 only apply for return statements with an expression)
*
* 1. Pop the expression on the stack
* 2. Create a new ReturnInstruction with the expression instruction
* embedded in it
* 3. Set the Context of the instruction
* 4. Add this instruction back
*/
Value returnExpressionInstr = cast(Value)popInstr();
assert(returnExpressionInstr);
ReturnInstruction returnInstr = new ReturnInstruction(returnExpressionInstr);
/* If the function's return type is void */
if(isSameType(functionReturnType, getBuiltInType(this, "void")))
{
/* It is an error to have a return expression if function is return void */
if(returnStatement.hasReturnExpression())
{
throw new TypeCheckerException(this, TypeCheckerException.TypecheckError.GENERAL_ERROR, "Function '"~functionName~"' of type void cannot have a return expression");
}
/* If we don't have an expression (expected) */
else
{
/* Generate the instruction */
returnInstr = new ReturnInstruction();
}
}
/* If there is a non-void return type */
else
{
/* We should have an expression in the non-void case */
if(returnStatement.hasReturnExpression())
{
Value returnExpressionInstr = cast(Value)popInstr();
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()~"'");
}
}
/* If not then this is an error */
else
{
throw new TypeCheckerException(this, TypeCheckerException.TypecheckError.GENERAL_ERROR, "Function '"~functionName~"' of has a type therefore it requires an expression in the return statement");
}
}
/**
* Codegen (continued)
*
* 3. Set the Context of the instruction
* 4. Add this instruction back
*/
returnInstr.setContext(returnStatement.getContext());
addInstrB(returnInstr);
}

View File

@ -1462,12 +1462,16 @@ public class DNodeGenerator
DNode returnStatementDNode = pool(returnStatement);
/* Process the return expression */
Expression returnExpression = returnStatement.getReturnExpression();
DNode returnExpressionDNode = expressionPass(returnExpression, context);
/* Check if this return statement has an expression attached */
if(returnStatement.hasReturnExpression())
{
/* Process the return expression */
Expression returnExpression = returnStatement.getReturnExpression();
DNode returnExpressionDNode = expressionPass(returnExpression, context);
/* Make return depend on the return expression */
returnStatementDNode.needs(returnExpressionDNode);
/* Make return depend on the return expression */
returnStatementDNode.needs(returnExpressionDNode);
}
/* Make this container depend on this return statement */
// node.needs(returnStatementDNode);

View File

@ -0,0 +1,11 @@
module simple_return_expressionless;
void expressionless()
{
return;
}
int expressionful()
{
return 2;
}

View File

@ -0,0 +1,6 @@
module simple_return_type;
byte expressionful()
{
return 1;
}