Skip to content

Commit

Permalink
TC stubs
Browse files Browse the repository at this point in the history
  • Loading branch information
evan-schott committed Oct 12, 2023
1 parent 2b38326 commit 93b6159
Show file tree
Hide file tree
Showing 2 changed files with 165 additions and 76 deletions.
146 changes: 73 additions & 73 deletions compiler/passes/src/type_checking/check_program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,81 @@ impl<'a> ProgramVisitor<'a> for TypeChecker<'a> {
}
}

// Typecheck the program's stubs.
input.stubs.values().for_each(|stub| self.visit_stub(stub));

// Typecheck the program scopes.
input.program_scopes.values().for_each(|scope| self.visit_program_scope(scope));
}

fn visit_stub(&mut self, input: &'a Stub) {
// Cannot have mappings in stubs.
if input.mappings.len() != 0 {
self.emit_err(TypeCheckerError::stubs_can_only_have_records_and_transitions(
"mapping",
input.mappings.get(0).unwrap().1.span,
));
}

// Cannot have constant declarations in stubs.
if input.consts.len() != 0 {
self.emit_err(TypeCheckerError::stubs_can_only_have_records_and_transitions(
"constant declaration",
input.consts.get(0).unwrap().1.span,
));
}

// Typecheck the program's structs.
input.structs.iter().for_each(|(_, function)| self.visit_struct_stub(function));

// Typecheck the program's functions.
input.functions.iter().for_each(|(_, function)| self.visit_function_stub(function));
}

fn visit_function_stub(&mut self, input: &'a FunctionStub) {
// Cannot have finalize scopes
if input.finalize.is_some() {
self.emit_err(TypeCheckerError::stub_functions_must_have_no_finalize(
input.finalize.as_ref().unwrap().span,
));
}

// Must be transition functions
if input.variant != Variant::Transition {
self.emit_err(TypeCheckerError::stub_functions_must_be_transitions(input.span));
}

// Must be empty
if !input.block.statements.is_empty() {
self.emit_err(TypeCheckerError::stub_functions_must_be_empty(input.block.span));
}

// Lookup function metadata in the symbol table.
// Note that this unwrap is safe since function metadata is stored in a prior pass.
let function_index = self.symbol_table.borrow().lookup_fn_symbol(input.identifier.name).unwrap().id;

// Enter the function's scope.
self.enter_scope(function_index);

// Query helper function to type check function parameters and outputs.
self.check_function_signature(&Function::from(input.clone()));

// Exit the function's scope.
self.exit_scope(function_index);
}

fn visit_struct_stub(&mut self, input: &'a Struct) {
// Allow records only.
if !input.is_record {
self.emit_err(TypeCheckerError::stubs_can_only_have_records_and_transitions(
"non-record struct",
input.span,
));
}

self.visit_struct(input);
}

fn visit_program_scope(&mut self, input: &'a ProgramScope) {
// Typecheck each const definition, and append to symbol table.
input.consts.iter().for_each(|(_, c)| self.visit_const(c));
Expand Down Expand Up @@ -202,8 +273,6 @@ impl<'a> ProgramVisitor<'a> for TypeChecker<'a> {
self.emit_err(TypeCheckerError::unknown_annotation(annotation, annotation.span))
}

self.variant = Some(function.variant);

// Lookup function metadata in the symbol table.
// Note that this unwrap is safe since function metadata is stored in a prior pass.
let function_index = self.symbol_table.borrow().lookup_fn_symbol(function.identifier.name).unwrap().id;
Expand All @@ -223,77 +292,8 @@ impl<'a> ProgramVisitor<'a> for TypeChecker<'a> {
// Create a new child scope for the function's parameters and body.
let scope_index = self.create_child_scope();

// Type check the function's parameters.
function.input.iter().for_each(|input_var| {
// Check that the type of input parameter is defined.
self.assert_type_is_defined(&input_var.type_(), input_var.span());
// Check that the type of the input parameter is not a tuple.
if matches!(input_var.type_(), Type::Tuple(_)) {
self.emit_err(TypeCheckerError::function_cannot_take_tuple_as_input(input_var.span()))
}

// Note that this unwrap is safe since we assign to `self.variant` above.
match self.variant.unwrap() {
// If the function is a transition function, then check that the parameter mode is not a constant.
Variant::Transition if input_var.mode() == Mode::Constant => {
self.emit_err(TypeCheckerError::transition_function_inputs_cannot_be_const(input_var.span()))
}
// If the function is not a transition function, then check that the parameters do not have an associated mode.
Variant::Standard | Variant::Inline if input_var.mode() != Mode::None => {
self.emit_err(TypeCheckerError::regular_function_inputs_cannot_have_modes(input_var.span()))
}
_ => {} // Do nothing.
}

// Check for conflicting variable names.
if let Err(err) =
self.symbol_table.borrow_mut().insert_variable(input_var.identifier().name, VariableSymbol {
type_: input_var.type_(),
span: input_var.identifier().span(),
declaration: VariableType::Input(input_var.mode()),
})
{
self.handler.emit_err(err);
}
});

// Type check the function's return type.
// Note that checking that each of the component types are defined is sufficient to check that `output_type` is defined.
function.output.iter().for_each(|output| {
match output {
Output::External(external) => {
// If the function is not a transition function, then it cannot output a record.
// Note that an external output must always be a record.
if !matches!(function.variant, Variant::Transition) {
self.emit_err(TypeCheckerError::function_cannot_output_record(external.span()));
}
// Otherwise, do not type check external record function outputs.
// TODO: Verify that this is not needed when the import system is updated.
}
Output::Internal(function_output) => {
// Check that the type of output is defined.
if self.assert_type_is_defined(&function_output.type_, function_output.span) {
// If the function is not a transition function, then it cannot output a record.
if let Type::Identifier(identifier) = function_output.type_ {
if !matches!(function.variant, Variant::Transition)
&& self.symbol_table.borrow().lookup_struct(identifier.name).unwrap().is_record
{
self.emit_err(TypeCheckerError::function_cannot_output_record(function_output.span));
}
}
}
// Check that the type of the output is not a tuple. This is necessary to forbid nested tuples.
if matches!(&function_output.type_, Type::Tuple(_)) {
self.emit_err(TypeCheckerError::nested_tuple_type(function_output.span))
}
// Check that the mode of the output is valid.
// For functions, only public and private outputs are allowed
if function_output.mode == Mode::Constant {
self.emit_err(TypeCheckerError::cannot_have_constant_output_mode(function_output.span));
}
}
}
});
// Query helper function to type check function parameters and outputs.
self.check_function_signature(function);

self.visit_block(&function.block);

Expand Down
95 changes: 92 additions & 3 deletions compiler/passes/src/type_checking/checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,21 @@
// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.

use crate::{CallGraph, StructGraph, SymbolTable};

use leo_ast::{CoreConstant, CoreFunction, Identifier, IntegerType, MappingType, Node, Type, Variant};
use crate::{CallGraph, StructGraph, SymbolTable, VariableSymbol, VariableType};

use leo_ast::{
CoreConstant,
CoreFunction,
Function,
Identifier,
IntegerType,
MappingType,
Mode,
Node,
Output,
Type,
Variant,
};
use leo_errors::{emitter::Handler, TypeCheckerError};
use leo_span::{Span, Symbol};

Expand Down Expand Up @@ -1092,6 +1104,83 @@ impl<'a> TypeChecker<'a> {
_ => None,
}
}

/// Helper function to check that the input and output of function are valid
pub(crate) fn check_function_signature(&mut self, function: &Function) {
self.variant = Some(function.variant);

// Type check the function's parameters.
function.input.iter().for_each(|input_var| {
// Check that the type of input parameter is defined.
self.assert_type_is_defined(&input_var.type_(), input_var.span());
// Check that the type of the input parameter is not a tuple.
if matches!(input_var.type_(), Type::Tuple(_)) {
self.emit_err(TypeCheckerError::function_cannot_take_tuple_as_input(input_var.span()))
}

// Note that this unwrap is safe since we assign to `self.variant` above.
match self.variant.unwrap() {
// If the function is a transition function, then check that the parameter mode is not a constant.
Variant::Transition if input_var.mode() == Mode::Constant => {
self.emit_err(TypeCheckerError::transition_function_inputs_cannot_be_const(input_var.span()))
}
// If the function is not a transition function, then check that the parameters do not have an associated mode.
Variant::Standard | Variant::Inline if input_var.mode() != Mode::None => {
self.emit_err(TypeCheckerError::regular_function_inputs_cannot_have_modes(input_var.span()))
}
_ => {} // Do nothing.
}

// Check for conflicting variable names.
if let Err(err) =
self.symbol_table.borrow_mut().insert_variable(input_var.identifier().name, VariableSymbol {
type_: input_var.type_(),
span: input_var.identifier().span(),
declaration: VariableType::Input(input_var.mode()),
})
{
self.handler.emit_err(err);
}
});

// Type check the function's return type.
// Note that checking that each of the component types are defined is sufficient to check that `output_type` is defined.
function.output.iter().for_each(|output| {
match output {
Output::External(external) => {
// If the function is not a transition function, then it cannot output a record.
// Note that an external output must always be a record.
if !matches!(function.variant, Variant::Transition) {
self.emit_err(TypeCheckerError::function_cannot_output_record(external.span()));
}
// Otherwise, do not type check external record function outputs.
// TODO: Verify that this is not needed when the import system is updated.
}
Output::Internal(function_output) => {
// Check that the type of output is defined.
if self.assert_type_is_defined(&function_output.type_, function_output.span) {
// If the function is not a transition function, then it cannot output a record.
if let Type::Identifier(identifier) = function_output.type_ {
if !matches!(function.variant, Variant::Transition)
&& self.symbol_table.borrow().lookup_struct(identifier.name).unwrap().is_record
{
self.emit_err(TypeCheckerError::function_cannot_output_record(function_output.span));
}
}
}
// Check that the type of the output is not a tuple. This is necessary to forbid nested tuples.
if matches!(&function_output.type_, Type::Tuple(_)) {
self.emit_err(TypeCheckerError::nested_tuple_type(function_output.span))
}
// Check that the mode of the output is valid.
// For functions, only public and private outputs are allowed
if function_output.mode == Mode::Constant {
self.emit_err(TypeCheckerError::cannot_have_constant_output_mode(function_output.span));
}
}
}
});
}
}

fn types_to_string(types: &[Type]) -> String {
Expand Down

0 comments on commit 93b6159

Please sign in to comment.