SCUFFED AF struct dereferencing.

Rip out the `StructRef` typechecking, add a "type deconstructor" operator
(postfix `$` sounds cute), and make it work for real.

On the other hand this lame-as-shit module setup appears to work, which
is interesting.
6 files changed, 100 insertions(+), 16 deletions(-)

M fml/src/ast.rs
M fml/src/lib.rs
M fml/src/parser.rs
M fml/src/typeck.rs
M fml/tests/main.rs
A => fml/tests/test_module1.gt
M fml/src/ast.rs +4 -0
@@ 100,6 100,10 @@ pub enum Expr {
         type_params: Vec<Type>,
         body: ExprNode,
     },
+    StructRef {
+        e: ExprNode,
+        name: String,
+    },
 }
 
 impl Expr {

          
M fml/src/lib.rs +7 -1
@@ 52,7 52,13 @@ impl Type {
                     }
                 }
                 Type::Generic(s) => {
-                    accm.push(s.clone());
+                    // Deduplicating these things while maintaining ordering
+                    // is kinda screwy.
+                    // This works, it's just, yanno, also O(n^2)
+                    // Could use a set to check membership , but fuckit for now.
+                    if !accm.contains(s) {
+                        accm.push(s.clone());
+                    }
                 }
             }
         }

          
M fml/src/parser.rs +10 -0
@@ 568,6 568,14 @@ impl<'input> Parser<'input> {
                         let params = self.parse_function_args();
                         ast::ExprNode::new(ast::Expr::Funcall { func: lhs, params })
                     }
+                    T::Period => {
+                        self.expect(T::Period);
+                        let struct_field_name = self.expect_ident();
+                        ast::ExprNode::new(ast::Expr::StructRef {
+                            e: lhs,
+                            name: struct_field_name,
+                        })
+                    }
                     _ => return None,
                 };
                 continue;

          
@@ 762,6 770,8 @@ fn postfix_binding_power(op: &TokenKind)
     match op {
         // "(" opening function call args
         T::LParen => Some((120, ())),
+        // "." separating struct refs a la foo.bar
+        T::Period => Some((110, ())),
         _x => None,
     }
 }

          
M fml/src/typeck.rs +49 -15
@@ 168,7 168,16 @@ impl Tck {
                 ))
             }
             TypeParam(name) => Ok(Type::Generic(name.to_owned())),
-            Struct(_body) => todo!(),
+            Struct(body) => {
+                let real_body: Result<HashMap<_, _>, String> = body
+                    .iter()
+                    .map(|(nm, t)| {
+                        let new_t = self.reconstruct(*t)?;
+                        Ok((nm.clone(), new_t))
+                    })
+                    .collect();
+                Ok(Type::Struct(real_body?))
+            }
         }
     }
 

          
@@ 206,19 215,7 @@ impl Tck {
                 if let Some(ty) = named_types.get(s) {
                     TypeInfo::Ref(*ty)
                 } else {
-                    // Otherwise create a new instance of a typevar that
-                    // this generic matches
-                    // TODO: This might be done better by checking that the
-                    // generic type actually exists in the function's scope or such,
-                    // when naming an unknown generic name this gives "type mismatch"
-                    // rather than "unknown name".  Guess it works for now though.
-                    //
-                    // TODO: The scoping here is still bananas, figure it out
-                    // I think we need to get the generic names from the function call
-                    // and figure 'em out, rather than just casually adding 'em in here
-                    let tid = self.insert(TypeInfo::Unknown);
-                    named_types.insert(s.clone(), tid);
-                    TypeInfo::Ref(tid)
+                    panic!("Referred to unknown generic named {}", s);
                 }
             }
             Type::Func(args, rettype) => {

          
@@ 407,6 404,7 @@ fn typecheck_expr(
         } => {
             typecheck_expr(tck, symtbl, init)?;
             let init_expr_type = tck.get_expr_type(init);
+            // Does our let decl have a type attached to it?
             let var_type = if let Some(t) = typename {
                 tck.insert_known(t)
             } else {

          
@@ 427,6 425,34 @@ fn typecheck_expr(
             tck.set_expr_type(expr, t);
             Ok(t)
         }
+        StructRef { e, name } => {
+            typecheck_expr(tck, symtbl, e)?;
+            let struct_type = tck.get_expr_type(e);
+            // TODO: Not sure this reconstruct does the Right Thing,
+            // especially where type params are involved,
+            // but it has the type signature we need.
+            //
+            // TODO: We really need an operator that "unwraps" a
+            // Named type to its contents, the opposite of a type
+            // constructor.
+            // I suspect when we have that we'll also need to
+            // instantiate the named type's generics, as we do
+            // with TypeCtor.
+            match tck.reconstruct(struct_type)? {
+                Type::Struct(body) => Ok(tck.insert_known(&body[name])),
+                Type::Named(s, args) => {
+                    let hopefully_a_struct = symtbl.get_type(s).unwrap();
+                    match hopefully_a_struct {
+                        Type::Struct(body) => Ok(tck.insert_known(&body[name])),
+                        other => Err(format!("Yeah I know this is wrong bite me")),
+                    }
+                }
+                other => Err(format!(
+                    "Tried to get field named {} but it is an {:?}, not a struct",
+                    name, other
+                )),
+            }
+        }
         TupleCtor { body } => {
             let body_types: Result<Vec<_>, _> = body
                 .iter()

          
@@ 469,7 495,7 @@ fn typecheck_expr(
             // is yet; we make a type var that will get resolved when the enclosing
             // expression is.
             let rettype_var = tck.insert(TypeInfo::Unknown);
-            let funcall_var = tck.insert(TypeInfo::Func(params_list, rettype_var));
+            let funcall_var = tck.insert(TypeInfo::Func(params_list.clone(), rettype_var));
 
             // Now I guess this is where we make a copy of the function
             // with new generic types.

          
@@ 478,7 504,15 @@ fn typecheck_expr(
             // types a function takes as input (our `Generic` or `TypeParam`
             // things I suppose), from "type variables" which are the TypeId
             // we have to solve for.
+            //
+            // So we go through the generics the function declares and create
+            // new type vars for each of them.
             let named_types = &mut HashMap::new();
+            let function_type_params = actual_func_type.get_generic_names();
+            for name in function_type_params.iter() {
+                let tid = tck.insert(TypeInfo::Unknown);
+                named_types.insert(name.clone(), tid);
+            }
             let heck = tck.instantiate(named_types, &actual_func_type);
             tck.unify(symtbl, heck, funcall_var)?;
 

          
M fml/tests/main.rs +6 -0
@@ 144,3 144,9 @@ fn test_let1() {
     let src = include_str!("test_let1.gt");
     let _output = fml::compile("test_let1.gt", src);
 }
+
+#[test]
+fn test_module1() {
+    let src = include_str!("test_module1.gt");
+    let _output = fml::compile("test_module1.gt", src);
+}

          
A => fml/tests/test_module1.gt +24 -0
@@ 0,0 1,24 @@ 
+
+-- A hasher that only accepts basic integers and always returns an
+-- integer.  Implements a particular hash algorithm, with optional
+-- state in it.
+-- Analogous to Rust's std::hash::Hasher
+-- We don't have mutation yet, so this just returns a new state.
+type Hasher(@Self) = struct
+    write: fn(@Self, I32): @Self,
+    finish: fn(@Self): I32,
+end
+
+type DumbHashState = I32
+
+
+fn main(): I32 =
+    let dumbhasher = $Hasher(DumbHashState) {
+        .write = fn(s: DumbHashState, i: I32): DumbHashState = $DumbHashState i end,
+        .finish = fn(s: DumbHashState): I32 = 3 end
+    }
+    let hash_state = $DumbHashState 1
+    let updated_state = dumbhasher.write(hash_state, 12)
+    let hash = dumbhasher.finish(updated_state)
+    3
+end