Repository: fika-lang/fika
Pull request #80 (2021-01-18 22:17:57)

Sort record fields by keys after parsing type and inferring expression
I was trying out Fika for the first time and seeing this error when running the included router example:

> 23:00:25.921 [debug] Setting typecheck result of public function: router.routes() as {:error, "Expected type: List({method: String, path: String, handler: Fn(->String)}), got: List({handler: Fn(->String), path: String, method: String})"}

To resolve it, I modified the compiler so that fields in a Record type definition or record expression are being sorted by the key. Not sure if this is the correct fix (maybe ordering of fields is significant and Fika may want to change the internal Record representation to something different than a map?) - if this is wrong, feel free to close without merging :)

emilsoman: Thanks @michallepicki , I was just about to start looking into this issue when I saw your PR, thank you so much for working on this! 

Patch:
diff --git a/lib/fika/compiler/parser/helper.ex b/lib/fika/compiler/parser/helper.ex
index 7d6e672..5eb68f0 100644
--- a/lib/fika/compiler/parser/helper.ex
+++ b/lib/fika/compiler/parser/helper.ex
@@ -138,7 +138,7 @@ defmodule Fika.Compiler.Parser.Helper do
   end
 
   def do_to_ast({fields, _line}, :record_type) do
-    %T.Record{fields: fields}
+    %T.Record{fields: Enum.sort_by(fields, &elem(&1, 0))}
   end
 
   def do_to_ast({[{:atom, _, atom}], _line}, :atom_type) do
diff --git a/lib/fika/compiler/type_checker.ex b/lib/fika/compiler/type_checker.ex
index e88341d..64c9d50 100644
--- a/lib/fika/compiler/type_checker.ex
+++ b/lib/fika/compiler/type_checker.ex
@@ -205,7 +205,7 @@ defmodule Fika.Compiler.TypeChecker do
     else
       case do_infer_key_values(key_values, env) do
         {:ok, k_v_types, env} ->
-          {:ok, %T.Record{fields: k_v_types}, env}
+          {:ok, %T.Record{fields: Enum.sort_by(k_v_types, &elem(&1, 0))}, env}
 
         error ->
           error


Test patch:
diff --git a/test/fika/compiler/parser_test.exs b/test/fika/compiler/parser_test.exs
index ae8964d..364442d 100644
--- a/test/fika/compiler/parser_test.exs
+++ b/test/fika/compiler/parser_test.exs
@@ -902,7 +902,7 @@ defmodule Fika.Compiler.ParserTest do
     test "record type" do
       str = "{foo: Int, bar: String}"
 
-      assert {:type, {1, 0, 23}, %T.Record{fields: [foo: :Int, bar: :String]}} ==
+      assert {:type, {1, 0, 23}, %T.Record{fields: [bar: :String, foo: :Int]}} ==
                TestParser.type_str!(str)
     end
 
diff --git a/test/fika/compiler/type_checker/match_test.exs b/test/fika/compiler/type_checker/match_test.exs
index c6c1f5a..2c2ed72 100644
--- a/test/fika/compiler/type_checker/match_test.exs
+++ b/test/fika/compiler/type_checker/match_test.exs
@@ -199,8 +199,8 @@ defmodule Fika.Compiler.TypeChecker.MatchTest do
     assert {:ok, env, unmatched} = Match.match_case(%{}, pattern, rhs)
 
     assert unmatched == [
-             %T.Record{fields: [name: :String, id: :Int, x: :b]},
-             %T.Record{fields: [name: :String, id: :Int, x: :c]}
+             %T.Record{fields: [id: :Int, name: :String, x: :b]},
+             %T.Record{fields: [id: :Int, name: :String, x: :c]}
            ]
   end
 
@@ -237,8 +237,8 @@ defmodule Fika.Compiler.TypeChecker.MatchTest do
     {:type, _, type} = TestParser.type_str!(str)
 
     assert Match.expand_unions(type) == [
-             %T.Record{fields: [name: :String, id: :Int, x: :a]},
-             %T.Record{fields: [name: :String, id: :Int, x: :b]}
+             %T.Record{fields: [id: :Int, name: :String, x: :a]},
+             %T.Record{fields: [id: :Int, name: :String, x: :b]}
            ]
   end