diff --git a/src/Rust/vvs_parser/src/ast/punctuated.rs b/src/Rust/vvs_parser/src/ast/punctuated.rs
index cb070f68f3c502458ad97a7220b3e112f553c865..f1e40f7bdf7bb89d30bb2c0249b3646cdbf9788a 100644
--- a/src/Rust/vvs_parser/src/ast/punctuated.rs
+++ b/src/Rust/vvs_parser/src/ast/punctuated.rs
@@ -128,6 +128,18 @@ impl<T> Punctuated<T> {
         self.pairs.first()
     }
 
+    /// Returns the first value in the sequence
+    /// ```rust
+    /// # use vvs_parser::prelude::ast::*;
+    /// let mut punctuated = Punctuated::new();
+    /// assert_eq!(punctuated.first(), None);
+    /// punctuated.push(Pair::new(1, None));
+    /// assert_eq!(punctuated.first_value(), Some(&1));
+    /// ```
+    pub fn first_value(&self) -> Option<&T> {
+        self.pairs.first().map(Pair::value)
+    }
+
     /// Returns the last pair in the sequence
     /// ```rust
     /// # use vvs_parser::prelude::ast::*;
@@ -139,6 +151,17 @@ impl<T> Punctuated<T> {
         self.pairs.last()
     }
 
+    /// Returns the last value in the sequence
+    /// ```rust
+    /// # use vvs_parser::prelude::ast::*;
+    /// let mut punctuated = Punctuated::new();
+    /// punctuated.push(Pair::new(1, None));
+    /// assert_eq!(punctuated.last_value(), Some(&1));
+    /// ```
+    pub fn last_value(&self) -> Option<&T> {
+        self.pairs.last().map(Pair::value)
+    }
+
     /// Returns an iterator over pairs as references
     /// ```rust
     /// # use vvs_parser::prelude::ast::*;
diff --git a/src/Rust/vvs_parser/src/vivy/frontend_pipeline.rs b/src/Rust/vvs_parser/src/vivy/frontend_pipeline.rs
index 53bc5d77bf182921be794636546e8a6e68e3b9b3..ee70bd1e09c30a09d61a3e7998793e817b9730f7 100644
--- a/src/Rust/vvs_parser/src/vivy/frontend_pipeline.rs
+++ b/src/Rust/vvs_parser/src/vivy/frontend_pipeline.rs
@@ -18,6 +18,7 @@ use crate::{
 use derive_more::Display;
 use hashbrown::{HashMap, HashSet};
 use std::{borrow::Cow, cell::RefCell, fmt, fs, mem, path::Path, rc::Rc};
+use symbol_table::SymbolTableLocked;
 
 /// Process a program.
 pub struct FrontendPipeline<'a> {
@@ -31,7 +32,7 @@ pub struct FrontendPipeline<'a> {
     imports: HashMap<ShortString, Rc<(Ast, Cow<'static, str>)>>,
 
     /// List of imported symbol tables.
-    symbols: Vec<Rc<SymbolTable<'a, TypeInfo>>>,
+    symbols: Vec<Rc<SymbolTable<'a, TypeInfo, SymbolTableLocked>>>,
 
     /// The search path.
     search: &'a SearchPath,
@@ -280,8 +281,8 @@ impl<'a> FrontendPipeline<'a> {
             let _ = ErrorReport::new().add_errors(self.name(), &self.program, err).report();
         })?;
 
-        let (ast, StmtCheckerResult { decl_opts, setd_opts, main, imported }) =
-            StmtChecker::new(ast, (self.name().as_str(), self.program.as_ref()))
+        let StmtCheckerResult { decl_opts, setd_opts, main, imported } =
+            StmtChecker::new(&ast, (self.name().as_str(), self.program.as_ref()))
                 .is_main(self.is_main)
                 .process()?;
         let ImportAllResult { mut declared_options, unused } = self.import_all(imported)?;
@@ -292,13 +293,18 @@ impl<'a> FrontendPipeline<'a> {
                 .map(|(name, value)| (self.name().clone(), name, value.into())),
         );
 
-        let symbols = SymbolTable::new(&ast, self.options.as_deref())
-            .import(self.symbols.iter().map(Rc::as_ref))
-            .take();
+        let symbols = SymbolTable::try_from(&ast)
+            .map_err(|err| {
+                let _ = ErrorReport::new()
+                    .add_errors(self.name(), &self.program, [err])
+                    .report();
+            })?
+            .import(self.symbols.iter().map(Rc::as_ref));
 
         let loc = (self.name().as_str(), self.program.as_ref());
-        let (ast, _) = TypeCollector::new(ast, loc, &mut self.library.borrow_mut()).process()?;
-        let (ast, _) = TypeChecker::new(ast, loc, &self.library.borrow(), symbols.sub_scope()).process()?;
+        TypeCollector::new(&ast, loc, &mut self.library.borrow_mut()).process()?;
+        TypeChecker::new(&ast, loc, &self.library.borrow(), symbols).process()?;
+
         let (ast, OptsTransformResult { unused }) = match self.options.clone() {
             None => (ast, Default::default()),
             Some(options) => {
diff --git a/src/Rust/vvs_parser/src/vivy/library.rs b/src/Rust/vvs_parser/src/vivy/library.rs
index 95d15e5fda3e38fd8201408661bce30db20f1e4e..bb8466a340b9bb827287588b926b9271fdf5cb96 100644
--- a/src/Rust/vvs_parser/src/vivy/library.rs
+++ b/src/Rust/vvs_parser/src/vivy/library.rs
@@ -1,7 +1,7 @@
 //! Store informations about methods and fields for types.
 
 use crate::{ast::TypeInfo, ShortString};
-use derive_more::{Display, IntoIterator, IsVariant};
+use derive_more::{IntoIterator, IsVariant};
 use hashbrown::HashMap;
 use std::{fmt, sync::LazyLock};
 
diff --git a/src/Rust/vvs_parser/src/vivy/passes/mod.rs b/src/Rust/vvs_parser/src/vivy/passes/mod.rs
index 6c6f8e65044f33f3832a102c727ceadebf32045d..b101e45a4e91089a54927ea4643d3d83e8507b87 100644
--- a/src/Rust/vvs_parser/src/vivy/passes/mod.rs
+++ b/src/Rust/vvs_parser/src/vivy/passes/mod.rs
@@ -5,8 +5,6 @@ mod stmt_checker;
 mod type_checker;
 mod type_collector;
 
-pub use self::{opts_transform::*, stmt_checker::*, type_checker::*, type_collector::*};
-
 /// Macro used to declare a pass, can be a transform pass or a visit pass (for the role).
 /// ```ignore
 /// declare_pass! {
@@ -17,21 +15,100 @@ pub use self::{opts_transform::*, stmt_checker::*, type_checker::*, type_collect
 /// };
 /// impl VisitorMut for OptsTransform {}
 /// ```
-#[macro_export]
 macro_rules! declare_pass {
-    (
-        $(#[$meta: meta])*
-        $role: ident :: $name: ident : $lifetime: lifetime {
+    ($(#[$meta: meta])* $role: ident :: $name: ident : $lifetime: lifetime {
             $($field_attr: ident : $field_ty: ty),* $(,)?
         }
+        $(aux    { $($info_attr: ident : $info_ty: ty),+$(,)? })?
+        $(opt    { $($opts_attr: ident : $opts_ty: ty),+$(,)? })?
+        $(result { $($res_attr:  ident : $res_ty:  ty),+$(,)? })?
+    ) => {
+        $crate::vivy::passes::declare_pass! { @struct-decl $(#[$meta])* $role :: $name : $lifetime
+            { $(  $field_attr : $field_ty),*   }
+            { $($($info_attr  : $info_ty ),+)? }
+            { $($($opts_attr  : $opts_ty ),+)? }
+            { $($($res_attr   : $res_ty  ),+)? }
+        }
+
+        impl <$lifetime> $name <$lifetime> {
+            $crate::vivy::passes::declare_pass! { @constructor $role :: $name : $lifetime
+                { $(  $field_attr : $field_ty),*   }
+                { $($($info_attr  : $info_ty ),+)? }
+                { $($($opts_attr  : $opts_ty ),+)? }
+                { $($($res_attr   : $res_ty  ),+)? }
+            }
+
+            /// Add a new ast error to the error list.
+            #[allow(dead_code)]
+            fn error(&mut self, token: impl Into<$crate::tokenizer::TokenReference>, error: impl Into<std::borrow::Cow<'static, str>>) {
+                self.report.add_errors(self.name, self.source, [$crate::ast::AstError::from_parts(token.into(), error)]);
+            }
+
+            /// Add a new ast warning to the error list.
+            #[allow(dead_code)]
+            fn warning(&mut self, token: impl Into<$crate::tokenizer::TokenReference>, error: impl Into<std::borrow::Cow<'static, str>>) {
+                self.report.add_warnings(self.name, self.source, [$crate::ast::AstError::from_parts(token.into(), error)]);
+            }
 
-        $(aux_infos { $($info_attr: ident : $info_ty: ty),+$(,)? })?
-        $(opt_infos { $($opts_attr: ident : $opts_ty: ty),+$(,)? })?
-        $(results   { $($res_attr:  ident : $res_ty:  ty),+$(,)? })?
+            paste::paste! {
+                $($(
+                    #[doc = "Set the optional information '" $opts_attr "' for the [" $name "] pass."]
+                    #[allow(clippy::wrong_self_convention)]
+                    pub fn $opts_attr(self, $opts_attr: $opts_ty) -> Self {
+                        Self { $opts_attr: Some($opts_attr), ..self }
+                    }
+                )+)?
+
+                #[doc = "Apply the [" $name "] pass and get the resulting [vvs_parser::ast::Ast] or the list of the errors."]
+                #[allow(unused_imports, unused_variables)]
+                pub fn process(mut self) -> $crate::vivy::passes::declare_pass!(@pass-returns $role :: $name) {
+                    use $crate::visitors::{Visitor as _, VisitorMut as _};
+                    let ast = self.program.take().unwrap();
+                    let ast = self.visit_ast(ast);
+                    (!self.report.report()).then_some(()).ok_or(())?;
+                    $crate::vivy::passes::declare_pass!(@returns $role :: $name (ast, [< $name Result >] {
+                        $($($res_attr: self.$res_attr),+)?
+                    }))
+                }
+            }
+        }
+    };
+
+
+    (@constructor $role: ident :: $name: ident : $lifetime: lifetime
+        { $($field_attr: ident : $field_ty: ty),* }
+        { $($info_attr:  ident : $info_ty:  ty),* }
+        { $($opts_attr:  ident : $opts_ty:  ty),* }
+        { $($res_attr:   ident : $res_ty:   ty),* }
+    ) => {
+        paste::paste! {
+            #[doc = "Create a new [" $name "] pass."]
+            pub fn new(
+                program: $crate::vivy::passes::declare_pass!(@decl-program $role $lifetime),
+                (name, source): (&$lifetime str, &$lifetime str)
+                $(, $info_attr: $info_ty)*
+            ) -> Self {
+                Self {
+                    program: Some(program),
+                    report: Default::default(),
+                    name, source, $($info_attr,)*
+                    $($field_attr: Default::default(),)*
+                    $($opts_attr:  None,)*
+                    $($res_attr:   Default::default(),)*
+                }
+            }
+        }
+    };
+
+    (@struct-decl $(#[$meta: meta])* $role: ident :: $name: ident : $lifetime: lifetime
+        { $(  $field_attr: ident : $field_ty: ty),*$(,)?   }
+        { $($($info_attr:  ident : $info_ty:  ty),+$(,)?)? }
+        { $($($opts_attr:  ident : $opts_ty:  ty),+$(,)?)? }
+        { $($($res_attr:   ident : $res_ty:   ty),+$(,)?)? }
     ) => {
         $(#[$meta])*
         pub struct $name <$lifetime> {
-            program: Option<$crate::ast::Ast>,
+            program: Option<$crate::vivy::passes::declare_pass!(@decl-program $role $lifetime)>,
             name:    &$lifetime str,
             source:  &$lifetime str,
             report:  $crate::vivy::error_report::ErrorReport<$lifetime>,
@@ -49,80 +126,24 @@ macro_rules! declare_pass {
             }
         }
 
-        impl <$lifetime> $name <$lifetime> {
-            paste::paste! {
-                #[doc = "Create a new [" $name "] pass."]
-                pub fn new(
-                    program:        $crate::ast::Ast,
-                    (name, source): (&$lifetime str, &$lifetime str)
-                    $($(, $info_attr: $info_ty)+)?
-                ) -> Self {
-                    Self {
-                        program: Some(program), name, source,
-                        report: $crate::vivy::error_report::ErrorReport::new(),
-                        $($($info_attr),+,)?
-                        $(  $field_attr: Default::default(),)*
-                        $($($opts_attr:  None),+,)?
-                        $($($res_attr:   Default::default()),+,)?
-                    }
-                }
-
-                /// Add a new ast error to the error list.
-                #[allow(dead_code)]
-                fn error(&mut self, token: impl Into<$crate::tokenizer::TokenReference>, error: impl Into<std::borrow::Cow<'static, str>>) {
-                    self.report.add_errors(
-                        self.name,
-                        self.source,
-                        [$crate::Error::AstError($crate::ast::AstError::from_parts(token.into(), error))]
-                    );
-                }
-
-                /// Add a new ast warning to the error list.
-                #[allow(dead_code)]
-                fn warning(&mut self, token: impl Into<$crate::tokenizer::TokenReference>, error: impl Into<std::borrow::Cow<'static, str>>) {
-                    self.report.add_warnings(
-                        self.name,
-                        self.source,
-                        [$crate::Error::AstError($crate::ast::AstError::from_parts(token.into(), error))]
-                    );
-                }
-
-                $($(
-                    #[doc = "Set the optional information '" $opts_attr "' for the [" $name "] pass."]
-                    #[allow(clippy::wrong_self_convention)]
-                    pub fn $opts_attr(self, $opts_attr: $opts_ty) -> Self {
-                        Self { $opts_attr: Some($opts_attr), ..self }
-                    }
-                )+)?
-
-                #[doc = "Apply the [" $name "] pass and get the resulting [vvs_parser::ast::Ast] or the list of the errors."]
-                pub fn process(mut self) -> Result<($crate::ast::Ast, [< $name Result >]), ()> {
-                    let ast = self.program.take().unwrap();
-                    let ast = $crate::declare_pass!(@apply_on_ast $role (self, ast));
-                    match self.report.report() {
-                        true => Err(()),
-                        false => Ok((ast, [< $name Result >] {
-                            $($($res_attr: self.$res_attr),+)?
-                        })),
-                    }
-                }
-            }
-        }
     };
 
-    (@apply_on_ast transform ($self: ident, $ast: expr)) => {{
-        use $crate::visitors::VisitorMut;
-        $self.visit_ast($ast)
-    }};
+    (@pass-returns visit     :: $name: ident) => { paste::paste! { Result<[< $name Result >], ()> } };
+    (@pass-returns transform :: $name: ident) => { paste::paste! { Result<($crate::ast::Ast, [< $name Result >]), ()> } };
 
-    (@apply_on_ast visit ($self: ident, $ast: expr)) => {{
-        use $crate::visitors::Visitor;
-        $self.visit_ast(&$ast);
-        $ast
-    }};
+    (@returns visit     :: $name: ident ($ast: expr, $result: expr)) => { Ok(($result)) };
+    (@returns transform :: $name: ident ($ast: expr, $result: expr)) => { Ok((($ast, $result))) };
+
+    (@decl-program visit     $lifetime: lifetime) => { &$lifetime $crate::ast::Ast };
+    (@decl-program transform $lifetime: lifetime) => { $crate::ast::Ast };
 }
+use declare_pass;
+
+pub use self::{opts_transform::*, stmt_checker::*, type_checker::*, type_collector::*};
 
-/// Trait used to provide a way for users to create their own passes
+/// Trait used to provide a way for users to create their own passes.
+///
+/// We don't want to expose the [declare_pass] macro for simplicity sake.
 pub trait UserFrontendPass {
     /// Get the name of the pass. For logging purposes.
     fn name(&self) -> &'static str;
diff --git a/src/Rust/vvs_parser/src/vivy/passes/opts_transform.rs b/src/Rust/vvs_parser/src/vivy/passes/opts_transform.rs
index 6c60caf8b37f6b40b1aa3d9b881199b8f9ca5e8e..d6976e49b2803a4a1c2e42aa39e4530137b6d3ac 100644
--- a/src/Rust/vvs_parser/src/vivy/passes/opts_transform.rs
+++ b/src/Rust/vvs_parser/src/vivy/passes/opts_transform.rs
@@ -1,13 +1,13 @@
-use crate::{ast::*, node::Node, tokenizer::*, ShortString};
+use crate::{ast::*, node::Node, tokenizer::*, vivy::passes::declare_pass, ShortString};
 
-crate::declare_pass! {
+declare_pass! {
     /// The option optimization is here to substitute the options with the values chosen by the
     /// user.
     #[allow(dead_code)]
     transform::OptsTransform: 'a {}
 
-    aux_infos { options: &'a OptionTable                 }
-    results   { unused:  Vec<(ShortString, ShortString)> }
+    aux    { options: &'a OptionTable                 }
+    result { unused:  Vec<(ShortString, ShortString)> }
 }
 
 impl crate::visitors::VisitorMut for OptsTransform<'_> {
diff --git a/src/Rust/vvs_parser/src/vivy/passes/stmt_checker.rs b/src/Rust/vvs_parser/src/vivy/passes/stmt_checker.rs
index 7dbb413a4eeb258749e0d7fb77d369e8a6f546f3..0bb444e7dab648e2f7e1ee2a2a192d4502125e01 100644
--- a/src/Rust/vvs_parser/src/vivy/passes/stmt_checker.rs
+++ b/src/Rust/vvs_parser/src/vivy/passes/stmt_checker.rs
@@ -3,18 +3,21 @@ use crate::{
     node::Node,
     tokenizer::{Token, TokenKind, TokenReference, TokenType},
     visitors::Visit,
-    vivy::main_program::{MainProgram, MainProgramCallListItem, MainProgramStep},
+    vivy::{
+        main_program::{MainProgram, MainProgramCallListItem, MainProgramStep},
+        passes::declare_pass,
+    },
     ShortString,
 };
 
-crate::declare_pass! {
+declare_pass! {
     /// The statement checker is here to verify that statements are not used in incorrect positions.
     visit::StmtChecker: 'a {
         in_job: bool,
     }
 
-    opt_infos { is_main: bool }
-    results   {
+    opt    { is_main: bool }
+    result {
         imported:  Vec<TokenReference>,
         setd_opts: Vec<(ShortString, ShortString, Expression)>,
         decl_opts: Vec<(ShortString, OptionDefaultValue)>,
diff --git a/src/Rust/vvs_parser/src/vivy/passes/type_checker.rs b/src/Rust/vvs_parser/src/vivy/passes/type_checker.rs
index 712253f5417f5617ee3684b2ef2418dcaa666607..71556dbb4f257f7e5479e2af43560d27e189c17f 100644
--- a/src/Rust/vvs_parser/src/vivy/passes/type_checker.rs
+++ b/src/Rust/vvs_parser/src/vivy/passes/type_checker.rs
@@ -3,22 +3,25 @@ use crate::{
     node::Node,
     tokenizer::*,
     traits::DefaultRef,
-    vivy::{library::*, symbol_table::SymbolTable},
+    vivy::{library::*, passes::declare_pass, symbol_table::*},
 };
 
-crate::declare_pass! {
+declare_pass! {
     /// The type checker is here to verify type corectness of the program and infer types, etc. To
     /// check the types we will need to do custom things...
+    ///
+    /// TODO: Verify unique functions at top level, with correct names
     visit::TypeChecker: 'a {}
 
-    aux_infos {
+    aux {
         library: &'a Library,
-        symbol_table: SymbolTable<'a, TypeInfo>,
+        symbols: SymbolTable<'a, TypeInfo, SymbolTableLocked>,
     }
 }
 
 impl crate::visitors::Visitor for TypeChecker<'_> {
     fn visit_ast(&mut self, ast: &Ast) {
+        log::error!("todo: verify unique functions at top level, with correct names");
         for error in Vec::from_iter(ast.nodes().stmts().flat_map(|stmt| match stmt {
             Stmt::JobDeclaration(decl) => self.type_compute().callable(decl.body(), true).err(),
             Stmt::FunctionDeclaration(decl) => self.type_compute().callable(decl.body(), false).err(),
@@ -28,8 +31,7 @@ impl crate::visitors::Visitor for TypeChecker<'_> {
             Stmt::TypeDeclaration(_) => unimplemented!("custom types are tbd"),
             _ => unreachable!("already checked that they are not here"),
         })) {
-            self.report
-                .add_errors(self.name, self.source, [crate::Error::AstError(*error)]);
+            self.report.add_errors(self.name, self.source, [*error]);
         }
         if !ast.eof().is_kind(TokenKind::Eof) {
             self.error(ast.eof(), "expected eof token here");
@@ -39,21 +41,21 @@ impl crate::visitors::Visitor for TypeChecker<'_> {
 
 impl TypeChecker<'_> {
     fn type_compute(&self) -> TypeCompute {
-        TypeCompute::new(self.library, self.symbol_table.sub_scope())
+        TypeCompute::new(self.library, self.symbols.sub_scope())
     }
 }
 
 /// The thing that will actually compute types and returns errors in case of problems.
 struct TypeCompute<'a> {
     library: &'a Library,
-    symbols: SymbolTable<'a, TypeInfo>,
+    symbols: SymbolTable<'a, TypeInfo, SymbolTableMutable>,
     in_loop: bool,
     in_job: bool,
     returns: &'a TypeInfo,
 }
 
 type TypeComputeError = Box<AstError>;
-type TypeComputeResult<'a> = Result<(TypeInfo, SymbolTable<'a, TypeInfo>), TypeComputeError>;
+type TypeComputeResult<'a> = Result<(TypeInfo, SymbolTable<'a, TypeInfo, SymbolTableMutable>), TypeComputeError>;
 
 macro_rules! box_error {
     ($token: expr, $format: literal                   $(,)?) => { Box::new(AstError::from_parts($token.into(), format!($format            ))) };
@@ -65,12 +67,12 @@ fn unsupported<T>(token: &TokenReference, whats: &str) -> Result<T, TypeComputeE
 }
 
 impl<'a> TypeCompute<'a> {
-    fn new(library: &'a Library, symbols: SymbolTable<'a, TypeInfo>) -> Self {
+    fn new(library: &'a Library, symbols: SymbolTable<'a, TypeInfo, SymbolTableMutable>) -> Self {
         Self { library, symbols, returns: TypeInfo::default_ref(), in_loop: false, in_job: false }
     }
 
     fn sub(&'a self) -> Self {
-        Self { symbols: self.symbols.sub_scope(), ..*self }
+        Self { symbols: self.symbols.clone(), ..*self }
     }
 
     fn set(mut self, symbol: &TokenReference, ty: TypeInfo) -> Result<Self, TypeComputeError> {
@@ -155,7 +157,7 @@ impl<'a> TypeCompute<'a> {
                 let mut arguments = args.iter().enumerate().zip(arguments.iter());
                 match arguments.find_map(|((idx, arg), type_info)| {
                     self.sub()
-                        .expression_as_type(type_info, arg)
+                        .expr_as_type(type_info, arg)
                         .is_err()
                         .then(|| (idx, arg.tokens().next().unwrap(), type_info))
                 }) {
@@ -166,9 +168,9 @@ impl<'a> TypeCompute<'a> {
         }
     }
 
-    fn expression(self, expression: &'a Expression) -> TypeComputeResult<'a> {
+    fn expr(self, expression: &'a Expression) -> TypeComputeResult<'a> {
         match expression {
-            Expression::Parentheses { expression, .. } => self.expression(expression),
+            Expression::Parentheses { expression, .. } => self.expr(expression),
             Expression::FunctionCall(call) => self.suffixes(call.prefix(), call.suffixes()),
 
             Expression::Number(_) => Ok((TypeInfo::number().clone(), self.symbols)),
@@ -194,7 +196,7 @@ impl<'a> TypeCompute<'a> {
 
             Expression::UnaryOperator { unop, expression } => {
                 use {TypeInfo::*, UnOp::*};
-                let (ty, symbols) = self.expression(expression)?;
+                let (ty, symbols) = self.expr(expression)?;
                 let is_container =
                     matches!(ty, Array { .. } | Basic(_) | Optional { .. } | Table { .. } | Tuple { .. });
                 match unop {
@@ -208,8 +210,8 @@ impl<'a> TypeCompute<'a> {
 
             Expression::BinaryOperator { lhs, binop, rhs } => {
                 use BinOp::*;
-                let ty = self.sub().expression(lhs)?.0;
-                self.sub().expression_as_type(&ty, rhs)?;
+                let ty = self.sub().expr(lhs)?.0;
+                self.sub().expr_as_type(&ty, rhs)?;
                 match binop {
                     TwoEqual(_) | TildeEqual(_) => Ok((TypeInfo::number().clone(), self.symbols)),
                     TwoDots(_) if ty == *TypeInfo::string() => Ok((TypeInfo::number().clone(), self.symbols)),
@@ -238,7 +240,7 @@ impl<'a> TypeCompute<'a> {
 
     fn suffixes(self, prefix: &'a Prefix, suffixes: impl Iterator<Item = &'a Suffix> + 'a) -> TypeComputeResult<'a> {
         let prefix = match prefix {
-            Prefix::Expression(expression) => self.sub().expression(expression)?.0,
+            Prefix::Expression(expression) => self.sub().expr(expression)?.0,
             Prefix::Name(token) => self
                 .symbols
                 .get(token.token_type().as_str())
@@ -264,7 +266,7 @@ impl<'a> TypeCompute<'a> {
                 },
                 Suffix::Index(Index::Brackets { expression, .. }) => self
                     .sub()
-                    .expression_as_type(TypeInfo::number(), expression)
+                    .expr_as_type(TypeInfo::number(), expression)
                     .map(|_| *type_info),
                 _ => unreachable!(),
             },
@@ -356,8 +358,8 @@ impl<'a> TypeCompute<'a> {
         Ok((ty, self.symbols))
     }
 
-    fn expression_as_type(self, as_ty: &TypeInfo, expression: &'a Expression) -> Result<(), TypeComputeError> {
-        let (ty, _) = self.sub().expression(expression)?;
+    fn expr_as_type(self, as_ty: &TypeInfo, expression: &'a Expression) -> Result<(), TypeComputeError> {
+        let (ty, _) = self.sub().expr(expression)?;
         (ty == *as_ty).then_some(()).ok_or_else(|| {
             box_error!(expression.tokens().next().unwrap(), "the expression should be of type '{as_ty}', got: '{ty}'",)
         })
@@ -370,22 +372,20 @@ impl<'a> TypeCompute<'a> {
             .zip(assignment.expressions());
         let variables = variables.map(|((name, ty), val)| {
             let ty = match ty {
-                None => self.sub().expression(val)?.0,
+                None => self.sub().expr(val)?.0,
                 Some(ty) => {
-                    self.sub().expression_as_type(ty, val)?;
+                    self.sub().expr_as_type(ty, val)?;
                     ty.clone()
                 }
             };
             Ok::<_, TypeComputeError>((name.token_type().as_str().into(), ty))
         });
-        let symbols = self
-            .symbols
+        self.symbols
             .extend(variables.collect::<Result<Vec<_>, TypeComputeError>>()?)
             .map_err(|variable| {
                 box_error!(assignment.local_token(), "can't declare {variable} because it shadows a global variable",)
-            })?
-            .take();
-        Ok((TypeInfo::default(), symbols))
+            })?;
+        Ok((TypeInfo::default(), self.symbols))
     }
 
     fn compound_assignment(self, assignment: &'a CompoundAssignment) -> TypeComputeResult<'a> {
@@ -404,11 +404,11 @@ impl<'a> TypeCompute<'a> {
         match variable {
             Var::Expression(_) => todo!(),
             Var::Name(variable) => match variable.token_type() {
-                TokenType::Identifier { identifier } if self.symbols.root().get(identifier).is_some() => {
+                TokenType::Identifier { identifier } if self.symbols.is_global(identifier) => {
                     Err(box_error!(variable, "try to assign a global variable"))
                 }
                 TokenType::Identifier { identifier } => match self.symbols.get(identifier).cloned() {
-                    Some(ty) => self.expression_as_type(&ty, expression),
+                    Some(ty) => self.expr_as_type(&ty, expression),
                     None => Err(box_error!(variable, "try to assign an undefined variable")),
                 },
                 _ => unreachable!(),
@@ -426,10 +426,10 @@ impl<'a> TypeCompute<'a> {
             Stmt::Do(block) => self.block(block.block()),
 
             Stmt::If(if_stmt) => {
-                self.sub().expression_as_type(TypeInfo::number(), if_stmt.condition())?;
+                self.sub().expr_as_type(TypeInfo::number(), if_stmt.condition())?;
                 self.sub().block(if_stmt.block())?;
                 for elseif in if_stmt.else_if().into_iter().flatten() {
-                    self.sub().expression_as_type(TypeInfo::number(), elseif.condition())?;
+                    self.sub().expr_as_type(TypeInfo::number(), elseif.condition())?;
                     self.sub().block(elseif.block())?;
                 }
                 if let Some(block) = if_stmt.else_block() {
@@ -442,22 +442,21 @@ impl<'a> TypeCompute<'a> {
                 let symbols = self.sub().block(repeat_stmt.block())?.1;
                 TypeCompute { symbols, ..self }
                     .in_loop()
-                    .expression_as_type(TypeInfo::number(), repeat_stmt.until())?;
+                    .expr_as_type(TypeInfo::number(), repeat_stmt.until())?;
                 Ok((TypeInfo::default(), self.symbols))
             }
 
             Stmt::While(while_stmt) => {
-                self.sub()
-                    .expression_as_type(TypeInfo::number(), while_stmt.condition())?;
+                self.sub().expr_as_type(TypeInfo::number(), while_stmt.condition())?;
                 self.sub().in_loop().block(while_stmt.block())?;
                 Ok((TypeInfo::default(), self.symbols))
             }
 
             Stmt::NumericFor(for_stmt) => {
-                self.sub().expression_as_type(TypeInfo::number(), for_stmt.start())?;
-                self.sub().expression_as_type(TypeInfo::number(), for_stmt.end())?;
+                self.sub().expr_as_type(TypeInfo::number(), for_stmt.start())?;
+                self.sub().expr_as_type(TypeInfo::number(), for_stmt.end())?;
                 if let Some(step) = for_stmt.step() {
-                    self.sub().expression_as_type(TypeInfo::number(), step)?;
+                    self.sub().expr_as_type(TypeInfo::number(), step)?;
                 }
                 self.sub()
                     .set(for_stmt.index_variable(), TypeInfo::number().clone())?
@@ -492,7 +491,7 @@ impl<'a> TypeCompute<'a> {
                 unsupported(returns.token(), "tuples to return multiple things")
             }
             LastStmt::Return(returns) if returns.returns().len() == 1 => (self.returns != TypeInfo::default_ref())
-                .then(|| self.expression(returns.returns().iter().next().unwrap()))
+                .then(|| self.expr(returns.returns().iter().next().unwrap()))
                 .ok_or_else(|| {
                     box_error!(returns.token(), "function or job returns something, but this return statement is empty")
                 })?,
@@ -510,7 +509,7 @@ impl<'a> TypeCompute<'a> {
     }
 
     fn block(self, block: &'a Block) -> TypeComputeResult<'a> {
-        let symbols = block.stmts().try_fold(self.symbols.sub_scope(), |symbols, stmt| {
+        let symbols = block.stmts().try_fold(self.symbols.clone(), |symbols, stmt| {
             Ok::<_, TypeComputeError>(TypeCompute { symbols, ..self }.stmt(stmt)?.1)
         })?;
         Ok(match block.last_stmt() {
diff --git a/src/Rust/vvs_parser/src/vivy/passes/type_collector.rs b/src/Rust/vvs_parser/src/vivy/passes/type_collector.rs
index e24536a60e14a80d3169e6c8eec9ac588402cb25..09435236789998e289e799d9feac1e4a933ac37a 100644
--- a/src/Rust/vvs_parser/src/vivy/passes/type_collector.rs
+++ b/src/Rust/vvs_parser/src/vivy/passes/type_collector.rs
@@ -1,11 +1,11 @@
-use crate::vivy::library::*;
+use crate::vivy::{library::*, passes::declare_pass};
 
-crate::declare_pass! {
+declare_pass! {
     /// Collector of custom type, to register them in the library.
     #[allow(dead_code)]
     visit::TypeCollector: 'a {}
 
-    aux_infos { library: &'a mut Library }
+    aux { library: &'a mut Library }
 }
 
 impl crate::visitors::Visitor for TypeCollector<'_> {}
diff --git a/src/Rust/vvs_parser/src/vivy/symbol_table.rs b/src/Rust/vvs_parser/src/vivy/symbol_table.rs
index 156e6036de0283dee5bee35afef324f90e506459..72203c7cfe96b6b3d365a4a9ae20d24812660704 100644
--- a/src/Rust/vvs_parser/src/vivy/symbol_table.rs
+++ b/src/Rust/vvs_parser/src/vivy/symbol_table.rs
@@ -1,42 +1,80 @@
-use crate::{traits::DefaultRef, ShortString};
+use crate::{
+    ast::*,
+    tokenizer::{Symbol, Token, TokenReference, TokenType},
+    traits::DefaultRef,
+    ShortString,
+};
 use hashbrown::HashMap;
-use std::ops;
+use std::{
+    marker,
+    ops::{self, Deref},
+};
+
+#[derive(Debug, Clone)]
+pub struct SymbolTableLocked;
+
+#[derive(Debug, Clone)]
+pub struct SymbolTableMutable;
 
 /// The symbol table stores the available symbols with theyr type or whatever.
-pub struct SymbolTable<'a, T> {
-    /// A possible parent for this scope. The invariant is: either [SymbolTable::parent] or
-    /// [SymbolTable::imported] can be non-empty/null.
-    parent: Option<&'a SymbolTable<'a, T>>,
+#[derive(Clone)]
+pub struct SymbolTable<'a, T: Clone, State> {
+    /// A possible parent for this scope.
+    parent: Option<&'a SymbolTable<'a, T, SymbolTableLocked>>,
 
-    /// Imported scopes. The invariant is: either [SymbolTable::parent] or [SymbolTable::imported]
-    /// can be non-empty/null.
-    imported: Vec<&'a SymbolTable<'a, T>>,
+    /// Imported scopes.
+    imported: Vec<&'a SymbolTable<'a, T, SymbolTableLocked>>,
 
     /// List of symbols from this scope.
     symbols: HashMap<ShortString, T>,
+
+    /// The state of the table, can be locked or mutable.
+    _state: marker::PhantomData<State>,
 }
 
-impl<T> Default for SymbolTable<'_, T> {
+impl<T: Clone, State> Default for SymbolTable<'_, T, State> {
     fn default() -> Self {
-        Self { parent: None, symbols: HashMap::new(), imported: vec![] }
+        Self { parent: None, symbols: HashMap::new(), imported: vec![], _state: marker::PhantomData }
     }
 }
 
-impl<'a, T> SymbolTable<'a, T> {
+impl<'a, T: Clone> SymbolTable<'a, T, SymbolTableLocked> {
     /// Create a new sub-scoped symbol table.
-    pub fn sub_scope(&'a self) -> Self {
-        Self { parent: Some(self), symbols: Default::default(), imported: vec![] }
+    pub fn sub_scope(&'a self) -> SymbolTable<'a, T, SymbolTableMutable> {
+        SymbolTable { parent: Some(self), symbols: Default::default(), imported: vec![], _state: marker::PhantomData }
+    }
+
+    /// Get the root scope.
+    pub fn root(&self) -> &SymbolTable<'a, T, SymbolTableLocked> {
+        let mut root = self;
+        while let Some(parent) = root.parent {
+            root = parent
+        }
+        root
+    }
+}
+
+impl<'a, T: Clone> SymbolTable<'a, T, SymbolTableMutable> {
+    /// Lock the table.
+    pub fn lock(self) -> SymbolTable<'a, T, SymbolTableLocked> {
+        let Self { parent, imported, symbols, .. } = self;
+        SymbolTable::<'a, T, SymbolTableLocked> { parent, imported, symbols, _state: marker::PhantomData }
     }
 
     /// Add a new variable to the current scope. We override symbols (non-UB shadowing of
     /// variables). Note that we can't shadow or set variables from the root scope as it denotes
     /// the global state. In case of error we return the name of the problematic variable.
     pub fn set(&mut self, symbol: impl Into<ShortString> + AsRef<str>, value: T) -> Result<&mut Self, ShortString> {
-        if self.root().get(symbol.as_ref()).is_none() {
-            self.symbols.insert(symbol.into(), value);
-            Ok(self)
-        } else {
-            Err(symbol.into())
+        match self.parent {
+            Some(parent) if parent.root().get(symbol.as_ref()).is_none() => {
+                self.symbols.insert(symbol.into(), value);
+                Ok(self)
+            }
+            None => {
+                self.symbols.insert(symbol.into(), value);
+                Ok(self)
+            }
+            Some(_) => Err(symbol.into()),
         }
     }
 
@@ -46,38 +84,41 @@ impl<'a, T> SymbolTable<'a, T> {
             .try_fold(self, |this, (symbol, value)| this.set(symbol, value))
     }
 
-    /// Imports other scopes into this one. Note that if this scope is not the root one, then we
-    /// will panic...
-    pub fn import(&mut self, iter: impl IntoIterator<Item = &'a SymbolTable<'a, T>>) -> &mut Self {
-        debug_assert!(self.parent.is_none(), "can't have a parent and import another scope");
+    /// Imports other scopes into this one.
+    pub fn import(
+        mut self,
+        iter: impl IntoIterator<Item = &'a SymbolTable<'a, T, SymbolTableLocked>>,
+    ) -> SymbolTable<'a, T, SymbolTableLocked> {
         self.imported.extend(iter);
-        self
-    }
-
-    /// Get the root scope.
-    pub fn root(&self) -> &Self {
-        let mut root = self;
-        while let Some(parent) = root.parent {
-            root = parent;
-        }
-        root
+        self.lock()
     }
+}
 
+impl<'a, T: Clone, State> SymbolTable<'a, T, State> {
     /// Get a variable/callback/function from this scope or any parent scope.
     pub fn get(&self, symbol: impl AsRef<str>) -> Option<&T> {
         self.symbols
             .get(symbol.as_ref())
-            .or_else(|| self.parent?.get(symbol.as_ref()))
             .or_else(|| self.imported.iter().find_map(|scope| scope.get(symbol.as_ref())))
+            .or_else(|| self.parent?.get(symbol.as_ref()))
+    }
+
+    /// Tells if a variable/callback/function is present in this scope or any parent scope.
+    pub fn has(&self, symbol: impl AsRef<str>) -> bool {
+        self.get(symbol).is_some()
     }
 
-    /// Consume the mutable reference to give back the owned table.
-    pub(crate) fn take(&mut self) -> Self {
-        std::mem::take(self)
+    /// Tells if a symbol is global or not.
+    pub fn is_global(&self, symbol: impl AsRef<str>) -> bool {
+        (self.parent.is_none() && self.get(symbol.as_ref()).is_some())
+            || self
+                .parent
+                .map(SymbolTable::root)
+                .map_or(false, |root| root.get(symbol).is_some())
     }
 }
 
-impl<T> FromIterator<(ShortString, T)> for SymbolTable<'_, T> {
+impl<T: Clone> FromIterator<(ShortString, T)> for SymbolTable<'_, T, SymbolTableMutable> {
     fn from_iter<I: IntoIterator<Item = (ShortString, T)>>(iter: I) -> Self {
         let mut table = Self::default();
         let _ = table.extend(iter);
@@ -85,7 +126,7 @@ impl<T> FromIterator<(ShortString, T)> for SymbolTable<'_, T> {
     }
 }
 
-impl<Idx: AsRef<str>, T: DefaultRef + 'static> ops::Index<Idx> for SymbolTable<'_, T> {
+impl<Idx: AsRef<str>, T: Clone + DefaultRef + 'static, State> ops::Index<Idx> for SymbolTable<'_, T, State> {
     type Output = T;
 
     /// Same as [SymbolTable::get] but we returns a reference to a default value if the variable was not found.
@@ -94,10 +135,77 @@ impl<Idx: AsRef<str>, T: DefaultRef + 'static> ops::Index<Idx> for SymbolTable<'
     }
 }
 
-impl<'a> SymbolTable<'a, crate::ast::TypeInfo> {
-    /// Create a new type symbol table, e.g. a [SymbolTable] where the values are
-    /// [crate::ast::TypeInfo]. This can be usefull for type checking passes.
-    pub fn new(ast: &crate::ast::Ast, options: Option<&crate::ast::OptionTable>) -> Self {
-        todo!()
+fn fold_table_on_stmt<'a>(
+    symbol: SymbolTable<'a, TypeInfo, SymbolTableMutable>,
+    stmt: &'a Stmt,
+) -> Result<SymbolTable<'a, TypeInfo, SymbolTableMutable>, Box<AstError>> {
+    let handle_decl = |mut symbol: SymbolTable<'a, TypeInfo, SymbolTableMutable>, name: &str, body: &FunctionBody| {
+        let (arrow, return_type) = body
+            .return_type()
+            .map(|type_specifier| (type_specifier.punctuation().clone(), Box::new(type_specifier.type_info().clone())))
+            .unwrap_or_else(|| {
+                let arrow = Token::new(TokenType::Symbol { symbol: Symbol::ThinArrow });
+                (TokenReference::new(vec![], arrow, vec![]), Box::new(TypeInfo::default()))
+            });
+
+        let arguments = Punctuated::from_iter(body.parameters().pairs().zip(body.type_specifiers()).map(
+            |(param, ty)| {
+                let punctuation = ty.map(|ty| ty.punctuation()).cloned().unwrap_or_else(|| {
+                    let token = Token::new(crate::tokenizer::TokenType::Symbol { symbol: Symbol::TwoDots });
+                    TokenReference::new(vec![], token, vec![])
+                });
+                let name = Some((param.value().clone().deref().clone(), punctuation));
+                let type_info = ty.map(TypeSpecifier::type_info).cloned().unwrap_or_default();
+                let type_argument = TypeArgument::new(type_info).with_name(name);
+                match param.punctuation() {
+                    Some(punctuation) => Pair::Punctuated(type_argument, punctuation.clone()),
+                    None => Pair::End(type_argument),
+                }
+            },
+        ));
+
+        let parentheses = body.parameters_parentheses().clone();
+        let _ = symbol.set(name, TypeInfo::Callback { parentheses, arguments, arrow, return_type });
+        Ok(symbol)
+    };
+
+    match stmt {
+        // Handled by the type collector
+        Stmt::FunctionDeclaration(d) if d.name().method_name().is_some() => Ok(symbol),
+
+        // Function declarations errors
+        Stmt::FunctionDeclaration(d) if d.name().names().len() != 1 => todo!("error: invalid name"),
+        Stmt::FunctionDeclaration(d) if matches!(d.name().names().first_value(), Some(n) if symbol.has(n.token_type().as_str())) =>
+        {
+            todo!("error: multiple definitions")
+        }
+        Stmt::LocalFunction(local) if symbol.has(local.name().token_type().as_str()) => {
+            todo!("error: multiple definitions")
+        }
+
+        // Function declarations
+        Stmt::LocalFunction(local) => handle_decl(symbol, local.name().token_type().as_str(), local.body()),
+        Stmt::FunctionDeclaration(decl) => {
+            let name = decl.name().names().into_iter().next().unwrap().token_type().as_str();
+            handle_decl(symbol, name, decl.body())
+        }
+
+        // Options & constants
+        Stmt::OptionDecl(_) => todo!(),
+        Stmt::LocalAssignment(_) => todo!("handle constants"),
+
+        // Ignore
+        _ => Ok(symbol),
+    }
+}
+
+impl<'a> TryFrom<&'a Ast> for SymbolTable<'a, TypeInfo, SymbolTableMutable> {
+    type Error = AstError;
+
+    fn try_from(ast: &'a Ast) -> Result<Self, Self::Error> {
+        ast.nodes()
+            .stmts()
+            .try_fold(Self::default(), fold_table_on_stmt)
+            .map_err(|boxed| *boxed)
     }
 }