Repository: fika-lang/fika (elixir)

Issue:
Add mix format --check-formatted as CI check
As per [my comment](https://github.com/fika-lang/fika/pull/13#discussion_r498636720) on #13, I think that formatter and credo would be nice additions to the workflow.
Since the formatter is standardized, it would be nice and simple for everyone to use.
And credo adds more code uniformity checks that might also be nice

Pull request:
chore: format and check formatted
This PR closes #17 and #15


Patch:
diff --git a/.formatter.exs b/.formatter.exs
new file mode 100644
index 0000000..5617558
--- /dev/null
+++ b/.formatter.exs
@@ -0,0 +1,4 @@
+[
+  inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
+  import_deps: [:nimble_parsec]
+]
diff --git a/.tool-versions b/.tool-versions
new file mode 100644
index 0000000..1c63df1
--- /dev/null
+++ b/.tool-versions
@@ -0,0 +1,2 @@
+elixir 1.10.4
+erlang 22.2.8
diff --git a/README.md b/README.md
index 17840f9..fd106ee 100644
--- a/README.md
+++ b/README.md
@@ -95,6 +95,9 @@ x = true
 # Type: List(Int)
 list_of_ints = [1, 2, 3]
 
+# Type: {String,Bool}
+tuple = {"tuple", true}
+
 # Type: {foo: Int}
 record = {foo: 123}
 ```
diff --git a/example.fi b/example.fi
index f41b84e..eea252a 100644
--- a/example.fi
+++ b/example.fi
@@ -33,3 +33,7 @@ fn simple_if : String do
     "you can't see me \m/"
   end
 end
+
+fn tuple : {Int,Bool} do
+  {1, true}
+end
diff --git a/lib/fika.ex b/lib/fika.ex
index eaaf289..1cbd6de 100644
--- a/lib/fika.ex
+++ b/lib/fika.ex
@@ -1,7 +1,7 @@
 defmodule Fika do
   def start(path \\ nil) do
     if path do
-      File.cd! path
+      File.cd!(path)
     end
 
     if File.exists?("router.fi") do
@@ -9,7 +9,7 @@ defmodule Fika do
       start_webserver()
       :ok
     else
-      IO.puts "Cannot start webserver: file router.fi not found."
+      IO.puts("Cannot start webserver: file router.fi not found.")
       :error
     end
   end
@@ -19,8 +19,11 @@ defmodule Fika do
   end
 
   def start_webserver(port \\ 6060) do
-    {:ok, _pid} = Supervisor.start_child(Fika.Supervisor,
-      {Plug.Cowboy, scheme: :http, plug: Fika.Router, options: [port: port, ip: {127, 0, 0, 1}]})
+    {:ok, _pid} =
+      Supervisor.start_child(
+        Fika.Supervisor,
+        {Plug.Cowboy, scheme: :http, plug: Fika.Router, options: [port: port, ip: {127, 0, 0, 1}]}
+      )
 
     IO.puts("Web server is running on http://localhost:#{port}\nPress Ctrl+C to exit")
   end
diff --git a/lib/fika/cli.ex b/lib/fika/cli.ex
index 63db87c..1abc424 100644
--- a/lib/fika/cli.ex
+++ b/lib/fika/cli.ex
@@ -17,13 +17,14 @@ defmodule Fika.Cli do
     function = opts[:function] || "main.start()"
 
     {:module, module} = Fika.Code.load_file(main_file)
-    Logger.debug "Calling :#{module}.#{function}"
+    Logger.debug("Calling :#{module}.#{function}")
     {result, _binding} = Code.eval_string(":\"#{module}\".#{function}")
-    IO.inspect result
+    IO.inspect(result)
   end
 
   defp parse_args(["start" | rest]) do
     path = List.first(rest)
+
     case Fika.start(path) do
       :ok -> :timer.sleep(:infinity)
       :error -> :error
diff --git a/lib/fika/code.ex b/lib/fika/code.ex
index bd3715b..e965221 100644
--- a/lib/fika/code.ex
+++ b/lib/fika/code.ex
@@ -13,7 +13,8 @@ defmodule Fika.Code do
       {:ok, str} ->
         load_string(str, file)
 
-      {:error, error} -> IO.puts("Cannot read file: #{inspect error}")
+      {:error, error} ->
+        IO.puts("Cannot read file: #{inspect(error)}")
     end
   end
 
@@ -24,7 +25,7 @@ defmodule Fika.Code do
       {:ok, _env} = TypeChecker.check_module(ast, Env.init())
       forms = ErlTranslate.translate(ast, file)
       {:module, module} = result = load_forms(forms, file)
-      Logger.debug "Loaded module #{module}"
+      Logger.debug("Loaded module #{module}")
       result
     else
       {:error, "Invalid filename. Make sure it's something like foo/bar/baz.fi"}
diff --git a/lib/fika/env.ex b/lib/fika/env.ex
index 0200b97..00a130e 100644
--- a/lib/fika/env.ex
+++ b/lib/fika/env.ex
@@ -14,21 +14,18 @@ defmodule Fika.Env do
         "kernel.+(Int,Float)" => "Float",
         "kernel.+(Float,Int)" => "Float",
         "kernel.+(Float,Float)" => "Float",
-
         "kernel.-(Int,Int)" => "Int",
         "kernel.-(Int,Float)" => "Float",
         "kernel.-(Float,Int)" => "Float",
         "kernel.-(Float,Float)" => "Float",
-
         "kernel.*(Int,Int)" => "Int",
         "kernel.*(Int,Float)" => "Float",
         "kernel.*(Float,Int)" => "Float",
         "kernel.*(Float,Float)" => "Float",
-
         "kernel./(Int,Int)" => "Float",
         "kernel./(Int,Float)" => "Float",
         "kernel./(Float,Int)" => "Float",
-        "kernel./(Float,Float)" => "Float",
+        "kernel./(Float,Float)" => "Float"
       }
     }
   end
@@ -70,7 +67,7 @@ defmodule Fika.Env do
   end
 
   defp add_known_function(env, signature) do
-    update_in(env, [:module_env, :known_functions], & [signature | &1])
+    update_in(env, [:module_env, :known_functions], &[signature | &1])
   end
 
   def ast_functions(env) do
diff --git a/lib/fika/erl_translate.ex b/lib/fika/erl_translate.ex
index 0a8a773..d72d1f2 100644
--- a/lib/fika/erl_translate.ex
+++ b/lib/fika/erl_translate.ex
@@ -2,10 +2,12 @@ defmodule Fika.ErlTranslate do
   def translate({:module, module_name, functions}, file) do
     line = 1
     file = String.to_charlist(file)
+
     module = [
       {:attribute, line, :file, {file, line}},
       {:attribute, line, :module, String.to_atom(module_name)}
     ]
+
     {exports, function_declaration} = to_forms(functions)
 
     module ++ exports ++ function_declaration
@@ -33,7 +35,8 @@ defmodule Fika.ErlTranslate do
     Enum.map(exps, &translate_exp/1)
   end
 
-  defp translate_exp({:call, {bin_op, {line, _, _}}, [arg1, arg2], _module}) when bin_op in [:+, :-, :*, :/] do
+  defp translate_exp({:call, {bin_op, {line, _, _}}, [arg1, arg2], _module})
+       when bin_op in [:+, :-, :*, :/] do
     {:op, line, bin_op, translate_exp(arg1), translate_exp(arg2)}
   end
 
@@ -62,7 +65,7 @@ defmodule Fika.ErlTranslate do
   defp translate_exp({:atom, {line, _, _}, value}) do
     {:atom, line, value}
   end
-  
+
   defp translate_exp({{:=, {line, _, _}}, pattern, exp}) do
     {:match, line, translate_exp(pattern), translate_exp(exp)}
   end
@@ -83,10 +86,16 @@ defmodule Fika.ErlTranslate do
     do_translate_list(value, line)
   end
 
+  defp translate_exp({:tuple, {line, _, _}, value}) do
+    {:tuple, line, translate_exps(value)}
+  end
+
   defp translate_exp({:record, {line, _, _}, name, k_vs}) do
-    k_vs = Enum.map(k_vs, fn {{:identifier, {l, _, _}, k}, v} ->
-      {:map_field_assoc, l, {:atom, l, k}, translate_exp(v)}
-    end)
+    k_vs =
+      Enum.map(k_vs, fn {{:identifier, {l, _, _}, k}, v} ->
+        {:map_field_assoc, l, {:atom, l, k}, translate_exp(v)}
+      end)
+
     k_vs = add_record_meta(k_vs, name, line)
 
     {:map, line, k_vs}
@@ -94,12 +103,14 @@ defmodule Fika.ErlTranslate do
 
   defp translate_exp({:function_ref, {line, _, _}, {module, function, arg_types}}) do
     arity = length(arg_types)
+
     f =
       if module do
         {:function, {:atom, line, module}, {:atom, line, function}, {:integer, line, arity}}
       else
         {:function, function, arity}
       end
+
     {:fun, line, f}
   end
 
@@ -118,7 +129,7 @@ defmodule Fika.ErlTranslate do
   defp add_record_meta(k_vs, name, line) do
     name =
       if name do
-         {:atom, 0, String.to_atom(name)}
+        {:atom, 0, String.to_atom(name)}
       else
         {nil, 0}
       end
diff --git a/lib/fika/parser.ex b/lib/fika/parser.ex
index 04e71d1..d8ae27f 100644
--- a/lib/fika/parser.ex
+++ b/lib/fika/parser.ex
@@ -11,7 +11,7 @@ defmodule Fika.Parser do
 
   comment =
     string("#")
-    |> repeat(utf8_char([not: ?\n]))
+    |> repeat(utf8_char(not: ?\n))
     |> string("\n")
 
   vertical_space =
@@ -82,7 +82,7 @@ defmodule Fika.Parser do
 
   string_exp =
     ignore(string("\""))
-    |> repeat(choice([string("\\\""), utf8_char([not: ?"])]))
+    |> repeat(choice([string("\\\""), utf8_char(not: ?")]))
     |> ignore(string("\""))
     |> Helper.to_ast(:string)
 
@@ -127,6 +127,14 @@ defmodule Fika.Parser do
     |> ignore(string("]"))
     |> Helper.to_ast(:exp_list)
 
+  tuple =
+    ignore(string("{"))
+    |> concat(allow_space)
+    |> concat(list_content)
+    |> concat(allow_space)
+    |> ignore(string("}"))
+    |> Helper.to_ast(:tuple)
+
   exp_paren =
     ignore(string("("))
     |> parsec(:exp)
@@ -230,6 +238,7 @@ defmodule Fika.Parser do
       boolean,
       string_exp,
       exp_list,
+      tuple,
       record,
       function_ref,
       atom,
@@ -260,8 +269,7 @@ defmodule Fika.Parser do
       |> parsec(:term)
     )
 
-  exp_mult_op =
-    Helper.to_ast(term, :exp_bin_op)
+  exp_mult_op = Helper.to_ast(term, :exp_bin_op)
 
   exp_bin_op =
     exp_mult_op
@@ -272,8 +280,7 @@ defmodule Fika.Parser do
       |> parsec(:exp_bin_op)
     )
 
-  exp_add_op =
-    Helper.to_ast(exp_bin_op, :exp_bin_op)
+  exp_add_op = Helper.to_ast(exp_bin_op, :exp_bin_op)
 
   exp =
     choice([
@@ -306,6 +313,29 @@ defmodule Fika.Parser do
     |> concat(allow_space)
     |> string(")")
 
+  # To parse functions with tuple return type
+  type_tuple_element =
+    allow_space
+    |> parsec(:type)
+    |> label("tuple element")
+
+  type_tuple_elements =
+    type_tuple_element
+    |> repeat(
+      allow_space
+      |> ignore(string(","))
+      |> concat(allow_space)
+      |> concat(type_tuple_element)
+    )
+    |> reduce({Enum, :join, [","]})
+
+  tuple_type =
+    string("{")
+    |> concat(type_tuple_elements)
+    |> string("}")
+    |> reduce({Enum, :join, []})
+    |> label("tuple type")
+
   type_key_value =
     allow_space
     |> concat(identifier_str)
@@ -349,7 +379,9 @@ defmodule Fika.Parser do
       simple_type
       |> optional(type_parens),
       record_type,
-      atom
+      atom,
+      record_type,
+      tuple_type
     ])
 
   parse_type =
diff --git a/lib/fika/parser_helper.ex b/lib/fika/parser_helper.ex
index 51b6765..7c3a86d 100644
--- a/lib/fika/parser_helper.ex
+++ b/lib/fika/parser_helper.ex
@@ -9,7 +9,6 @@ defmodule Fika.ParserHelper do
     |> map({Fika.ParserHelper, :do_to_ast, [kind]})
   end
 
-
   def put_line_offset({[{result, {line, line_start_offset}}], string_offset}) do
     {result, {line, line_start_offset, string_offset}}
   end
@@ -26,7 +25,8 @@ defmodule Fika.ParserHelper do
     {{:if, line}, condition, true_block, false_block}
   end
 
-  def do_to_ast({[left, bin_op, right | rest], line}, :exp_bin_op) when bin_op in ["+", "-", "*", "/"] do
+  def do_to_ast({[left, bin_op, right | rest], line}, :exp_bin_op)
+      when bin_op in ["+", "-", "*", "/"] do
     new_left = {:call, {String.to_atom(bin_op), line}, [left, right], :kernel}
     do_to_ast({[new_left | rest], line}, :exp_bin_op)
   end
@@ -88,6 +88,7 @@ defmodule Fika.ParserHelper do
     case val do
       [exp, args] ->
         {:call, {exp, line}, args}
+
       [val] ->
         val
     end
@@ -105,6 +106,10 @@ defmodule Fika.ParserHelper do
     {:list, line, result}
   end
 
+  def do_to_ast({result, line}, :tuple) do
+    {:tuple, line, result}
+  end
+
   def do_to_ast({[k, v], _line}, :key_value) do
     {k, v}
   end
@@ -123,15 +128,17 @@ defmodule Fika.ParserHelper do
     case ast do
       [[], function, arg_types] ->
         {:function_ref, line, {nil, value_from_identifier(function), arg_types}}
+
       [[module], function, arg_types] ->
-        {:function_ref, line, {value_from_identifier(module), value_from_identifier(function), arg_types}}
+        {:function_ref, line,
+         {value_from_identifier(module), value_from_identifier(function), arg_types}}
     end
   end
 
   def do_to_ast({[{:identifier, line, value}], line}, :atom) do
     {:atom, line, value}
   end
-  
+
   defp value_from_identifier({:identifier, _line, value}) do
     value
   end
diff --git a/lib/fika/route_store.ex b/lib/fika/route_store.ex
index 76773b5..2271a8c 100644
--- a/lib/fika/route_store.ex
+++ b/lib/fika/route_store.ex
@@ -18,11 +18,12 @@ defmodule Fika.RouteStore do
 
     if function_exported?(:router, :routes, 0) do
       routes = :router.routes()
+
       Enum.into(routes, %{}, fn %{method: method, path: path, handler: function} ->
-          {"#{method}:#{path}", function}
+        {"#{method}:#{path}", function}
       end)
     else
-      Logger.error "Router has no function routes/0"
+      Logger.error("Router has no function routes/0")
       %{}
     end
   end
diff --git a/lib/fika/router.ex b/lib/fika/router.ex
index d0b6a78..3240268 100644
--- a/lib/fika/router.ex
+++ b/lib/fika/router.ex
@@ -16,7 +16,9 @@ defmodule Fika.Router do
 
   defp get_resp(method, path) do
     case Fika.RouteStore.get_route(method, path) do
-      nil -> {404, "Not found"}
+      nil ->
+        {404, "Not found"}
+
       function ->
         body = function.()
         {200, body}
diff --git a/lib/fika/type_checker.ex b/lib/fika/type_checker.ex
index dea1889..685bb40 100644
--- a/lib/fika/type_checker.ex
+++ b/lib/fika/type_checker.ex
@@ -7,20 +7,22 @@ defmodule Fika.TypeChecker do
   # function definitions.
   def check_module({:module, module_name, functions} = ast, env) do
     env = Env.init_module_env(env, module_name, ast)
+
     Enum.reduce_while(functions, {:ok, env}, fn function, {:ok, env} ->
       {:function, _line, {name, _args, _type, _exprs}} = function
       signature = signature(function, module_name)
 
       if Env.known_function?(env, signature) do
-        Logger.debug "Already checked function: #{signature}. Skipping."
+        Logger.debug("Already checked function: #{signature}. Skipping.")
         {:cont, {:ok, env}}
       else
         case check(function, env) do
           {:ok, type, env} ->
-            Logger.debug "Function: #{name} checks type: #{type}"
+            Logger.debug("Function: #{name} checks type: #{type}")
             {:cont, {:ok, env}}
+
           error ->
-            Logger.debug "Function: #{name} failed type check"
+            Logger.debug("Function: #{name} failed type check")
             {:halt, error}
         end
       end
@@ -36,8 +38,10 @@ defmodule Fika.TypeChecker do
     case infer(function, env) do
       {:ok, ^expected_type, _env} = result ->
         result
+
       {:ok, other_type, _} ->
         {:error, "Expected type: #{expected_type}, got: #{other_type}"}
+
       error ->
         error
     end
@@ -57,15 +61,18 @@ defmodule Fika.TypeChecker do
   end
 
   def infer_block(env, []) do
-    Logger.debug "Block is empty."
+    Logger.debug("Block is empty.")
     {:ok, "Nothing", env}
   end
+
   def infer_block(env, [exp]) do
-    Logger.debug "Block has one exp left"
+    Logger.debug("Block has one exp left")
     infer_exp(env, exp)
   end
+
   def infer_block(env, [exp | exp_list]) do
-    Logger.debug "Block has multiple exps"
+    Logger.debug("Block has multiple exps")
+
     case infer_exp(env, exp) do
       {:ok, _type, env} -> infer_block(env, exp_list)
       error -> error
@@ -74,24 +81,25 @@ defmodule Fika.TypeChecker do
 
   # Integer literals
   def infer_exp(env, {:integer, _line, integer}) do
-    Logger.debug "Integer #{integer} found. Type: Int"
+    Logger.debug("Integer #{integer} found. Type: Int")
     {:ok, "Int", env}
   end
 
   # Booleans
   def infer_exp(env, {:boolean, _line, boolean}) do
-    Logger.debug "Boolean #{boolean} found. Type: Bool"
+    Logger.debug("Boolean #{boolean} found. Type: Bool")
     {:ok, "Bool", env}
   end
 
   # Variables
   def infer_exp(env, {:identifier, _line, name}) do
     type = Env.scope_get(env, name)
+
     if type do
-      Logger.debug "Variable type found from scope: #{name}:#{type}"
+      Logger.debug("Variable type found from scope: #{name}:#{type}")
       {:ok, type, env}
     else
-      Logger.debug "Variable type not found in scope: #{name}"
+      Logger.debug("Variable type not found in scope: #{name}")
       {:error, "Unknown variable: #{name}"}
     end
   end
@@ -100,7 +108,7 @@ defmodule Fika.TypeChecker do
   def infer_exp(env, {:call, {name, _line}, args, module}) do
     exp = %{args: args, name: name}
     module_name = module || Env.current_module(env)
-    Logger.debug "Inferring type of function: #{name}"
+    Logger.debug("Inferring type of function: #{name}")
     infer_args(env, exp, module_name)
   end
 
@@ -112,15 +120,19 @@ defmodule Fika.TypeChecker do
         case do_infer_args_without_name(env, args) do
           {:ok, ^arg_types, env} ->
             {:ok, type, env}
+
           {:ok, other_arg_types, _env} ->
-            error = "Expected function reference to be called with" <>
-              " arguments (#{Enum.join(arg_types, ",")}), but it was called " <>
+            error =
+              "Expected function reference to be called with" <>
+                " arguments (#{Enum.join(arg_types, ",")}), but it was called " <>
                 "with arguments (#{Enum.join(other_arg_types, ",")})"
 
             {:error, error}
         end
+
       {:ok, type, _} ->
         {:error, "Expected a function reference, but got type: #{type}"}
+
       error ->
         error
     end
@@ -130,9 +142,10 @@ defmodule Fika.TypeChecker do
   def infer_exp(env, {{:=, _}, {:identifier, _line, left}, right}) do
     case infer_exp(env, right) do
       {:ok, type, env} ->
-        Logger.debug "Adding variable to scope: #{left}:#{type}"
+        Logger.debug("Adding variable to scope: #{left}:#{type}")
         env = Env.scope_add(env, left, type)
         {:ok, type, env}
+
       error ->
         error
     end
@@ -140,7 +153,7 @@ defmodule Fika.TypeChecker do
 
   # String
   def infer_exp(env, {:string, _line, string}) do
-    Logger.debug "String #{string} found. Type: String"
+    Logger.debug("String #{string} found. Type: String")
     {:ok, "String", env}
   end
 
@@ -149,14 +162,28 @@ defmodule Fika.TypeChecker do
     infer_list_exps(env, exps)
   end
 
+  # Tuple
+  def infer_exp(env, {:tuple, _, exps}) do
+    case do_infer_tuple_exps(exps, env) do
+      {:ok, exp_types, env} ->
+        exp_types =
+          exp_types
+          |> Enum.reverse()
+          |> Enum.join(",")
+
+        {:ok, "{#{exp_types}}", env}
+      error ->
+        error
+    end
+  end
+
   # Record
   def infer_exp(env, {:record, _, name, key_values}) do
     if name do
       # Lookup type of name, ensure it matches.
-      Logger.error "Not implemented"
+      Logger.error("Not implemented")
     else
       case do_infer_key_values(key_values, env) do
-
         {:ok, k_v_types, env} ->
           k_v_types =
             k_v_types
@@ -164,6 +191,7 @@ defmodule Fika.TypeChecker do
             |> Enum.join(",")
 
           {:ok, "{#{k_v_types}}", env}
+
         error ->
           error
       end
@@ -172,14 +200,15 @@ defmodule Fika.TypeChecker do
 
   # Function ref
   def infer_exp(env, {:function_ref, _, {module, function_name, arg_types}}) do
-    Logger.debug "Inferring type of function: #{function_name}"
+    Logger.debug("Inferring type of function: #{function_name}")
 
     module_name = module || Env.current_module(env)
     signature = get_signature(module_name, function_name, arg_types)
 
     result =
       if module_name == Env.current_module(env) && !Env.known_function?(env, signature) do
-        Logger.debug "Checking unknown function #{signature} in module: #{module_name}"
+        Logger.debug("Checking unknown function #{signature} in module: #{module_name}")
+
         case check_by_signature(env, signature) do
           {:ok, _, _} = result -> result
           error -> error
@@ -192,6 +221,7 @@ defmodule Fika.TypeChecker do
       {:ok, type, env} ->
         type = %FunctionRef{arg_types: arg_types, return_type: type}
         {:ok, type, env}
+
       error ->
         error
     end
@@ -205,7 +235,7 @@ defmodule Fika.TypeChecker do
 
   # if-else expression
   def infer_exp(env, {{:if, _line}, condition, if_block, else_block}) do
-    Logger.debug "Inferring an if-else expression"
+    Logger.debug("Inferring an if-else expression")
 
     case infer_if_else_condition(env, condition) do
       {:ok, "Bool", env} -> infer_if_else_blocks(env, if_block, else_block)
@@ -232,8 +262,9 @@ defmodule Fika.TypeChecker do
     unless if_type_val == else_type_val do
       Logger.debug("if and else block have different types")
 
-      error = "Expected if and else blocks to have same return type. " <>
-        "Got #{if_type_val} and #{else_type_val}"
+      error =
+        "Expected if and else blocks to have same return type. " <>
+          "Got #{if_type_val} and #{else_type_val}"
 
       {:error, error}
     else
@@ -247,6 +278,18 @@ defmodule Fika.TypeChecker do
         {:ok, type, env} ->
           {:identifier, _, key} = k
           {:cont, {:ok, ["#{key}:#{type}" | acc], env}}
+
+        error ->
+          {:halt, error}
+      end
+    end)
+  end
+
+  defp do_infer_tuple_exps(exps, env) do
+    Enum.reduce_while(exps, {:ok, [], env}, fn exp, {:ok, acc, env} ->
+      case infer_exp(env, exp) do
+        {:ok, exp_type, env} ->
+          {:cont, {:ok, [exp_type | acc], env}}
         error -> {:halt, error}
       end
     end)
@@ -258,7 +301,8 @@ defmodule Fika.TypeChecker do
         signature = get_signature(module, exp.name, type_acc)
 
         if module == Env.current_module(env) && !Env.known_function?(env, signature) do
-          Logger.debug "Checking unknown function #{signature} in module: #{module}"
+          Logger.debug("Checking unknown function #{signature} in module: #{module}")
+
           case check_by_signature(env, signature) do
             {:ok, _, _} = result -> result
             error -> error
@@ -275,22 +319,30 @@ defmodule Fika.TypeChecker do
   defp infer_list_exps(env, []) do
     {:ok, "List(Nothing)", env}
   end
+
   defp infer_list_exps(env, [exp]) do
     case infer_exp(env, exp) do
       {:ok, type, env} -> {:ok, "List(#{type})", env}
       error -> error
     end
   end
+
   defp infer_list_exps(env, [exp | rest]) do
     {:ok, type, env} = infer_exp(env, exp)
+
     Enum.reduce_while(rest, {:ok, "List(#{type})", env}, fn exp, {:ok, acc_type, acc_env} ->
       case infer_exp(acc_env, exp) do
         {:ok, ^type, env} ->
           acc = {:ok, acc_type, env}
           {:cont, acc}
+
         {:ok, diff_type, _} ->
-          error = {:error, "Elements of list have different types. Expected: #{type}, got: #{diff_type}"}
+          error =
+            {:error,
+             "Elements of list have different types. Expected: #{type}, got: #{diff_type}"}
+
           {:halt, error}
+
         error ->
           {:halt, error}
       end
@@ -301,10 +353,11 @@ defmodule Fika.TypeChecker do
     Enum.reduce_while(exp.args, {:ok, [], env}, fn arg, {:ok, type_acc, env} ->
       case infer_exp(env, arg) do
         {:ok, type, env} ->
-          Logger.debug "Argument of #{exp.name} is type: #{type}"
+          Logger.debug("Argument of #{exp.name} is type: #{type}")
           {:cont, {:ok, [type | type_acc], env}}
+
         error ->
-          Logger.debug "Argument of #{exp.name} cannot be inferred"
+          Logger.debug("Argument of #{exp.name} cannot be inferred")
           {:halt, error}
       end
     end)
@@ -314,10 +367,11 @@ defmodule Fika.TypeChecker do
     Enum.reduce_while(args, {:ok, [], env}, fn arg, {:ok, type_acc, env} ->
       case infer_exp(env, arg) do
         {:ok, type, env} ->
-          Logger.debug "Argument is type: #{type}"
+          Logger.debug("Argument is type: #{type}")
           {:cont, {:ok, type_acc ++ [type], env}}
+
         error ->
-          Logger.debug "Argument cannot be inferred"
+          Logger.debug("Argument cannot be inferred")
           {:halt, error}
       end
     end)
@@ -325,11 +379,12 @@ defmodule Fika.TypeChecker do
 
   defp get_type_by_signature(env, signature) do
     type = Env.get_function_type(env, signature)
+
     if type do
-      Logger.debug "Type of function signature: #{signature} is: #{type}"
+      Logger.debug("Type of function signature: #{signature} is: #{type}")
       {:ok, type, env}
     else
-      Logger.debug "Type of function signature: #{signature} not found"
+      Logger.debug("Type of function signature: #{signature} not found")
       {:error, "Unknown function: #{signature}"}
     end
   end
@@ -342,9 +397,11 @@ defmodule Fika.TypeChecker do
   end
 
   defp add_function_type({:ok, type, env}, name, args) do
-    types = Enum.map(args, fn {_, {_, _, type}} ->
-      type
-    end)
+    types =
+      Enum.map(args, fn {_, {_, _, type}} ->
+        type
+      end)
+
     module = Env.current_module(env)
     signature = get_signature(module, name, types)
 
@@ -352,6 +409,7 @@ defmodule Fika.TypeChecker do
 
     {:ok, type, env}
   end
+
   defp add_function_type(error, _, _) do
     error
   end
@@ -368,10 +426,12 @@ defmodule Fika.TypeChecker do
   defp check_by_signature(env, signature) do
     functions = Env.ast_functions(env)
     module = Env.current_module(env)
+
     function =
       Enum.find(functions, fn function ->
         signature(function, module) == signature
       end)
+
     if function do
       check(function, env)
     else
@@ -383,6 +443,7 @@ defmodule Fika.TypeChecker do
     case result do
       {:ok, type, env} ->
         {:ok, to_string(type), env}
+
       error ->
         error
     end
diff --git a/mix.exs b/mix.exs
index b5b6fe4..26edfad 100644
--- a/mix.exs
+++ b/mix.exs
@@ -10,7 +10,7 @@ defmodule Fika.MixProject do
       escript: [main_module: Fika.Cli],
       deps: deps(),
       xref: [exclude: [:router]],
-      elixirc_paths: elixirc_paths(Mix.env),
+      elixirc_paths: elixirc_paths(Mix.env())
     ]
   end
 


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index ec6cb61..ccade7b 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -3,12 +3,31 @@ on: [pull_request, push]
 jobs:
   test:
     runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        elixir: [1.10.4]
+        otp: [22.2.8]
     steps:
       - uses: actions/checkout@v2
       - uses: actions/setup-elixir@v1
         with:
-          otp-version: '22.2'
-          elixir-version: '1.10.4'
-      - run: mix deps.get
-      - run: mix compile --warnings-as-errors
-      - run: mix test
+          otp-version: ${{ matrix.otp }}
+          elixir-version: ${{ matrix.elixir }}
+      - name: Retrieve Mix Dependencies Cache
+        uses: actions/cache@v1
+        id: mix-cache # id to use in retrieve action
+        with:
+          path: deps
+          key: ${{ runner.os }}-${{ matrix.otp }}-${{ matrix.elixir }}-mix-${{ hashFiles(format('{0}{1}', github.workspace, '/mix.lock')) }}
+      - name: Install Mix Dependencies
+        if: steps.mix-cache.outputs.cache-hit != 'true'
+        run: |
+          mix local.rebar --force
+          mix local.hex --force
+          mix deps.get
+      - name: Compile Code
+        run: mix compile --warnings-as-errors
+      - name: Ensure Code is Formatted
+        run: mix format --check-formatted
+      - name: Run Tests
+        run: mix test
diff --git a/test/fika/erl_translate_test.exs b/test/fika/erl_translate_test.exs
index 7b76179..6eadc05 100644
--- a/test/fika/erl_translate_test.exs
+++ b/test/fika/erl_translate_test.exs
@@ -1,5 +1,6 @@
 defmodule Fika.ErlTranslateTest do
   use ExUnit.Case
+
   alias Fika.{
     Parser,
     ErlTranslate
@@ -19,6 +20,7 @@ defmodule Fika.ErlTranslateTest do
 
     ast = Parser.parse_module(str, "test")
     result = ErlTranslate.translate(ast, "/tmp/foo")
+
     forms = [
       {:attribute, 1, :file, {'/tmp/foo', 1}},
       {:attribute, 1, :module, :test},
@@ -34,6 +36,7 @@ defmodule Fika.ErlTranslateTest do
           ]}
        ]}
     ]
+
     assert result == forms
   end
 
@@ -46,21 +49,21 @@ defmodule Fika.ErlTranslateTest do
 
     ast = Parser.parse_module(str, "test_arithmetic")
     result = ErlTranslate.translate(ast, "/tmp/foo")
+
     forms = [
       {:attribute, 1, :file, {'/tmp/foo', 1}},
       {:attribute, 1, :module, :test_arithmetic},
       {:attribute, 3, :export, [a: 0]},
       {:function, 3, :a, 0,
-        [
-          {:clause, 3, [], [],
-            [
-              {:op, 2, :+, {:integer, 2, 1},
-                {:op, 2, :/, {:op, 2, :*, {:integer, 2, 2}, {:integer, 2, 3}},
-                  {:integer, 2, 4}}}
-            ]}
-        ]
-      }
+       [
+         {:clause, 3, [], [],
+          [
+            {:op, 2, :+, {:integer, 2, 1},
+             {:op, 2, :/, {:op, 2, :*, {:integer, 2, 2}, {:integer, 2, 3}}, {:integer, 2, 4}}}
+          ]}
+       ]}
     ]
+
     assert result == forms
   end
 
@@ -69,10 +72,12 @@ defmodule Fika.ErlTranslateTest do
     ast = Parser.expression!(str)
     result = ErlTranslate.translate_expression(ast)
 
-    assert result == {:map, 1, [
-        {:map_field_assoc, 1, {:atom, 0, :__record__}, {nil, 0}},
-        {:map_field_assoc, 1, {:atom, 1, :foo}, {:integer, 1, 1}}
-    ]}
+    assert result ==
+             {:map, 1,
+              [
+                {:map_field_assoc, 1, {:atom, 0, :__record__}, {nil, 0}},
+                {:map_field_assoc, 1, {:atom, 1, :foo}, {:integer, 1, 1}}
+              ]}
   end
 
   describe "function reference" do
@@ -81,8 +86,8 @@ defmodule Fika.ErlTranslateTest do
       ast = Parser.expression!(str)
       result = ErlTranslate.translate_expression(ast)
 
-      assert result == {:fun, 1,
-        {:function, {:atom, 1, :my_module}, {:atom, 1, :foo}, {:integer, 1, 2}}}
+      assert result ==
+               {:fun, 1, {:function, {:atom, 1, :my_module}, {:atom, 1, :foo}, {:integer, 1, 2}}}
     end
 
     test "without module" do
@@ -90,8 +95,7 @@ defmodule Fika.ErlTranslateTest do
       ast = Parser.expression!(str)
       result = ErlTranslate.translate_expression(ast)
 
-      assert result == {:fun, 1,
-        {:function, :foo, 2}}
+      assert result == {:fun, 1, {:function, :foo, 2}}
     end
   end
 
@@ -124,7 +128,7 @@ defmodule Fika.ErlTranslateTest do
   end
 
   test "if-else expression" do
-    str =  """
+    str = """
     if true do
       "foo"
     else
@@ -134,11 +138,31 @@ defmodule Fika.ErlTranslateTest do
 
     ast = Fika.Parser.expression!(str)
     result = ErlTranslate.translate_expression(ast)
+
     assert {:case, 5, {:atom, 1, true},
-      [
-        {:clause, 5, [{:atom, 5, true}], [], [{:string, 2, 'foo'}]},
-        {:clause, 5, [{:atom, 5, false}], [], [{:string, 4, 'bar'}]}
-      ]
-    } = result
+            [
+              {:clause, 5, [{:atom, 5, true}], [], [{:string, 2, 'foo'}]},
+              {:clause, 5, [{:atom, 5, false}], [], [{:string, 4, 'bar'}]}
+            ]} = result
+  end
+
+  describe "tuple" do
+    test "single element tuple" do
+      str = "{1}"
+      ast = Fika.Parser.expression!(str)
+
+      assert {:tuple, 1, [{:integer, 1, 1}]} = ErlTranslate.translate_expression(ast)
+    end
+
+    test "tuple of tuples" do
+      str = "{{1}, {2}}"
+      ast = Fika.Parser.expression!(str)
+
+      assert {
+        :tuple,
+        1,
+        [{:tuple, 1, [{:integer, 1, 1}]}, {:tuple, 1, [{:integer, 1, 2}]}]
+      } = ErlTranslate.translate_expression(ast)
+    end
   end
 end
diff --git a/test/fika/parser_test.exs b/test/fika/parser_test.exs
index b3fd603..f736641 100644
--- a/test/fika/parser_test.exs
+++ b/test/fika/parser_test.exs
@@ -8,7 +8,7 @@ defmodule Fika.ParserTest do
     """
 
     {:ok, result, _rest, _context, _line, _byte_offset} = Parser.expression(str)
-    assert result ==  [{:integer, {1, 0, 3}, 123}]
+    assert result == [{:integer, {1, 0, 3}, 123}]
   end
 
   describe "boolean" do
@@ -16,14 +16,14 @@ defmodule Fika.ParserTest do
       str = "true"
 
       result = Parser.expression!(str)
-      assert result ==  {:boolean, {1, 0, 4}, true}
+      assert result == {:boolean, {1, 0, 4}, true}
     end
 
     test "false" do
       str = "false"
 
       result = Parser.expression!(str)
-      assert result ==  {:boolean, {1, 0, 5}, false}
+      assert result == {:boolean, {1, 0, 5}, false}
     end
   end
 
@@ -44,14 +44,18 @@ defmodule Fika.ParserTest do
 
       {:ok, result, _rest, _context, _line, _byte_offset} = Parser.expression(str)
 
-      assert result == [{
-        :call, {:+, {1, 0, 9}},
-          [
-            {:integer, {1, 0, 1}, 2},
-            {:call, {:*, {1, 0, 9}}, [{:integer, {1, 0, 5}, 3},
-              {:integer, {1, 0, 9}, 4}], :kernel}
-          ], :kernel
-      }]
+      assert result == [
+               {
+                 :call,
+                 {:+, {1, 0, 9}},
+                 [
+                   {:integer, {1, 0, 1}, 2},
+                   {:call, {:*, {1, 0, 9}}, [{:integer, {1, 0, 5}, 3}, {:integer, {1, 0, 9}, 4}],
+                    :kernel}
+                 ],
+                 :kernel
+               }
+             ]
     end
 
     test "add/sub has less precedence than mult/div" do
@@ -61,17 +65,23 @@ defmodule Fika.ParserTest do
 
       {:ok, result, _rest, _context, _line, _byte_offset} = Parser.expression(str)
 
-      assert result == [{
-          :call,
-          {:-, {1, 0, 22}},
-          [
-            {:call, {:+, {1, 0, 22}}, [{:integer, {1, 0, 2}, 10},
-              {:call, {:*, {1, 0, 12}}, [{:integer, {1, 0, 7}, 20},
-                {:integer, {1, 0, 12}, 30}], :kernel}], :kernel},
-              {:call, {:/, {1, 0, 22}}, [{:integer, {1, 0, 17}, 40},
-                {:integer, {1, 0, 22}, 50}], :kernel}
-          ], :kernel
-        }]
+      assert result == [
+               {
+                 :call,
+                 {:-, {1, 0, 22}},
+                 [
+                   {:call, {:+, {1, 0, 22}},
+                    [
+                      {:integer, {1, 0, 2}, 10},
+                      {:call, {:*, {1, 0, 12}},
+                       [{:integer, {1, 0, 7}, 20}, {:integer, {1, 0, 12}, 30}], :kernel}
+                    ], :kernel},
+                   {:call, {:/, {1, 0, 22}},
+                    [{:integer, {1, 0, 17}, 40}, {:integer, {1, 0, 22}, 50}], :kernel}
+                 ],
+                 :kernel
+               }
+             ]
     end
 
     test "grouping using parens" do
@@ -82,28 +92,29 @@ defmodule Fika.ParserTest do
       {:ok, result, _rest, _context, _line, _byte_offset} = Parser.expression(str)
 
       assert result == [
-        {
-          :call,
-          {:/, {1, 0, 26}},
-          [
-            {
-              :call,
-              {:*, {1, 0, 26}},
-                [
-                  {:call, {:+, {1, 0, 8}}, [{:integer,
-                    {1, 0, 3}, 10}, {:integer, {1, 0, 8}, 20}], :kernel},
-                  {:call, {:-, {1, 0, 20}}, [{:integer, {1, 0, 15}, 30},
-                    {:integer, {1, 0, 20}, 40}], :kernel}
-                ],
-                :kernel
-            },
-                {:integer, {1, 0, 26}, 50}
-          ], :kernel}
-      ]
+               {
+                 :call,
+                 {:/, {1, 0, 26}},
+                 [
+                   {
+                     :call,
+                     {:*, {1, 0, 26}},
+                     [
+                       {:call, {:+, {1, 0, 8}},
+                        [{:integer, {1, 0, 3}, 10}, {:integer, {1, 0, 8}, 20}], :kernel},
+                       {:call, {:-, {1, 0, 20}},
+                        [{:integer, {1, 0, 15}, 30}, {:integer, {1, 0, 20}, 40}], :kernel}
+                     ],
+                     :kernel
+                   },
+                   {:integer, {1, 0, 26}, 50}
+                 ],
+                 :kernel
+               }
+             ]
     end
   end
 
-
   describe "function calls" do
     test "local function call without args" do
       str = """
@@ -120,10 +131,12 @@ defmodule Fika.ParserTest do
       """
 
       {:ok, result, _rest, _context, _line, _byte_offset} = Parser.expression(str)
+
       args = [
         {:identifier, {1, 0, 9}, :x},
         {:integer, {1, 0, 14}, 123}
       ]
+
       assert result == [{:call, {:my_func, {1, 0, 15}}, args, nil}]
     end
 
@@ -133,10 +146,12 @@ defmodule Fika.ParserTest do
       """
 
       {:ok, result, _rest, _context, _line, _byte_offset} = Parser.expression(str)
+
       args = [
         {:identifier, {1, 0, 19}, :x},
         {:integer, {1, 0, 24}, 123}
       ]
+
       assert result == [{:call, {:my_func, {1, 0, 25}}, args, :my_module}]
     end
 
@@ -146,10 +161,12 @@ defmodule Fika.ParserTest do
       """
 
       {:ok, result, _rest, _context, _line, _byte_offset} = Parser.expression(str)
+
       args = [
         {:identifier, {1, 0, 5}, :x},
         {:call, {:bar, {1, 0, 13}}, [{:identifier, {1, 0, 12}, :y}], nil}
       ]
+
       assert result == [{:call, {:foo, {1, 0, 14}}, args, nil}]
     end
   end
@@ -165,9 +182,9 @@ defmodule Fika.ParserTest do
       {:ok, result, _rest, _context, _line, _byte_offset} = Parser.function_def(str)
 
       assert result == [
-        {:function, [position: {3, 16, 19}],
-          {:foo, [], {:type, {1, 0, 6}, "Nothing"}, [{:integer, {2, 10, 15}, 123}]}}
-      ]
+               {:function, [position: {3, 16, 19}],
+                {:foo, [], {:type, {1, 0, 6}, "Nothing"}, [{:integer, {2, 10, 15}, 123}]}}
+             ]
     end
 
     test "with return type Int" do
@@ -180,9 +197,9 @@ defmodule Fika.ParserTest do
       {:ok, result, _rest, _context, _line, _byte_offset} = Parser.function_def(str)
 
       assert result == [
-                {:function, [position: {3, 22, 25}],
+               {:function, [position: {3, 22, 25}],
                 {:foo, [], {:type, {1, 0, 12}, "Int"}, [{:integer, {2, 16, 21}, 123}]}}
-            ]
+             ]
     end
 
     test "with return type :ok" do
@@ -195,9 +212,9 @@ defmodule Fika.ParserTest do
       {:ok, result, _rest, _context, _line, _byte_offset} = Parser.function_def(str)
 
       assert result == [
-                {:function, [position: {3, 22, 25}],
+               {:function, [position: {3, 22, 25}],
                 {:foo, [], {:type, {1, 0, 12}, ":ok"}, [{:integer, {2, 16, 21}, 123}]}}
-            ]
+             ]
     end
 
     test "with type params" do
@@ -210,10 +227,10 @@ defmodule Fika.ParserTest do
       {:ok, result, _rest, _context, _line, _byte_offset} = Parser.function_def(str)
 
       assert result == [
-        {:function, [position: {3, 43, 46}],
-          {:foo, [{{:identifier, {1, 0, 8}, :a}, {:type, {1, 0, 22}, "List(String)"}}],
-            {:type, {1, 0, 35}, "List(Int)"}, [{:identifier, {2, 39, 42}, :x}]}}
-      ]
+               {:function, [position: {3, 43, 46}],
+                {:foo, [{{:identifier, {1, 0, 8}, :a}, {:type, {1, 0, 22}, "List(String)"}}],
+                 {:type, {1, 0, 35}, "List(Int)"}, [{:identifier, {2, 39, 42}, :x}]}}
+             ]
     end
 
     test "with args" do
@@ -226,20 +243,17 @@ defmodule Fika.ParserTest do
       {:ok, result, _rest, _context, _line, _byte_offset} = Parser.function_def(str)
 
       assert result == [
-        {:function, [position: {3, 40, 43}],
-          {:foo,
-            [
-              {{:identifier, {1, 0, 8}, :x}, {:type, {1, 0, 13}, "Int"}},
-              {{:identifier, {1, 0, 16}, :y}, {:type, {1, 0, 21}, "Int"}}
-            ],
-            {:type, {1, 0, 28}, "Int"},
-            [
-              {:call, {:+, {2, 32, 39}},
-                [{:identifier, {2, 32, 35}, :x}, {:identifier, {2, 32, 39}, :y}], :kernel}
-            ]
-          }
-        }
-      ]
+               {:function, [position: {3, 40, 43}],
+                {:foo,
+                 [
+                   {{:identifier, {1, 0, 8}, :x}, {:type, {1, 0, 13}, "Int"}},
+                   {{:identifier, {1, 0, 16}, :y}, {:type, {1, 0, 21}, "Int"}}
+                 ], {:type, {1, 0, 28}, "Int"},
+                 [
+                   {:call, {:+, {2, 32, 39}},
+                    [{:identifier, {2, 32, 35}, :x}, {:identifier, {2, 32, 39}, :y}], :kernel}
+                 ]}}
+             ]
     end
   end
 
@@ -255,19 +269,22 @@ defmodule Fika.ParserTest do
       """
 
       assert {
-        {:if, {6, 40, 43}}, parsed_condition, parsed_if_block, parsed_else_block,
-      } = Parser.expression!(str)
+               {:if, {6, 40, 43}},
+               parsed_condition,
+               parsed_if_block,
+               parsed_else_block
+             } = Parser.expression!(str)
 
       assert {:boolean, {1, 0, 7}, true} = parsed_condition
 
       assert [
-        {{:=, {2, 11, 18}}, {:identifier, {2, 11, 14}, :a}, {:integer, {2, 11, 18}, 1}},
-        {{:=, {3, 19, 26}}, {:identifier, {3, 19, 22}, :b}, {:integer, {3, 19, 26}, 2}}
-      ] = parsed_if_block
+               {{:=, {2, 11, 18}}, {:identifier, {2, 11, 14}, :a}, {:integer, {2, 11, 18}, 1}},
+               {{:=, {3, 19, 26}}, {:identifier, {3, 19, 22}, :b}, {:integer, {3, 19, 26}, 2}}
+             ] = parsed_if_block
 
       assert [
-        {{:=, {5, 32, 39}}, {:identifier, {5, 32, 35}, :c}, {:integer, {5, 32, 39}, 3}}
-      ] = parsed_else_block
+               {{:=, {5, 32, 39}}, {:identifier, {5, 32, 35}, :c}, {:integer, {5, 32, 39}, 3}}
+             ] = parsed_else_block
     end
   end
 
@@ -280,8 +297,8 @@ defmodule Fika.ParserTest do
       {:ok, result, _rest, _context, _line, _byte_offset} = Parser.expression(str)
 
       assert result == [
-        {{:=, {1, 0, 5}}, {:identifier, {1, 0, 1}, :x}, {:integer, {1, 0, 5}, 1}}
-      ]
+               {{:=, {1, 0, 5}}, {:identifier, {1, 0, 1}, :x}, {:integer, {1, 0, 5}, 1}}
+             ]
     end
 
     test "multiple match" do
@@ -292,16 +309,16 @@ defmodule Fika.ParserTest do
       {:ok, result, _rest, _context, _line, _byte_offset} = Parser.expression(str)
 
       assert result == [
-        {
-          {:=, {1, 0, 9}},
-          {:identifier, {1, 0, 1}, :x},
-          {
-            {:=, {1, 0, 9}},
-            {:identifier, {1, 0, 5}, :y},
-            {:integer, {1, 0, 9}, 1}
-          }
-        }
-      ]
+               {
+                 {:=, {1, 0, 9}},
+                 {:identifier, {1, 0, 1}, :x},
+                 {
+                   {:=, {1, 0, 9}},
+                   {:identifier, {1, 0, 5}, :y},
+                   {:integer, {1, 0, 9}, 1}
+                 }
+               }
+             ]
     end
 
     test "errors when non match exps come on the left of the match" do
@@ -320,18 +337,16 @@ defmodule Fika.ParserTest do
       {:ok, result, _rest, _context, _line, _byte_offset} = Parser.expression(str)
 
       assert result == [
-        {:call,
-          {:+, {1, 0, 11}},
-          [
-            {:integer, {1, 0, 1}, 1},
-            {
-              {:=, {1, 0, 10}},
-              {:identifier, {1, 0, 6}, :x},
-              {:integer, {1, 0, 10}, 2}
-            }
-          ],
-        :kernel}
-      ]
+               {:call, {:+, {1, 0, 11}},
+                [
+                  {:integer, {1, 0, 1}, 1},
+                  {
+                    {:=, {1, 0, 10}},
+                    {:identifier, {1, 0, 6}, :x},
+                    {:integer, {1, 0, 10}, 2}
+                  }
+                ], :kernel}
+             ]
     end
   end
 
@@ -343,7 +358,7 @@ defmodule Fika.ParserTest do
 
       {:ok, result, _rest, _context, _line, _byte_offset} = Parser.expression(str)
 
-      assert result ==  [{:string, {1, 0, 13}, "Hello world"}]
+      assert result == [{:string, {1, 0, 13}, "Hello world"}]
     end
 
     test "parses a string with escaped double quotes" do
@@ -353,7 +368,7 @@ defmodule Fika.ParserTest do
 
       {:ok, result, _rest, _context, _line, _byte_offset} = Parser.expression(str)
 
-      assert result ==  [{:string, {1, 0, 17}, "Hello \\\"world\\\""}]
+      assert result == [{:string, {1, 0, 17}, "Hello \\\"world\\\""}]
     end
   end
 
@@ -382,11 +397,15 @@ defmodule Fika.ParserTest do
       """
 
       {:ok, result, _rest, _context, _line, _byte_offset} = Parser.expression(str)
-      assert result == [{:list, {1, 0, 9}, [
-        {:integer, {1, 0, 2}, 1},
-        {:integer, {1, 0, 5}, 2},
-        {:integer, {1, 0, 8}, 3},
-      ]}]
+
+      assert result == [
+               {:list, {1, 0, 9},
+                [
+                  {:integer, {1, 0, 2}, 1},
+                  {:integer, {1, 0, 5}, 2},
+                  {:integer, {1, 0, 8}, 3}
+                ]}
+             ]
     end
 
     test "parses list with match exps" do
@@ -396,11 +415,14 @@ defmodule Fika.ParserTest do
 
       {:ok, result, _rest, _context, _line, _byte_offset} = Parser.expression(str)
 
-      assert result == [{:list, {1, 0, 13}, [
-        {{:=, {1, 0, 6}}, {:identifier, {1, 0, 2}, :a}, {:integer, {1, 0, 6}, 1}},
-        {:integer, {1, 0, 9}, 2},
-        {:integer, {1, 0, 12}, 3},
-      ]}]
+      assert result == [
+               {:list, {1, 0, 13},
+                [
+                  {{:=, {1, 0, 6}}, {:identifier, {1, 0, 2}, :a}, {:integer, {1, 0, 6}, 1}},
+                  {:integer, {1, 0, 9}, 2},
+                  {:integer, {1, 0, 12}, 3}
+                ]}
+             ]
     end
   end
 
@@ -409,6 +431,7 @@ defmodule Fika.ParserTest do
       str = """
       {}
       """
+
       assert {:error, _, "{}\n", %{}, {1, 0}, 0} = Parser.expression(str)
     end
 
@@ -419,10 +442,57 @@ defmodule Fika.ParserTest do
 
       {:ok, result, _rest, _context, _line, _byte_offset} = Parser.expression(str)
 
-      assert result == [{:record, {1, 0, 26}, nil, [
-        {{:identifier, {1, 0, 6}, :hello}, {:string, {1, 0, 15}, "World"}},
-        {{:identifier, {1, 0, 20}, :foo}, {:integer, {1, 0, 25}, 123}}
-      ]}]
+      assert result == [
+               {:record, {1, 0, 26}, nil,
+                [
+                  {{:identifier, {1, 0, 6}, :hello}, {:string, {1, 0, 15}, "World"}},
+                  {{:identifier, {1, 0, 20}, :foo}, {:integer, {1, 0, 25}, 123}}
+                ]}
+             ]
+    end
+  end
+
+  describe "tuple" do
+    test "parses tuple with one element" do
+      str = """
+      {1}
+      """
+
+      {:ok, result, _rest, _context, _line, _byte_offset} = Parser.expression(str)
+      assert result == [{:tuple, {1, 0, 3}, [{:integer, {1, 0, 2}, 1}]}]
+    end
+
+    test "parses tuple with multiple elements" do
+      str = """
+      {1, 2, 3}
+      """
+
+      {:ok, result, _rest, _context, _line, _byte_offset} = Parser.expression(str)
+      assert result == [
+        {:tuple, {1, 0, 9}, [
+            {:integer, {1, 0, 2}, 1},
+            {:integer, {1, 0, 5}, 2},
+            {:integer, {1, 0, 8}, 3},
+          ]
+        }
+      ]
+    end
+
+    test "parses tuple with match exps" do
+      str = """
+      {a = 1, 2, 3}
+      """
+
+      {:ok, result, _rest, _context, _line, _byte_offset} = Parser.expression(str)
+
+      assert result == [
+        {:tuple, {1, 0, 13}, [
+            {{:=, {1, 0, 6}}, {:identifier, {1, 0, 2}, :a}, {:integer, {1, 0, 6}, 1}},
+            {:integer, {1, 0, 9}, 2},
+            {:integer, {1, 0, 12}, 3},
+          ]
+        }
+      ]
     end
   end
 
@@ -474,7 +544,7 @@ defmodule Fika.ParserTest do
       assert result == [{:type, {1, 0, 23}, "{foo:Int,bar:String}"}]
     end
 
-     test "atom type" do
+    test "atom type" do
       str = ":foo"
 
       {:ok, result, _rest, _context, _line, _byte_offset} = Parser.type_str(str)
@@ -507,11 +577,13 @@ defmodule Fika.ParserTest do
 
       result = Parser.expression!(str)
 
-      assert result == {:function_ref, {1, 0, 8}, {
-        :foo,
-        :bar,
-        []
-      }}
+      assert result ==
+               {:function_ref, {1, 0, 8},
+                {
+                  :foo,
+                  :bar,
+                  []
+                }}
     end
 
     test "parses a function ref with arg types" do
@@ -521,14 +593,16 @@ defmodule Fika.ParserTest do
 
       result = Parser.expression!(str)
 
-      assert result == {:function_ref, {1, 0, 18}, {
-        :foo,
-        :bar,
-        [
-          "Int",
-          "Int"
-        ]
-      }}
+      assert result ==
+               {:function_ref, {1, 0, 18},
+                {
+                  :foo,
+                  :bar,
+                  [
+                    "Int",
+                    "Int"
+                  ]
+                }}
     end
   end
 
@@ -544,9 +618,9 @@ defmodule Fika.ParserTest do
       {:ok, result, _rest, _context, _line, _byte_offset} = Parser.function_def(str)
 
       assert result == [
-        {:function, [position: {4, 36, 39}],
-          {:foo, [], {:type, {2, 20, 26}, "Nothing"}, [{:integer, {3, 30, 35}, 123}]}}
-      ]
+               {:function, [position: {4, 36, 39}],
+                {:foo, [], {:type, {2, 20, 26}, "Nothing"}, [{:integer, {3, 30, 35}, 123}]}}
+             ]
     end
 
     test "At end of line" do
@@ -559,9 +633,9 @@ defmodule Fika.ParserTest do
       {:ok, result, _rest, _context, _line, _byte_offset} = Parser.function_def(str)
 
       assert result == [
-        {:function, [position: {3, 40, 43}],
-          {:foo, [], {:type, {1, 0, 6}, "Nothing"}, [{:integer, {2, 22, 27}, 123}]}}
-      ]
+               {:function, [position: {3, 40, 43}],
+                {:foo, [], {:type, {1, 0, 6}, "Nothing"}, [{:integer, {2, 22, 27}, 123}]}}
+             ]
     end
 
     test "Can't appear in between characters" do
@@ -584,10 +658,9 @@ defmodule Fika.ParserTest do
       {:ok, result, _rest, _context, _line, _byte_offset} = Parser.function_def(str)
 
       assert result == [
-        {:function, [position: {3, 22, 25}],
-          {:foo, [], {:type, {1, 0, 6}, "Nothing"},
-            [{:string, {2, 10, 21}, "foo#bar"}]}}
-      ]
+               {:function, [position: {3, 22, 25}],
+                {:foo, [], {:type, {1, 0, 6}, "Nothing"}, [{:string, {2, 10, 21}, "foo#bar"}]}}
+             ]
     end
   end
 
@@ -600,6 +673,7 @@ defmodule Fika.ParserTest do
         {:identifier, {1, 0, 6}, :x},
         {:identifier, {1, 0, 9}, :y}
       ]
+
       assert result == {:call, {{:identifier, {1, 0, 3}, :foo}, {1, 0, 10}}, args}
     end
 
@@ -611,6 +685,7 @@ defmodule Fika.ParserTest do
         {:identifier, {1, 0, 8}, :x},
         {:identifier, {1, 0, 11}, :y}
       ]
+
       exp = {:call, {:foo, {1, 0, 5}}, [], nil}
       assert result == {:call, {exp, {1, 0, 12}}, args}
     end
diff --git a/test/fika/type_checker_test.exs b/test/fika/type_checker_test.exs
index 5cce937..c29f598 100644
--- a/test/fika/type_checker_test.exs
+++ b/test/fika/type_checker_test.exs
@@ -1,9 +1,11 @@
 defmodule Fika.TypeCheckerTest do
   use ExUnit.Case
+
   alias Fika.{
     Env,
     TypeChecker
   }
+
   alias Fika.Types.FunctionRef
 
   test "infer type of integer literals" do
@@ -78,6 +80,22 @@ defmodule Fika.TypeCheckerTest do
     assert {:error, "Expected type: Float, got: Int"} = TypeChecker.check(function, env)
   end
 
+  test "checks tuple return type for function" do
+    str = """
+    fn foo : {Float} do
+      {2}
+    end
+    """
+
+    {:module, _, [function]} = ast = Fika.Parser.parse_module(str, "test")
+
+    env =
+      Env.init()
+      |> Env.init_module_env("test", ast)
+
+    assert {:error, "Expected type: {Float}, got: {Int}"} = TypeChecker.check(function, env)
+  end
+
   test "infer return type of another function in the module" do
     str = """
     fn foo(a: Int) : Int do
@@ -147,7 +165,8 @@ defmodule Fika.TypeCheckerTest do
 
       {:ok, [ast], _, _, _, _} = Fika.Parser.expression(str)
 
-      assert {:error, "Elements of list have different types. Expected: Int, got: Float"} = TypeChecker.infer_exp(Env.init(), ast)
+      assert {:error, "Elements of list have different types. Expected: Int, got: Float"} =
+               TypeChecker.infer_exp(Env.init(), ast)
     end
 
     test "list of floats inferred from fn calls" do
@@ -183,6 +202,48 @@ defmodule Fika.TypeCheckerTest do
     end
   end
 
+  describe "tuples" do
+    test "tuple of integers" do
+      str = "{1, 2, 3}"
+
+      ast = Fika.Parser.expression!(str)
+
+      assert {:ok, "{Int,Int,Int}", _env} = TypeChecker.infer_exp(Env.init(), ast)
+    end
+
+    test "tuple of integers and floats" do
+      str = "{1, 2/3, 3}"
+
+      ast = Fika.Parser.expression!(str)
+
+      assert {:ok,"{Int,Float,Int}", _env} = TypeChecker.infer_exp(Env.init(), ast)
+    end
+
+    test "tuple of floats inferred from fn calls" do
+      str = "{1/2, 2/3}"
+
+      ast = Fika.Parser.expression!(str)
+
+      assert {:ok, "{Float,Float}", _env} = TypeChecker.infer_exp(Env.init(), ast)
+    end
+
+    test "tuple of strings" do
+      str = ~s({"foo", "bar"})
+
+      ast = Fika.Parser.expression!(str)
+
+      assert {:ok, "{String,String}", _env} = TypeChecker.infer_exp(Env.init(), ast)
+    end
+
+    test "tuple of tuple of mixed types" do
+      str = ~s({{1, 2/5}, {"3", true}})
+
+      ast = Fika.Parser.expression!(str)
+
+      assert {:ok, "{{Int,Float},{String,Bool}}", _env} = TypeChecker.infer_exp(Env.init(), ast)
+    end
+  end
+
   describe "record" do
     test "unnamed record" do
       str = "{foo: 123, bar: \"Baz\"}"
@@ -211,8 +272,8 @@ defmodule Fika.TypeCheckerTest do
         |> Env.init_module_env("test", ast)
         |> Env.add_function_type("bar.sum(Int,Int)", "Int")
 
-      assert {:ok, %FunctionRef{arg_types: ["Int", "Int"],
-        return_type: "Int"}, _} = TypeChecker.infer_exp(env, ast)
+      assert {:ok, %FunctionRef{arg_types: ["Int", "Int"], return_type: "Int"}, _} =
+               TypeChecker.infer_exp(env, ast)
     end
 
     test "without args" do
@@ -225,7 +286,7 @@ defmodule Fika.TypeCheckerTest do
         |> Env.add_function_type("bar.sum()", "Int")
 
       assert {:ok, %FunctionRef{arg_types: [], return_type: "Int"}, _} =
-        TypeChecker.infer_exp(env, ast)
+               TypeChecker.infer_exp(env, ast)
     end
   end
 
@@ -247,7 +308,7 @@ defmodule Fika.TypeCheckerTest do
 
   describe "if-else expression" do
     test "error when condition expression has non-boolean type" do
-      str =  """
+      str = """
       if "true" do
         "foo"
       else
@@ -259,13 +320,13 @@ defmodule Fika.TypeCheckerTest do
       env = Env.init_module_env(Env.init(), "test", ast)
 
       assert {
-        :error,
-        "Wrong type for if condition. Expected: Bool, Got: String"
-      } = TypeChecker.infer_exp(env, ast)
+               :error,
+               "Wrong type for if condition. Expected: Bool, Got: String"
+             } = TypeChecker.infer_exp(env, ast)
     end
 
     test "completes when if and else blocks have same return type" do
-      str =  """
+      str = """
       if true do
         "done"
       else
@@ -280,7 +341,7 @@ defmodule Fika.TypeCheckerTest do
     end
 
     test "error when if and else blocks have different return types" do
-      str =  """
+      str = """
       if false do
         "done"
       else
@@ -292,13 +353,13 @@ defmodule Fika.TypeCheckerTest do
       env = Env.init_module_env(Env.init(), "test", ast)
 
       assert {
-        :error,
-        "Expected if and else blocks to have same return type. Got String and Int"
-      } = TypeChecker.infer_exp(env, ast)
+               :error,
+               "Expected if and else blocks to have same return type. Got String and Int"
+             } = TypeChecker.infer_exp(env, ast)
     end
 
     test "with multiple expressions in blocks" do
-      str =  """
+      str = """
       if true do
         a = "done"
         a
@@ -348,7 +409,7 @@ defmodule Fika.TypeCheckerTest do
         |> Env.init_module_env("test", ast)
 
       assert {:error, "Expected a function reference, but got type: Int"} =
-        TypeChecker.infer(function, env)
+               TypeChecker.infer(function, env)
     end
 
     test "function ref when given wrong types" do
@@ -366,7 +427,9 @@ defmodule Fika.TypeCheckerTest do
         |> Env.init_module_env("test", ast)
         |> Env.add_function_type("test2.bar(String,Int)", "Bool")
 
-      error = "Expected function reference to be called with arguments (String,Int), but it was called with arguments (Int)"
+      error =
+        "Expected function reference to be called with arguments (String,Int), but it was called with arguments (Int)"
+
       assert {:error, ^error} = TypeChecker.infer(function, env)
     end
   end