Repository: fika-lang/fika (elixir)

Issue:
String interpolation doesn't compile
To be more precise, it seems like string interpolation does compile as long as:
- the "outer string" is empty (`"<--nothing here-->#{anything}<--nothing here-->"`);
- it's used only once (`"#{first_usage}"` is ok, `"#{first_usage}#{second_usage}"` is not).

Examples:
- Single interpolated string with nothing outside: `"#{"string"}"` compiles ✅
- Single interpolated variable with nothing outside:`"#{var}"` compiles ✅
- Single interpolated string with anything outside: `"Hello #{"World"}"` doesn't compile ❌
- Single interpolated variable with anything outside: `"Hello, #{name}!"` doesn't compile ❌
- Multiple interpolated string/variables with or without anything outside: `"#{var}#{"string"}"` doesn't compile ❌

Note that any of this examples gets parsed as intended, the error seems to arise during type checking.

Pull request description:
Fix string interpolation compilation
Closes #45 

Patch:
diff --git a/example.fi b/example.fi
index 2331b67..1c2dfab 100644
--- a/example.fi
+++ b/example.fi
@@ -10,6 +10,13 @@ fn hello : String do
   "World"
 end
 
+# Strings support interpolations
+fn hello_interpolation : String do
+  hello = "Hello!"
+  world = "World"
+  "#{hello}, Greetings to the #{world}"
+end
+
 # This function returns a list of integers.
 fn foo : List(Int) do
   [1, 2, 3]
@@ -96,4 +103,4 @@ fn interval(start: Int, finish: Int) : {:error, String} | {:ok, Int} do
   else
     {:error, "Interval cannot end before it's started"}
   end
-end
\ No newline at end of file
+end
diff --git a/lib/fika/erl_translate.ex b/lib/fika/erl_translate.ex
index 0853927..04fb652 100644
--- a/lib/fika/erl_translate.ex
+++ b/lib/fika/erl_translate.ex
@@ -103,17 +103,22 @@ defmodule Fika.ErlTranslate do
   end
 
   defp translate_exp({:string, {line, _, _}, [value]}) when is_binary(value) do
-    {:string, line, String.to_charlist(value)}
+    string = {:string, line, String.to_charlist(value)}
+    {:bin, line, [{:bin_element, line, string, :default, :default}]}
   end
 
   defp translate_exp({:string, {line, _, _}, str_elements}) do
     translated_exps =
       str_elements
       |> Enum.map(fn
-        value when is_binary(value) -> {:string, line, String.to_charlist(value)}
-        exp -> translate_exp(exp)
+        value when is_binary(value) ->
+          string = {:string, line, String.to_charlist(value)}
+          {:bin_element, line, string, :default, :default}
+
+        exp ->
+          interpolation = translate_exp(exp)
+          {:bin_element, line, interpolation, :default, [:binary]}
       end)
-      |> Enum.map(&{:bin_element, line, &1, :default, :default})
 
     {:bin, line, translated_exps}
   end
diff --git a/lib/fika/type_checker.ex b/lib/fika/type_checker.ex
index 9b0c069..b5c35d1 100644
--- a/lib/fika/type_checker.ex
+++ b/lib/fika/type_checker.ex
@@ -151,15 +151,15 @@ defmodule Fika.TypeChecker do
 
   # String
   def infer_exp(env, {:string, _line, string_parts}) do
-    Enum.reduce_while(string_parts, env, fn
-      string, env when is_binary(string) ->
+    Enum.reduce_while(string_parts, {:ok, nil, env}, fn
+      string, {:ok, _acc_type, acc_env} when is_binary(string) ->
         Logger.debug("String #{string} found. Type: String")
-        {:cont, {:ok, :String, env}}
+        {:cont, {:ok, :String, acc_env}}
 
-      exp, env ->
+      exp, {:ok, _acc_type, acc_env} ->
         Logger.debug("String interpolation found. Inferring type of expression")
 
-        case infer_exp(env, exp) do
+        case infer_exp(acc_env, exp) do
           {:ok, :String, _env} = result ->
             {:cont, result}
 


Test patch:
diff --git a/test/fika/erl_translate_test.exs b/test/fika/erl_translate_test.exs
index cab5f4e..a9e998f 100644
--- a/test/fika/erl_translate_test.exs
+++ b/test/fika/erl_translate_test.exs
@@ -104,8 +104,12 @@ defmodule Fika.ErlTranslateTest do
     ast = TestParser.expression!(str)
     result = ErlTranslate.translate_expression(ast)
 
-    assert result ==
-             {:map, 1, [{:map_field_assoc, 1, {:string, 1, 'foo'}, {:integer, 1, 1}}]}
+    assert {:map, 1,
+            [
+              {:map_field_assoc, 1,
+               {:bin, 1, [{:bin_element, 1, {:string, 1, 'foo'}, :default, :default}]},
+               {:integer, 1, 1}}
+            ]} = result
   end
 
   describe "function reference" do
@@ -169,8 +173,10 @@ defmodule Fika.ErlTranslateTest do
 
     assert {:case, 5, {:atom, 1, true},
             [
-              {:clause, 5, [{:atom, 5, true}], [], [{:string, 2, 'foo'}]},
-              {:clause, 5, [{:atom, 5, false}], [], [{:string, 4, 'bar'}]}
+              {:clause, 5, [{:atom, 5, true}], '',
+               [{:bin, 2, [{:bin_element, 2, {:string, 2, 'foo'}, :default, :default}]}]},
+              {:clause, 5, [{:atom, 5, false}], '',
+               [{:bin, 4, [{:bin_element, 4, {:string, 4, 'bar'}, :default, :default}]}]}
             ]} = result
   end
 
@@ -183,15 +189,16 @@ defmodule Fika.ErlTranslateTest do
       ast = TestParser.expression!(str)
       result = ErlTranslate.translate_expression(ast)
 
-      assert {
-               :bin,
-               1,
-               [
-                 {:bin_element, 1, {:string, 1, 'Hello'}, :default, :default},
-                 {:bin_element, 1, {:string, 1, ' '}, :default, :default},
-                 {:bin_element, 1, {:string, 1, 'World'}, :default, :default}
-               ]
-             } = result
+      assert {:bin, 1,
+              [
+                {:bin_element, 1,
+                 {:bin, 1, [{:bin_element, 1, {:string, 1, 'Hello'}, :default, :default}]},
+                 :default, [:binary]},
+                {:bin_element, 1, {:string, 1, ' '}, :default, :default},
+                {:bin_element, 1,
+                 {:bin, 1, [{:bin_element, 1, {:string, 1, 'World'}, :default, :default}]},
+                 :default, [:binary]}
+              ]} = result
     end
 
     test "parses known variable in string interpolation" do
@@ -204,7 +211,7 @@ defmodule Fika.ErlTranslateTest do
 
       result = ErlTranslate.translate_expression(interpolation)
 
-      assert {:bin, 2, [{:bin_element, 2, {:var, 2, :hello}, :default, :default}]} = result
+      assert {:bin, 2, [{:bin_element, 2, {:var, 2, :hello}, :default, [:binary]}]} = result
     end
   end
 
diff --git a/test/fika/type_checker_test.exs b/test/fika/type_checker_test.exs
index c4ead9a..42774b5 100644
--- a/test/fika/type_checker_test.exs
+++ b/test/fika/type_checker_test.exs
@@ -210,6 +210,19 @@ defmodule Fika.TypeCheckerTest do
       assert {:ok, :String, _} = TypeChecker.infer_block(env, ast)
     end
 
+    test "parses multiple string interpolation with expressions" do
+      str = ~S"""
+      hello = "Hello#{"!"}"
+      "#{hello} Greetings to the #{"world!"}"
+      """
+
+      {:ok, ast, _, _, _, _} = TestParser.exps(str)
+
+      env = Env.init_module_env(Env.init(), "test", ast)
+
+      assert {:ok, :String, _} = TypeChecker.infer_block(env, ast)
+    end
+
     test "accepts only strings in interpolation" do
       str = ~S"""
       "#{1}"