From fb981a5ec5398eece21874855aac90bba7df9b6b Mon Sep 17 00:00:00 2001 From: Gert van Valkenhoef Date: Tue, 24 Feb 2026 21:03:05 +0100 Subject: [PATCH 1/8] schurhammer/gig#6: refactor - propagate errors in typed_ast --- src/gig/compiler.gleam | 5 +- src/gig/typed_ast.gleam | 935 ++++++++++++++++++++++------------------ 2 files changed, 527 insertions(+), 413 deletions(-) diff --git a/src/gig/compiler.gleam b/src/gig/compiler.gleam index 9df58cf..36daf8c 100644 --- a/src/gig/compiler.gleam +++ b/src/gig/compiler.gleam @@ -107,7 +107,8 @@ pub fn compile(gleam_file_name: String, options: CompileOptions) { let assert Ok(source_text) = read_source(sources, "gleam") let assert Ok(prelude) = glance.module(source_text) let typed = typed_ast.new_context() - let typed = typed_ast.infer_module(typed, prelude, "gleam", source_text) + let assert Ok(typed) = + typed_ast.infer_module(typed, prelude, "gleam", source_text) // parse and typecheck input (recursively) let #(typed, _done) = infer_file(sources, typed, ["gleam"], module_id) @@ -310,7 +311,7 @@ fn infer_file( // infer this file io.println("Check " <> source) - let c = typed_ast.infer_module(c, module, module_id, source_text) + let assert Ok(c) = typed_ast.infer_module(c, module, module_id, source_text) #(c, done) } diff --git a/src/gig/typed_ast.gleam b/src/gig/typed_ast.gleam index fbc3730..37fe59b 100644 --- a/src/gig/typed_ast.gleam +++ b/src/gig/typed_ast.gleam @@ -323,9 +323,21 @@ pub type Annotation { HoleAnno(typ: Type, name: String) } +// TODO: errors should probably get a Span pub type Error { - UnexpectedEndOfInput - UnexpectedToken + UnresolvedModule(name: String) + UnresolvedGlobal(name: String) + UnresolvedType(name: String) + UnresolvedFunction(name: String) + EmptyBlock + InvalidTupleAccess + InvalidFieldAccess + FieldNotFound + UnresolvedTypeVariable(String) + NotAFunction(ResolvedGlobal) + WrongArity(expected_arg_count: Int, actual_arg_count: Int) + LabelNotFound(String) + TupleIndexOutOfBounds(tuple_size: Int, index: Int) } pub type QualifiedName { @@ -369,7 +381,7 @@ pub fn infer_module( module: g.Module, module_name: String, module_source: String, -) -> Context { +) -> Result(Context, Error) { let modules = dict.insert( c.modules, @@ -390,9 +402,9 @@ pub fn infer_module( let c = Context(..c, modules:, current_module: module_name, module_source:) // handle module imports - let c = - list.fold(module.imports, c, fn(c, imp) { - update_module(c, fn(module) { + use c <- result.try( + list.try_fold(module.imports, c, fn(c, imp) { + try_update_module(c, fn(module) { let imp = imp.definition let module_id = imp.module @@ -403,35 +415,42 @@ pub fn infer_module( g.Discarded(_) -> module.module_env } None -> { + // assert: imported name is a non-empty string let assert Ok(alias) = list.last(string.split(module_id, "/")) dict.insert(module.module_env, alias, module_id) } } - let type_env = - list.fold(imp.unqualified_types, module.type_env, fn(acc, imp) { - let assert Ok(#(_, poly, variants)) = - resolve_global_type_name(c, module_id, imp.name) + use type_env <- result.try( + list.try_fold(imp.unqualified_types, module.type_env, fn(acc, imp) { + use #(_, poly, variants) <- result.map(resolve_global_type_name( + c, + module_id, + imp.name, + )) let alias = case imp.alias { Some(alias) -> alias None -> imp.name } dict.insert(acc, alias, #(poly, variants)) - }) + }), + ) - let value_env = - list.fold(imp.unqualified_values, module.value_env, fn(acc, imp) { - let assert Ok(value) = resolve_global_name(c, module_id, imp.name) + use value_env <- result.map( + list.try_fold(imp.unqualified_values, module.value_env, fn(acc, imp) { + use value <- result.map(resolve_global_name(c, module_id, imp.name)) let alias = case imp.alias { Some(alias) -> alias None -> imp.name } dict.insert(acc, alias, value) - }) + }), + ) Module(..module, module_env:, type_env:, value_env:) }) - }) + }), + ) // add types to env so they can reference eachother (but not yet constructors) let c = @@ -462,27 +481,31 @@ pub fn infer_module( }) // infer type aliases fr fr - let #(c, aliases) = - list.fold(module.type_aliases, #(c, []), fn(acc, def) { + use #(c, aliases) <- result.try( + list.try_fold(module.type_aliases, #(c, []), fn(acc, def) { let #(c, aliases) = acc let c = Context(..c, current_definition: def.definition.name) let c = Context(..c, current_span: def.definition.location) // infer the alias type - let #(c, alias) = infer_alias_type(c, def.definition) + use #(c, alias) <- result.try(infer_alias_type(c, def.definition)) // update the placeholder type - let assert Ok(#(_, placeholder, _)) = - resolve_global_type_name(c, c.current_module, alias.name) + use #(_, placeholder, _) <- result.map(resolve_global_type_name( + c, + c.current_module, + alias.name, + )) let c = unify(c, alias.aliased.typ, placeholder.typ) #(c, [#(def, alias), ..aliases]) - }) + }), + ) // create alias entries // we have to do this in two stages to make sure we genralize correctly - let c = - list.fold(aliases, c, fn(c, alias) { + use c <- result.try( + list.try_fold(aliases, c, fn(c, alias) { let #(def, alias) = alias let c = Context(..c, current_definition: alias.name) let c = Context(..c, current_span: def.definition.location) @@ -490,35 +513,44 @@ pub fn infer_module( // create alias entry let poly = generalise(c, alias.aliased.typ) let c = register_type(c, alias.name, poly, []) - let attrs = infer_attributes(c, def.attributes) + use attrs <- result.map(infer_attributes(c, def.attributes)) let def = Definition(attrs, alias) update_module(c, fn(mod) { Module(..mod, type_aliases: [def, ..mod.type_aliases]) }) - }) + }), + ) // now infer custom types fr fr - let c = - list.fold(module.custom_types, c, fn(c, def) { + use c <- result.try( + list.try_fold(module.custom_types, c, fn(c, def) { let custom = def.definition let c = Context(..c, current_definition: custom.name) let c = Context(..c, current_span: def.definition.location) // reconstruct the type parameters - let assert Ok(#(_, poly, _)) = - resolve_global_type_name(c, c.current_module, custom.name) + use #(_, poly, _) <- result.try(resolve_global_type_name( + c, + c.current_module, + custom.name, + )) let param_types = list.map(poly.vars, fn(x) { VariableType(Ref(x)) }) let parameters = list.zip(custom.parameters, param_types) // infer the custom type including variants - let #(c, custom) = infer_custom_type(c, def.definition, parameters) + use #(c, custom) <- result.try(infer_custom_type( + c, + def.definition, + parameters, + )) let c = register_type(c, custom.name, custom.typ, custom.variants) - let attrs = infer_attributes(c, def.attributes) + use attrs <- result.map(infer_attributes(c, def.attributes)) let def = Definition(attrs, custom) update_module(c, fn(mod) { Module(..mod, custom_types: [def, ..mod.custom_types]) }) - }) + }), + ) let constants = call_graph.constant_graph(module) @@ -530,15 +562,18 @@ pub fn infer_module( }) // add functions to global env so they are available for recursion - let c = - list.fold(module.functions, c, fn(c, def) { + use c <- result.try( + list.try_fold(module.functions, c, fn(c, def) { let fun = def.definition let c = Context(..c, current_definition: fun.name) let c = Context(..c, current_span: def.definition.location) // create placeholder function type based on function signature - let #(c, parameters, return) = - infer_function_parameters(c, fun.parameters, fun.return) + use #(c, parameters, return) <- result.map(infer_function_parameters( + c, + fun.parameters, + fun.return, + )) let #(c, return_type) = case return { Some(x) -> #(c, x.typ) @@ -550,23 +585,25 @@ pub fn infer_module( let typ = FunctionType(param_types, return_type) register_function(c, def.definition.name, Poly([], typ), param_labels) - }) + }), + ) // infer constant expressions - let c = - list.fold(constants, c, fn(c, def) { - let #(c, constant) = infer_constant(c, def.definition) + use c <- result.try( + list.try_fold(constants, c, fn(c, def) { + use #(c, constant) <- result.try(infer_constant(c, def.definition)) let c = Context(..c, current_definition: constant.name) let c = Context(..c, current_span: def.definition.location) let poly = generalise(c, constant.value.typ) let c = register_constant(c, constant.name, poly, constant.value) - let attrs = infer_attributes(c, def.attributes) + use attrs <- result.map(infer_attributes(c, def.attributes)) let def = Definition(attrs, constant) update_module(c, fn(mod) { Module(..mod, constants: [def, ..mod.constants]) }) - }) + }), + ) // create a function call graph to group mutually recursive functions // these will be type checked/inferred together as a group @@ -574,35 +611,41 @@ pub fn infer_module( call_graph.function_graph(module) |> graph.strongly_connected_components() - list.fold(rec_groups, c, fn(c, group) { + list.try_fold(rec_groups, c, fn(c, group) { // find the function definitions by name - let assert Ok(group) = + use group <- result.try( list.try_map(group, fn(fun_name) { list.find(module.functions, fn(f) { f.definition.name == fun_name }) - }) + |> result.replace_error(UnresolvedFunction(fun_name)) + }), + ) // infer types for the group - let #(c, group) = - list.fold(group, #(c, []), fn(acc, def) { + use #(c, group) <- result.try( + list.try_fold(group, #(c, []), fn(acc, def) { let #(c, group) = acc let c = Context(..c, current_definition: def.definition.name) let c = Context(..c, current_span: def.definition.location) // infer function - let #(c, fun) = infer_function(c, def.definition) - let attrs = infer_attributes(c, def.attributes) + use #(c, fun) <- result.try(infer_function(c, def.definition)) + use attrs <- result.map(infer_attributes(c, def.attributes)) let def = Definition(attrs, fun) #(c, [def, ..group]) - }) + }), + ) // generalise - list.fold(group, c, fn(c, def) { + list.try_fold(group, c, fn(c, def) { let fun = def.definition // unify placeholder type - let assert Ok(placeholder) = - resolve_global_name(c, c.current_module, fun.name) + use placeholder <- result.map(resolve_global_name( + c, + c.current_module, + fun.name, + )) let c = unify(c, placeholder.typ.typ, fun.typ.typ) // generalise @@ -628,11 +671,8 @@ fn generalise(c: Context, typ: Type) { } fn get_current_module(c: Context) -> Module { - get_module(c, c.current_module) -} - -fn get_module(c: Context, module: String) -> Module { - let assert Ok(module) = dict.get(c.modules, module) + // assert: the current module exists + let assert Ok(module) = dict.get(c.modules, c.current_module) module } @@ -643,6 +683,13 @@ fn update_module(c: Context, fun: fn(Module) -> Module) { Context(..c, modules:) } +fn try_update_module(c: Context, fun: fn(Module) -> Result(Module, Error)) { + let module = get_current_module(c) + use module <- result.map(fun(module)) + let modules = dict.insert(c.modules, c.current_module, module) + Context(..c, modules:) +} + fn register_function( c: Context, name: String, @@ -696,46 +743,57 @@ fn infer_attributes(c: Context, attrs: List(g.Attribute)) { |> dict.insert("erlang", nil_type) |> dict.insert("javascript", nil_type) - let #(_, attrs) = - list.fold(attrs, #(c, []), fn(acc, attr) { + use #(_, attrs) <- result.map( + list.try_fold(attrs, #(c, []), fn(acc, attr) { let #(c, attrs) = acc - let #(c, exprs) = - list.fold(attr.arguments, #(c, []), fn(acc, attr) { + use #(c, exprs) <- result.map( + list.try_fold(attr.arguments, #(c, []), fn(acc, attr) { let #(c, exprs) = acc - let assert Ok(#(c, expr)) = infer_expression(c, attr_env, attr) + use #(c, expr) <- result.map(infer_expression(c, attr_env, attr)) #(c, [expr, ..exprs]) - }) + }), + ) let exprs = list.reverse(exprs) let attr = Attribute(attr.name, exprs) #(c, [attr, ..attrs]) - }) + }), + ) list.reverse(attrs) } -fn infer_constant(c: Context, con: g.Constant) -> #(Context, ConstantDefinition) { - let assert Ok(#(c, value)) = infer_expression(c, dict.new(), con.value) +fn infer_constant( + c: Context, + con: g.Constant, +) -> Result(#(Context, ConstantDefinition), Error) { + use #(c, value) <- result.try(infer_expression(c, dict.new(), con.value)) let publicity = case con.publicity { g.Public -> Public g.Private -> Private } - let #(c, annotation) = case con.annotation { + use #(c, annotation) <- result.map(case con.annotation { Some(anno) -> { - let #(c, anno) = do_infer_annotation(c, dict.new(), anno) + use #(c, anno) <- result.map(do_infer_annotation(c, dict.new(), anno)) #(c, Some(anno)) } - None -> #(c, None) - } + None -> Ok(#(c, None)) + }) let constant = ConstantDefinition(con.name, publicity, annotation, value) #(c, constant) } -fn infer_function(c: Context, fun: g.Function) -> #(Context, FunctionDefinition) { - let #(c, parameters, return) = - infer_function_parameters(c, fun.parameters, fun.return) +fn infer_function( + c: Context, + fun: g.Function, +) -> Result(#(Context, FunctionDefinition), Error) { + use #(c, parameters, return) <- result.try(infer_function_parameters( + c, + fun.parameters, + fun.return, + )) let #(c, return_type) = case return { Some(x) -> #(c, x.typ) @@ -752,7 +810,7 @@ fn infer_function(c: Context, fun: g.Function) -> #(Context, FunctionDefinition) }) // infer body - let #(c, body) = infer_body(c, n, fun.body) + use #(c, body) <- result.map(infer_body(c, n, fun.body)) // compute function type let parameter_types = list.map(parameters, fn(x) { x.typ }) @@ -788,7 +846,10 @@ fn infer_function(c: Context, fun: g.Function) -> #(Context, FunctionDefinition) #(c, fun) } -fn infer_alias_type(c: Context, alias: g.TypeAlias) -> #(Context, TypeAlias) { +fn infer_alias_type( + c: Context, + alias: g.TypeAlias, +) -> Result(#(Context, TypeAlias), Error) { let publicity = case alias.publicity { g.Public -> Public g.Private -> Private @@ -802,13 +863,18 @@ fn infer_alias_type(c: Context, alias: g.TypeAlias) -> #(Context, TypeAlias) { let #(c, n, args) = acc let #(c, typ) = new_type_var_ref(c) let n = dict.insert(n, name, typ) + // TODO: does this need wrapping in a result or is it guaranteed to succeed? let assert VariableType(ref) = typ #(c, n, [ref.id, ..args]) }) let args = list.reverse(args) - let #(c, aliased) = do_infer_annotation(c, type_env, alias.aliased) + use #(c, aliased) <- result.map(do_infer_annotation( + c, + type_env, + alias.aliased, + )) let poly = Poly(args, aliased.typ) @@ -834,12 +900,13 @@ fn infer_custom_type( list.fold(parameters, dict.new(), fn(n, p) { dict.insert(n, p.0, p.1) }) // process each variant - let #(c, variants) = - list.fold(custom.variants, #(c, []), fn(acc, variant) { + use #(c, variants) <- result.map( + list.try_fold(custom.variants, #(c, []), fn(acc, variant) { let #(c, l) = acc - let #(c, v) = infer_variant(c, n, typ, variant) + use #(c, v) <- result.map(infer_variant(c, n, typ, variant)) #(c, [v, ..l]) - }) + }), + ) let variants = list.reverse(variants) let opaque_ = custom.opaque_ @@ -857,18 +924,24 @@ fn infer_custom_type( #(c, custom) } -fn infer_variant(c, n, typ: Type, variant: g.Variant) -> #(Context, Variant) { - let #(c, fields) = - list.fold(variant.fields, #(c, []), fn(acc, field) { +fn infer_variant( + c, + n, + typ: Type, + variant: g.Variant, +) -> Result(#(Context, Variant), Error) { + use #(c, fields) <- result.map( + list.try_fold(variant.fields, #(c, []), fn(acc, field) { let #(c, fields) = acc - let #(c, annotation) = do_infer_annotation(c, n, field.item) + use #(c, annotation) <- result.map(do_infer_annotation(c, n, field.item)) let label = case field { g.LabelledVariantField(_, label) -> Some(label) g.UnlabelledVariantField(_) -> None } let field = Field(label, annotation) #(c, [field, ..fields]) - }) + }), + ) let fields = list.reverse(fields) let types = list.map(fields, fn(f) { f.item.typ }) @@ -903,7 +976,7 @@ fn infer_function_parameters( c: Context, parameters: List(g.FunctionParameter), return: Option(g.Type), -) -> #(Context, List(FunctionParameter), Option(Annotation)) { +) -> Result(#(Context, List(FunctionParameter), Option(Annotation)), Error) { // find type variables used in the function's parameters let vars = list.flat_map(parameters, fn(param) { @@ -931,8 +1004,8 @@ fn infer_function_parameters( }) // create type vars for parameters - let #(c, params) = - list.fold(parameters, #(c, []), fn(acc, param) { + use #(c, params) <- result.try( + list.try_fold(parameters, #(c, []), fn(acc, param) { let #(c, param_types) = acc let label = param.label @@ -942,13 +1015,13 @@ fn infer_function_parameters( g.Discarded(s) -> Discarded(s) } - let #(c, annotation) = case param.type_ { + use #(c, annotation) <- result.map(case param.type_ { Some(typ) -> { - let #(c, anno) = do_infer_annotation(c, type_env, typ) + use #(c, anno) <- result.map(do_infer_annotation(c, type_env, typ)) #(c, Some(anno)) } - None -> #(c, None) - } + None -> Ok(#(c, None)) + }) let #(c, typ) = case annotation { Some(a) -> #(c, a.typ) @@ -956,17 +1029,18 @@ fn infer_function_parameters( } #(c, [FunctionParameter(typ, label, name, annotation), ..param_types]) - }) + }), + ) let params = list.reverse(params) // handle function return type - let #(c, return) = case return { + use #(c, return) <- result.map(case return { Some(typ) -> { - let #(c, anno) = do_infer_annotation(c, type_env, typ) + use #(c, anno) <- result.map(do_infer_annotation(c, type_env, typ)) #(c, Some(anno)) } - None -> #(c, None) - } + None -> Ok(#(c, None)) + }) #(c, params, return) } @@ -975,58 +1049,72 @@ fn do_infer_annotation( c: Context, n: TypeEnv, typ: g.Type, -) -> #(Context, Annotation) { +) -> Result(#(Context, Annotation), Error) { case typ { g.NamedType(_, name, anno_module, params) -> { - let #(c, params) = - list.fold(params, #(c, []), fn(acc, p) { + use #(c, params) <- result.try( + list.try_fold(params, #(c, []), fn(acc, p) { let #(c, l) = acc - let #(c, p) = do_infer_annotation(c, n, p) + use #(c, p) <- result.map(do_infer_annotation(c, n, p)) #(c, [p, ..l]) - }) + }), + ) let params = list.reverse(params) // instantiate the polymorphic type with the parameter types - let assert Ok(#(_, poly, _variants)) = - resolve_type_name(c, anno_module, name) + use #(_, poly, _variants) <- result.try(resolve_type_name( + c, + anno_module, + name, + )) let param_types = list.map(params, fn(param) { param.typ }) - let assert Ok(mapping) = list.strict_zip(poly.vars, param_types) - let mapping = dict.from_list(mapping) + use mapping <- result.map( + list.strict_zip(poly.vars, param_types) + |> result.map_error(fn(_) { + WrongArity(list.length(poly.vars), list.length(param_types)) + }) + |> result.map(dict.from_list), + ) let typ = do_instantiate(c, mapping, poly.typ) // let typ = NamedType(name, module, list.map(params, fn(x) { x.typ })) #(c, NamedAnno(typ, name, anno_module, params)) } g.TupleType(_, elements) -> { - let #(c, elements) = - list.fold(elements, #(c, []), fn(acc, p) { + use #(c, elements) <- result.map( + list.try_fold(elements, #(c, []), fn(acc, p) { let #(c, l) = acc - let #(c, p) = do_infer_annotation(c, n, p) + use #(c, p) <- result.map(do_infer_annotation(c, n, p)) #(c, [p, ..l]) - }) + }), + ) let elements = list.reverse(elements) let typ = TupleType(list.map(elements, fn(x) { x.typ })) #(c, TupleAnno(typ, elements)) } g.FunctionType(_, parameters, return) -> { - let #(c, params) = - list.fold(parameters, #(c, []), fn(acc, p) { + use #(c, params) <- result.try( + list.try_fold(parameters, #(c, []), fn(acc, p) { let #(c, l) = acc - let #(c, p) = do_infer_annotation(c, n, p) + use #(c, p) <- result.map(do_infer_annotation(c, n, p)) #(c, [p, ..l]) - }) + }), + ) let params = list.reverse(params) - let #(c, ret) = do_infer_annotation(c, n, return) + use #(c, ret) <- result.map(do_infer_annotation(c, n, return)) let typ = FunctionType(list.map(params, fn(x) { x.typ }), ret.typ) #(c, FunctionAnno(typ, params, ret)) } g.VariableType(_, name) -> { - let assert Ok(typ) = dict.get(n, name) + use typ <- result.map( + dict.get(n, name) + |> result.replace_error(UnresolvedTypeVariable(name)), + ) #(c, VariableAnno(typ, name)) } g.HoleType(_, name) -> { let #(c, typ) = new_type_var_ref(c) - #(c, HoleAnno(typ, name)) + Ok(#(c, HoleAnno(typ, name))) } } } @@ -1046,122 +1134,110 @@ type ResolvedVariable { ResolvedGlobal(global: ResolvedGlobal) } +/// Resolve an unqualified name against the local and then global environment. fn resolve_unqualified_name( c: Context, n: LocalEnv, name: String, -) -> Result(ResolvedVariable, String) { - case dict.get(n, name) { - // try local env - Ok(typ) -> Ok(ResolvedLocal(name, typ)) - Error(_) -> - case resolve_unqualified_global(c, name) { - Ok(value) -> Ok(ResolvedGlobal(value)) - Error(e) -> Error(e) - } - } +) -> Result(ResolvedVariable, Error) { + dict.get(n, name) + |> result.map(ResolvedLocal(name, _)) + |> result.try_recover(fn(_) { + resolve_unqualified_global(c, name) |> result.map(ResolvedGlobal) + }) } +/// Resolve an unqualified name against the global environment. fn resolve_unqualified_global( c: Context, name: String, -) -> Result(ResolvedGlobal, String) { +) -> Result(ResolvedGlobal, Error) { // try global env - case resolve_global_name(c, c.current_module, name) { - Ok(value) -> Ok(value) + resolve_global_name(c, c.current_module, name) + |> result.try_recover(fn(_) { // try prelude - Error(_) -> { - case resolve_global_name(c, builtin, name) { - Ok(value) -> Ok(value) - Error(_) -> Error("Could not resolve name " <> name) - } - } - } + resolve_global_name(c, builtin, name) + }) } -fn resolve_aliased_name( +/// Resolve a global from a possibly aliased module +fn resolve_aliased_global( c: Context, name: QualifiedName, -) -> Result(ResolvedGlobal, String) { - case dict.get(get_current_module(c).module_env, name.module) { - Ok(module) -> resolve_global_name(c, module, name.name) - Error(_) -> Error("Could not resolve module name " <> name.module) - } +) -> Result(ResolvedGlobal, Error) { + resolve_module(c, name.module) + |> result.try(resolve_global_name(c, _, name.name)) } +/// Resolve a name from the global environment pub fn resolve_global_name( c: Context, module_name: String, name: String, -) -> Result(ResolvedGlobal, String) { - case dict.get(c.modules, module_name) { - Ok(module) -> - case dict.get(module.value_env, name) { - Ok(value) -> Ok(value) - Error(_) -> Error("Could not resolve name " <> name) - } - Error(_) -> Error("Missing module " <> module_name) - } +) -> Result(ResolvedGlobal, Error) { + use module <- result.try(get_module(c, module_name)) + dict.get(module.value_env, name) + |> result.replace_error(UnresolvedGlobal(name)) } +/// Resolve a type name from the global environment fn resolve_type_name( c: Context, mod: Option(String), name: String, -) -> Result(#(QualifiedName, Poly, List(Variant)), String) { +) -> Result(#(QualifiedName, Poly, List(Variant)), Error) { case mod { Some(mod) -> resolve_aliased_type_name(c, mod, name) None -> - case resolve_global_type_name(c, c.current_module, name) { - Ok(result) -> Ok(result) - Error(_) -> - case resolve_global_type_name(c, builtin, name) { - Ok(result) -> Ok(result) - Error(_) -> Error("Could not resolve type " <> name) - } - } + resolve_global_type_name(c, c.current_module, name) + |> result.try_recover(fn(_) { resolve_global_type_name(c, builtin, name) }) } } +/// Resolve a type name from a possibly aliased module fn resolve_aliased_type_name( c: Context, module: String, name: String, -) -> Result(#(QualifiedName, Poly, List(Variant)), String) { - case dict.get(get_current_module(c).module_env, module) { - Ok(module) -> resolve_global_type_name(c, module, name) - Error(_) -> Error("Could not resolve module name " <> module) - } +) -> Result(#(QualifiedName, Poly, List(Variant)), Error) { + resolve_module(c, module) + |> result.try(resolve_global_type_name(c, _, name)) } +// Resolve a type name from a fully qualified module name pub fn resolve_global_type_name( c: Context, - module: String, + module_name: String, name: String, -) -> Result(#(QualifiedName, Poly, List(Variant)), String) { - case dict.get(c.modules, module) { - Ok(mod) -> - case dict.get(mod.type_env, name) { - Ok(#(typ, variants)) -> - Ok(#(QualifiedName(module, name), typ, variants)) - Error(_) -> Error("Could not resolve type " <> name) - } - Error(_) -> Error("Missing module " <> module) - } +) -> Result(#(QualifiedName, Poly, List(Variant)), Error) { + use module <- result.try(get_module(c, module_name)) + use #(typ, variants) <- result.map( + dict.get(module.type_env, name) + |> result.replace_error(UnresolvedType(module_name <> "." <> name)), + ) + #(QualifiedName(module_name, name), typ, variants) } +/// Resolve a qualified or unqualified contructor name fn resolve_constructor_name(c: Context, mod: Option(String), name: String) { case mod { - Some(mod) -> { - let q_name = QualifiedName(mod, name) - resolve_aliased_name(c, q_name) - } - None -> { - resolve_unqualified_global(c, name) - } + Some(mod) -> resolve_aliased_global(c, QualifiedName(mod, name)) + None -> resolve_unqualified_global(c, name) } } +/// Resolve a module alias to its fully qualified name +fn resolve_module(c: Context, module_name: String) -> Result(String, Error) { + dict.get(get_current_module(c).module_env, module_name) + |> result.replace_error(UnresolvedModule(module_name)) +} + +/// Get a module by its fully qualified name +fn get_module(c: Context, module_name: String) -> Result(Module, Error) { + dict.get(c.modules, module_name) + |> result.replace_error(UnresolvedModule(module_name)) +} + fn new_temp_var(c: Context) -> #(Context, String) { let id = "T" <> int.to_string(c.temp_uid) #(Context(..c, temp_uid: c.temp_uid + 1), id) @@ -1178,29 +1254,30 @@ fn infer_pattern( c: Context, n: LocalEnv, pattern: g.Pattern, -) -> #(Context, LocalEnv, Pattern) { +) -> Result(#(Context, LocalEnv, Pattern), Error) { case pattern { - g.PatternInt(_, value) -> #(c, n, PatternInt(int_type, value)) - g.PatternFloat(_, value) -> #(c, n, PatternFloat(float_type, value)) - g.PatternString(_, value) -> #(c, n, PatternString(string_type, value)) + g.PatternInt(_, value) -> Ok(#(c, n, PatternInt(int_type, value))) + g.PatternFloat(_, value) -> Ok(#(c, n, PatternFloat(float_type, value))) + g.PatternString(_, value) -> Ok(#(c, n, PatternString(string_type, value))) g.PatternDiscard(_, name) -> { let #(c, typ) = new_type_var_ref(c) - #(c, n, PatternDiscard(typ, name)) + Ok(#(c, n, PatternDiscard(typ, name))) } g.PatternVariable(_, name) -> { let #(c, typ) = new_type_var_ref(c) let pattern = PatternVariable(typ, name) let n = dict.insert(n, name, typ) - #(c, n, pattern) + Ok(#(c, n, pattern)) } g.PatternTuple(_, elements) -> { // Infer types for all elements in the tuple pattern - let #(c, n, elems) = - list.fold(elements, #(c, n, []), fn(acc, elem) { + use #(c, n, elems) <- result.map( + list.try_fold(elements, #(c, n, []), fn(acc, elem) { let #(c, n, patterns) = acc - let #(c, n, pattern) = infer_pattern(c, n, elem) + use #(c, n, pattern) <- result.map(infer_pattern(c, n, elem)) #(c, n, [pattern, ..patterns]) - }) + }), + ) let elems = list.reverse(elems) // Create the tuple type from the inferred element types @@ -1210,12 +1287,13 @@ fn infer_pattern( } g.PatternList(_, elements, tail) -> { // Infer types for all elements in the list pattern - let #(c, n, elements) = - list.fold(elements, #(c, n, []), fn(acc, elem) { + use #(c, n, elements) <- result.try( + list.try_fold(elements, #(c, n, []), fn(acc, elem) { let #(c, n, patterns) = acc - let #(c, n, pattern) = infer_pattern(c, n, elem) + use #(c, n, pattern) <- result.map(infer_pattern(c, n, elem)) #(c, n, [pattern, ..patterns]) - }) + }), + ) let elements = list.reverse(elements) // Create a type variable for the element type @@ -1229,21 +1307,21 @@ fn infer_pattern( let typ = NamedType("List", builtin, [elem_type]) // Handle the tail pattern if present - let #(c, n, tail) = case tail { + use #(c, n, tail) <- result.map(case tail { Some(tail_pattern) -> { - let #(c, n, tail) = infer_pattern(c, n, tail_pattern) + use #(c, n, tail) <- result.map(infer_pattern(c, n, tail_pattern)) // The tail should be a list of the same type let c = unify(c, tail.typ, typ) #(c, n, Some(tail)) } - None -> #(c, n, None) - } + None -> Ok(#(c, n, None)) + }) #(c, n, PatternList(typ, elements, tail)) } g.PatternAssignment(_, pattern, name) -> { // First, infer the type of the inner pattern - let #(c, n, pattern) = infer_pattern(c, n, pattern) + use #(c, n, pattern) <- result.map(infer_pattern(c, n, pattern)) // Create the PatternAssignment with the same type as the inner pattern let pattern = PatternAssignment(pattern.typ, pattern, name) @@ -1276,56 +1354,44 @@ fn infer_pattern( let pattern = PatternConcatenate(string_type, prefix, prefix_name, rest_name_result) - #(c, n, pattern) + Ok(#(c, n, pattern)) } g.PatternBitString(_, segments) -> { - let #(c, n, segs) = - list.fold(segments, #(c, n, []), fn(acc, seg) { + use #(c, n, segs) <- result.map( + list.try_fold(segments, #(c, n, []), fn(acc, seg) { let #(c, n, segs) = acc let #(pattern, options) = seg - let #(c, n, options, typ) = - list.fold(options, #(c, n, [], None), fn(acc, option) { + use #(c, n, options, typ) <- result.try( + list.try_fold(options, #(c, n, [], None), fn(acc, option) { let #(c, n, options, typ) = acc - let #(c, n, option, option_type) = case option { - g.BigOption -> #(c, n, BigOption, None) - g.LittleOption -> #(c, n, LittleOption, None) - g.NativeOption -> #(c, n, NativeOption, None) - g.SignedOption -> #(c, n, SignedOption, None) - g.UnsignedOption -> #(c, n, UnsignedOption, None) - g.BytesOption -> #(c, n, BytesOption, Some(bit_array_type)) - g.BitsOption -> #(c, n, BitsOption, Some(bit_array_type)) - g.IntOption -> #(c, n, IntOption, Some(int_type)) - g.FloatOption -> #(c, n, FloatOption, Some(float_type)) - g.Utf8Option -> #(c, n, Utf8Option, Some(string_type)) - g.Utf16Option -> #(c, n, Utf16Option, Some(string_type)) - g.Utf32Option -> #(c, n, Utf32Option, Some(string_type)) - g.Utf8CodepointOption -> #( - c, - n, - Utf8CodepointOption, - Some(codepoint_type), - ) - g.Utf16CodepointOption -> #( - c, - n, - Utf16CodepointOption, - Some(codepoint_type), - ) - g.Utf32CodepointOption -> #( - c, - n, - Utf32CodepointOption, - Some(codepoint_type), - ) - g.SizeOption(size) -> #(c, n, SizeOption(size), None) + use #(c, n, option, option_type) <- result.map(case option { + g.BigOption -> Ok(#(c, n, BigOption, None)) + g.LittleOption -> Ok(#(c, n, LittleOption, None)) + g.NativeOption -> Ok(#(c, n, NativeOption, None)) + g.SignedOption -> Ok(#(c, n, SignedOption, None)) + g.UnsignedOption -> Ok(#(c, n, UnsignedOption, None)) + g.BytesOption -> Ok(#(c, n, BytesOption, Some(bit_array_type))) + g.BitsOption -> Ok(#(c, n, BitsOption, Some(bit_array_type))) + g.IntOption -> Ok(#(c, n, IntOption, Some(int_type))) + g.FloatOption -> Ok(#(c, n, FloatOption, Some(float_type))) + g.Utf8Option -> Ok(#(c, n, Utf8Option, Some(string_type))) + g.Utf16Option -> Ok(#(c, n, Utf16Option, Some(string_type))) + g.Utf32Option -> Ok(#(c, n, Utf32Option, Some(string_type))) + g.Utf8CodepointOption -> + Ok(#(c, n, Utf8CodepointOption, Some(codepoint_type))) + g.Utf16CodepointOption -> + Ok(#(c, n, Utf16CodepointOption, Some(codepoint_type))) + g.Utf32CodepointOption -> + Ok(#(c, n, Utf32CodepointOption, Some(codepoint_type))) + g.SizeOption(size) -> Ok(#(c, n, SizeOption(size), None)) g.SizeValueOption(pattern) -> { - let #(c, n, p) = infer_pattern(c, n, pattern) + use #(c, n, p) <- result.map(infer_pattern(c, n, pattern)) let c = unify(c, p.typ, int_type) #(c, n, SizeValueOption(p), None) } - g.UnitOption(unit) -> #(c, n, UnitOption(unit), None) - } + g.UnitOption(unit) -> Ok(#(c, n, UnitOption(unit), None)) + }) let typ = case typ, option_type { Some(_), Some(_) -> panic as error(c, "segment type was set multiple times") @@ -1333,7 +1399,8 @@ fn infer_pattern( _, _ -> option_type } #(c, n, [option, ..options], typ) - }) + }), + ) let options = list.reverse(options) // If no type option was specified, default to int_type @@ -1342,10 +1409,11 @@ fn infer_pattern( None -> int_type } - let #(c, n, pattern) = infer_pattern(c, n, pattern) + use #(c, n, pattern) <- result.map(infer_pattern(c, n, pattern)) let c = unify(c, pattern.typ, expected_type) #(c, n, [#(pattern, options), ..segs]) - }) + }), + ) let segs = list.reverse(segs) // The overall pattern type should be bit_array_type @@ -1359,12 +1427,13 @@ fn infer_pattern( } // resolve the constructor function - let assert Ok(FunctionGlobal(module, constructor, poly, labels)) = - resolve_constructor_name(c, module, constructor) + use #(module, constructor, poly, labels) <- result.try( + resolve_constructor(c, module, constructor), + ) // infer the type of all arguments - let #(c, n, arguments) = - list.fold(arguments, #(c, n, []), fn(acc, arg) { + use #(c, n, arguments) <- result.try( + list.try_fold(arguments, #(c, n, []), fn(acc, arg) { let #(c, n, arguments) = acc let #(item, label) = case arg { @@ -1376,13 +1445,14 @@ fn infer_pattern( g.UnlabelledField(item) -> #(item, None) } - let #(c, n, arg2) = infer_pattern(c, n, item) + use #(c, n, arg2) <- result.map(infer_pattern(c, n, item)) #(c, n, [Field(label, arg2), ..arguments]) - }) + }), + ) let arguments = list.reverse(arguments) // handle labels - let #(c, ordered_arguments) = case with_spread { + use #(c, ordered_arguments) <- result.map(case with_spread { True -> { let #(c, args) = match_labels_optional(arguments, labels) @@ -1397,13 +1467,13 @@ fn infer_pattern( } #(c, [opt, ..opts]) }) - #(c, list.reverse(args)) + Ok(#(c, list.reverse(args))) } False -> { - let args = match_labels(c, arguments, labels) + use args <- result.map(match_labels(c, arguments, labels)) #(c, args) } - } + }) let arg_types = list.map(ordered_arguments, fn(x) { x.item.typ }) // handle 0 parameter variants are not functions @@ -1434,7 +1504,19 @@ fn infer_pattern( } } -fn infer_annotation(c: Context, typ: g.Type) -> #(Context, Annotation) { +fn resolve_constructor(c: Context, module: Option(String), constructor: String) { + use constructor <- result.try(resolve_constructor_name(c, module, constructor)) + case constructor { + FunctionGlobal(module:, name:, typ:, labels:) -> + Ok(#(module, name, typ, labels)) + ConstantGlobal(..) -> Error(NotAFunction(constructor)) + } +} + +fn infer_annotation( + c: Context, + typ: g.Type, +) -> Result(#(Context, Annotation), Error) { let vars = find_vars_in_type(typ) |> list.unique() @@ -1455,36 +1537,36 @@ fn infer_body( c: Context, n: LocalEnv, body: List(g.Statement), -) -> #(Context, List(Statement)) { +) -> Result(#(Context, List(Statement)), Error) { case body { - [] -> #(c, []) + [] -> Ok(#(c, [])) [x, ..xs] -> case x { g.Expression(value) -> { - let assert Ok(#(c, value)) = infer_expression(c, n, value) + use #(c, value) <- result.try(infer_expression(c, n, value)) let statement = Expression(value.typ, value) // infer the rest of the body - let #(c, rest) = infer_body(c, n, xs) + use #(c, rest) <- result.map(infer_body(c, n, xs)) #(c, [statement, ..rest]) } g.Assignment(_, kind, pattern, annotation, value) -> { // infer value before binding the new variable - let assert Ok(#(c, value)) = infer_expression(c, n, value) + use #(c, value) <- result.try(infer_expression(c, n, value)) // infer pattern, annotation, and value - let #(c, n, pattern) = infer_pattern(c, n, pattern) + use #(c, n, pattern) <- result.try(infer_pattern(c, n, pattern)) // if there is an annotation, the pattern must unify with the annotation - let #(c, annotation) = case annotation { + use #(c, annotation) <- result.try(case annotation { Some(typ) -> { - let #(c, annotation) = infer_annotation(c, typ) + use #(c, annotation) <- result.map(infer_annotation(c, typ)) let c = unify(c, pattern.typ, annotation.typ) #(c, Some(annotation)) } - None -> #(c, None) - } + None -> Ok(#(c, None)) + }) // the pattern must unify with both the annotation // and the assigned value @@ -1506,62 +1588,57 @@ fn infer_body( ) // infer the rest of the body - let #(c, rest) = infer_body(c, n, xs) + use #(c, rest) <- result.map(infer_body(c, n, xs)) #(c, [statement, ..rest]) } g.Assert(_, expression, message) -> { - let assert Ok(#(c, expression)) = infer_expression(c, n, expression) + use #(c, expression) <- result.try(infer_expression(c, n, expression)) - let #(c, message) = case message { + use #(c, message) <- result.try(case message { Some(msg) -> { - let assert Ok(#(c, msg)) = infer_expression(c, n, msg) + use #(c, msg) <- result.map(infer_expression(c, n, msg)) #(c, Some(msg)) } - None -> #(c, None) - } + None -> Ok(#(c, None)) + }) let statement = Assert(expression.typ, expression, message) // infer the rest of the body - let #(c, rest) = infer_body(c, n, xs) + use #(c, rest) <- result.map(infer_body(c, n, xs)) #(c, [statement, ..rest]) } g.Use(span, patterns, function) -> { // TODO infer without desugaring - let fun = case function { - g.Call(..) -> function - _ -> g.Call(span, function, []) + let #(span, fun, args) = case function { + g.Call(span, fun, args) -> #(span, fun, args) + _ -> #(span, function, []) } - case fun { - g.Call(span, fun, args) -> { - let params = - list.index_map(patterns, fn(_pat, i) { - g.FnParameter(g.Named("P" <> int.to_string(i)), None) - }) - let body = - list.index_fold(patterns, xs, fn(body, pat, i) { - let param = g.Variable(span, "P" <> int.to_string(i)) - let assignment = - g.Assignment(span, g.Let, pat.pattern, None, param) - [assignment, ..body] - }) - let callback = g.Fn(span, params, None, body) - let assert Ok(#(_, ifun)) = infer_expression(c, n, fun) - let field = case ifun { - Function(labels:, ..) -> - case list.last(labels) { - Ok(Some(label)) -> g.LabelledField(label, callback) - _ -> g.UnlabelledField(callback) - } + let params = + list.index_map(patterns, fn(_pat, i) { + g.FnParameter(g.Named("P" <> int.to_string(i)), None) + }) + let body = + list.index_fold(patterns, xs, fn(body, pat, i) { + let param = g.Variable(span, "P" <> int.to_string(i)) + let assignment = + g.Assignment(span, g.Let, pat.pattern, None, param) + [assignment, ..body] + }) + let callback = g.Fn(span, params, None, body) + use #(_, ifun) <- result.try(infer_expression(c, n, fun)) + let field = case ifun { + Function(labels:, ..) -> + case list.last(labels) { + Ok(Some(label)) -> g.LabelledField(label, callback) _ -> g.UnlabelledField(callback) } - let call = g.Call(span, fun, list.append(args, [field])) - let assert Ok(#(c, exp)) = infer_expression(c, n, call) - let statement = Expression(exp.typ, exp) - #(c, [statement]) - } - _ -> panic as error(c, "expected a function call") + _ -> g.UnlabelledField(callback) } + let call = g.Call(span, fun, list.append(args, [field])) + use #(c, exp) <- result.map(infer_expression(c, n, call)) + let statement = Expression(exp.typ, exp) + #(c, [statement]) } } } @@ -1615,28 +1692,32 @@ fn match_labels( c: Context, args: List(Field(a)), params: List(Option(String)), -) -> List(Field(a)) { +) -> Result(List(Field(a)), Error) { // find the labels in the order specified by parameters // either we find the matching label or default to the first unlabelled arg + // TODO: keep track of original number of args/params for WrongArity error case params { [] -> case args { - [] -> [] + [] -> Ok([]) + // TODO: return WrongArity _ -> panic as error(c, "too many arguments given") } [p, ..p_rest] -> - case listx.pop(args, fn(a) { a.label == p }) { - Ok(#(a, a_rest)) -> [a, ..match_labels(c, a_rest, p_rest)] - Error(_) -> - case listx.pop(args, fn(a) { a.label == None }) { - Ok(#(a, a_rest)) -> [a, ..match_labels(c, a_rest, p_rest)] - Error(_) -> - case p { - Some(l) -> panic as error(c, "no matching label '" <> l <> "'") - None -> panic as error(c, "not enough arguments given") - } - } - } + listx.pop(args, fn(a) { a.label == p }) + |> result.try_recover(fn(_) { listx.pop(args, fn(a) { a.label == None }) }) + |> result.map_error(fn(_) { + case p { + Some(l) -> LabelNotFound(l) + // TODO: return WrongArity + None -> panic as error(c, "not enough arguments given") + } + }) + |> result.try(fn(r) { + let #(a, a_rest) = r + use rest <- result.map(match_labels(c, a_rest, p_rest)) + [a, ..rest] + }) } } @@ -1659,7 +1740,7 @@ fn infer_expression( c: Context, n: LocalEnv, exp: g.Expression, -) -> Result(#(Context, Expression), String) { +) -> Result(#(Context, Expression), Error) { let c = Context(..c, current_span: exp.location) case exp { g.Int(_, s) -> Ok(#(c, Int(int_type, s))) @@ -1696,11 +1777,11 @@ fn infer_expression( Ok(#(c, NegateBool(bool_type, e))) } g.Block(_, statements) -> { - let #(c, statements) = infer_body(c, n, statements) + use #(c, statements) <- result.try(infer_body(c, n, statements)) case list.last(statements) { Ok(last) -> Ok(#(c, Block(last.typ, statements))) - Error(_) -> Error("empty block") + Error(_) -> Error(EmptyBlock) } } g.Panic(_, e) -> { @@ -1792,11 +1873,13 @@ fn infer_expression( use #(c, base_expr) <- try(infer_expression(c, n, record)) // Resolve the constructor type - let assert Ok(FunctionGlobal(res_module, constructor, poly, labels)) = - resolve_constructor_name(c, module, constructor) + use #(res_module, constructor, poly, labels) <- result.try( + resolve_constructor(c, module, constructor), + ) // Instantiate the constructor type let #(c, constructor_type) = instantiate(c, poly) + // TODO: wrap in a result, or is this guaranteed to succeed? let assert FunctionType(constructor_args, constructor_ret) = constructor_type @@ -1819,8 +1902,13 @@ fn infer_expression( let fields = list.map(updated_fields, fn(x) { Field(Some(x.0), x.1) }) let ordered_fields = match_labels_optional(fields, labels) - let assert Ok(ordered_fields) = + use ordered_fields <- result.try( list.strict_zip(ordered_fields, constructor_args) + |> result.map_error(fn(_) { + WrongArity(list.length(constructor_args), list.length(ordered_fields)) + }), + ) + let #(c, ordered_fields) = ordered_fields |> list.fold_right(#(c, []), fn(acc, x) { @@ -1861,20 +1949,18 @@ fn infer_expression( // field access must be on a named type let value_typ = case resolve_type(c, value.typ) { NamedType(type_name, module, _) -> Ok(#(type_name, module)) - _ -> Error("Field access attempted on invalid type. ") + _ -> Error(InvalidFieldAccess) } use #(type_name, module) <- try(value_typ) // find the custom type definition - let assert Ok(mod) = dict.get(c.modules, module) - let assert Ok(custom) = - list.find(mod.custom_types, fn(x) { x.definition.name == type_name }) + use custom <- result.try(resolve_custom_type(c, module, type_name)) // access only works with one variant let variant = case custom.definition.variants { // TODO proper implementation checking all variants [variant, ..] -> Ok(variant) - _ -> Error("Field access attempted on type with multiple variants.") + _ -> Error(InvalidFieldAccess) } use variant <- try(variant) @@ -1883,7 +1969,7 @@ fn infer_expression( variant.fields |> list.index_map(fn(x, i) { #(x, i) }) |> list.find(fn(x) { { x.0 }.label == Some(label) }) - |> result.replace_error("Variant does not have a matching field.") + |> result.replace_error(FieldNotFound) use #(field, index) <- try(field) // create a getter function type @@ -1903,7 +1989,7 @@ fn infer_expression( // try a module access instead case container { g.Variable(_, module) -> { - case resolve_aliased_name(c, QualifiedName(module, label)) { + case resolve_aliased_global(c, QualifiedName(module, label)) { Ok(FunctionGlobal(module, name, poly, labels)) -> { let #(c, typ) = instantiate(c, poly) Ok(#(c, Function(typ, module, name, labels))) @@ -1940,17 +2026,22 @@ fn infer_expression( Field(label, arg) }) - let args = match_labels(c, args, labels) + use args <- result.try(match_labels(c, args, labels)) // use fun parameter type as type hints for inferring arguments - let args = case resolve_type(c, fun.typ) { + use args <- result.try(case resolve_type(c, fun.typ) { FunctionType(params, _ret) -> { let params = list.map(params, Some) - let assert Ok(args) = list.strict_zip(params, args) + use args <- result.map( + list.strict_zip(params, args) + |> result.map_error(fn(_) { + WrongArity(list.length(params), list.length(args)) + }), + ) args } - _ -> list.map(args, fn(arg) { #(None, arg) }) - } + _ -> Ok(list.map(args, fn(arg) { #(None, arg) })) + }) // infer the type of all args use #(c, args) <- try( list.try_fold(args, #(c, []), fn(acc, type_hint_and_field) { @@ -1987,10 +2078,10 @@ fn infer_expression( use #(c, tuple) <- try(infer_expression(c, n, tuple)) case resolve_type(c, tuple.typ) { TupleType(elements) -> { - let typ = tuple_index_type(c, elements, index) - Ok(#(c, TupleIndex(typ, tuple, index))) + tuple_index_type(c, elements, index) + |> result.map(fn(typ) { #(c, TupleIndex(typ, tuple, index)) }) } - _ -> Error("tuple index on non-tuple") + _ -> Error(InvalidTupleAccess) } } g.FnCapture(span, label, function, arguments_before, arguments_after) -> { @@ -2011,48 +2102,38 @@ fn infer_expression( list.try_fold(segments, #(c, []), fn(acc, seg) { let #(c, segs) = acc let #(expression, options) = seg - let #(c, options, typ) = - list.fold(options, #(c, [], None), fn(acc, option) { + use #(c, options, typ) <- result.try( + list.try_fold(options, #(c, [], None), fn(acc, option) { let #(c, options, typ) = acc // TODO handle all options - let #(c, option, option_type) = case option { - g.BigOption -> #(c, BigOption, None) - g.BytesOption -> #(c, BytesOption, Some(bit_array_type)) - g.BitsOption -> #(c, BitsOption, Some(bit_array_type)) - g.FloatOption -> #(c, FloatOption, Some(float_type)) - g.IntOption -> #(c, IntOption, Some(int_type)) - g.LittleOption -> #(c, LittleOption, None) - g.NativeOption -> #(c, NativeOption, None) - g.SignedOption -> #(c, SignedOption, None) - g.SizeOption(size) -> #(c, SizeOption(size), None) + use #(c, option, option_type) <- result.map(case option { + g.BigOption -> Ok(#(c, BigOption, None)) + g.BytesOption -> Ok(#(c, BytesOption, Some(bit_array_type))) + g.BitsOption -> Ok(#(c, BitsOption, Some(bit_array_type))) + g.FloatOption -> Ok(#(c, FloatOption, Some(float_type))) + g.IntOption -> Ok(#(c, IntOption, Some(int_type))) + g.LittleOption -> Ok(#(c, LittleOption, None)) + g.NativeOption -> Ok(#(c, NativeOption, None)) + g.SignedOption -> Ok(#(c, SignedOption, None)) + g.SizeOption(size) -> Ok(#(c, SizeOption(size), None)) g.SizeValueOption(e) -> { - let assert Ok(#(c, e)) = infer_expression(c, n, e) + use #(c, e) <- result.map(infer_expression(c, n, e)) let c = unify(c, e.typ, int_type) #(c, SizeValueOption(e), None) } - g.UnitOption(unit) -> #(c, UnitOption(unit), None) - g.UnsignedOption -> #(c, UnsignedOption, None) - g.Utf16CodepointOption -> #( - c, - Utf16CodepointOption, - Some(codepoint_type), - ) - g.Utf16Option -> #(c, Utf16Option, Some(string_type)) - g.Utf32CodepointOption -> #( - c, - Utf32CodepointOption, - Some(codepoint_type), - ) - g.Utf32Option -> #(c, Utf32Option, Some(string_type)) - g.Utf8CodepointOption -> #( - c, - Utf8CodepointOption, - Some(codepoint_type), - ) - g.Utf8Option -> { - #(c, Utf8Option, Some(string_type)) - } - } + g.UnitOption(unit) -> Ok(#(c, UnitOption(unit), None)) + g.UnsignedOption -> Ok(#(c, UnsignedOption, None)) + g.Utf16CodepointOption -> + Ok(#(c, Utf16CodepointOption, Some(codepoint_type))) + g.Utf16Option -> Ok(#(c, Utf16Option, Some(string_type))) + g.Utf32CodepointOption -> + Ok(#(c, Utf32CodepointOption, Some(codepoint_type))) + g.Utf32Option -> Ok(#(c, Utf32Option, Some(string_type))) + g.Utf8CodepointOption -> + Ok(#(c, Utf8CodepointOption, Some(codepoint_type))) + g.Utf8Option -> Ok({ #(c, Utf8Option, Some(string_type)) }) + }) + // TODO: wrap in a result? let typ = case typ, option_type { Some(_), Some(_) -> panic as error(c, "segment type was set multiple times") @@ -2060,7 +2141,8 @@ fn infer_expression( _, _ -> option_type } #(c, [option, ..options], typ) - }) + }), + ) let options = list.reverse(options) let typ = case typ { Some(typ) -> typ @@ -2094,21 +2176,28 @@ fn infer_expression( // patterns is a List(List(Pattern)) // the inner list has a pattern to match each subject // the outer list has alternatives that have the same body - let #(c, n, patterns) = - list.fold_right(clause.patterns, #(c, n, []), fn(acc, pat) { + use #(c, n, patterns) <- result.try( + list.try_fold(clause.patterns, #(c, n, []), fn(acc, pat) { let #(c, n, pats) = acc // each pattern has a corresponding subject - let assert Ok(sub_pats) = list.strict_zip(subjects, pat) - let #(c, n, pat) = - list.fold_right(sub_pats, #(c, n, []), fn(acc, sub_pat) { + use sub_pats <- result.try( + list.strict_zip(subjects, pat) + |> result.map_error(fn(_) { + WrongArity(list.length(subjects), list.length(pat)) + }), + ) + use #(c, n, pat) <- result.map( + list.try_fold(sub_pats, #(c, n, []), fn(acc, sub_pat) { let #(c, n, pats) = acc let #(sub, pat) = sub_pat - let #(c, n, pat) = infer_pattern(c, n, pat) + use #(c, n, pat) <- result.map(infer_pattern(c, n, pat)) // the pattern type should match the corresponding subject let c = unify(c, pat.typ, sub.typ) #(c, n, [pat, ..pats]) - }) + }), + ) + let pat = list.reverse(pat) // all alternatives must bind the same names // TODO check the alternative patterns bind the same names @@ -2117,7 +2206,9 @@ fn infer_expression( // instead of a new env #(c, n, [pat, ..pats]) - }) + }), + ) + let patterns = list.reverse(patterns) // if the guard exists ensure it has a boolean result use #(c, guard) <- try(case clause.guard { @@ -2233,13 +2324,28 @@ fn infer_expression( } } -fn tuple_index_type(c: Context, elements: List(Type), index: Int) { - // if the index is 0, return the first element - // otherwise skip the first element and decrement the index - case index, elements { - 0, [x, ..] -> x - _, [_, ..xs] -> tuple_index_type(c, xs, index - 1) - _, _ -> panic as error(c, "tuple index out of bounds") +fn resolve_custom_type(c: Context, module: String, type_name: String) { + use mod <- result.try(get_module(c, module)) + list.find(mod.custom_types, fn(x) { x.definition.name == type_name }) + |> result.replace_error(UnresolvedType(type_name)) +} + +fn tuple_index_type( + _c: Context, + elements: List(Type), + index: Int, +) -> Result(Type, Error) { + index_into_list(elements, index) + |> result.map_error(fn(_) { + TupleIndexOutOfBounds(list.length(elements), index) + }) +} + +fn index_into_list(list: List(a), index: Int) -> Result(a, Nil) { + case index, list { + 0, [item, ..] -> Ok(item) + _, [_, ..rest] -> index_into_list(rest, index - 1) + _, _ -> Error(Nil) } } @@ -2250,13 +2356,16 @@ fn infer_fn( return: Option(g.Type), body: List(g.Statement), hint: Option(Type), -) -> Result(#(Context, Expression), String) { +) -> Result(#(Context, Expression), Error) { // map parameters to FunctionParameter for code reuse let parameters = list.map(parameters, fn(p) { g.FunctionParameter(None, p.name, p.type_) }) - let #(c, parameters, return) = - infer_function_parameters(c, parameters, return) + use #(c, parameters, return) <- result.try(infer_function_parameters( + c, + parameters, + return, + )) let #(c, return_type) = case return { Some(x) -> #(c, x.typ) @@ -2283,7 +2392,7 @@ fn infer_fn( }) // infer body - let #(c, body) = infer_body(c, n, body) + use #(c, body) <- result.map(infer_body(c, n, body)) // unify the return type with the last statement let c = case list.last(body) { @@ -2292,13 +2401,14 @@ fn infer_fn( } let fun = Fn(typ:, parameters:, return:, body:) - Ok(#(c, fun)) + #(c, fun) } type PolyEnv = Dict(Int, Type) fn get_type_var(c: Context, var: Ref) { + // TODO: guaranteed to succeed? let assert Ok(x) = dict.get(c.type_vars, var) x } @@ -2363,6 +2473,7 @@ fn unify(c: Context, a: Type, b: Type) -> Context { case a == b { True -> c False -> { + // TODO: guaranteed to succeed? let assert Unbound(aid) = get_type_var(c, ref) let #(c, occurs) = occurs(c, aid, b) case occurs { @@ -2380,12 +2491,12 @@ fn unify(c: Context, a: Type, b: Type) -> Context { if aname != bname || amodule != bmodule -> { panic as error( - c, - "failed to unify types \n\n" - <> string.inspect(a) - <> "\n" - <> string.inspect(b), - ) + c, + "failed to unify types \n\n" + <> string.inspect(a) + <> "\n" + <> string.inspect(b), + ) } NamedType(_, _, aargs), NamedType(_, _, bargs) -> { case list.strict_zip(aargs, bargs) { @@ -2414,12 +2525,12 @@ fn unify(c: Context, a: Type, b: Type) -> Context { } _, _ -> { panic as error( - c, - "failed to unify types \n\n" - <> string.inspect(a) - <> "\n" - <> string.inspect(b), - ) + c, + "failed to unify types \n\n" + <> string.inspect(a) + <> "\n" + <> string.inspect(b), + ) } } } @@ -2457,9 +2568,10 @@ fn occurs(c: Context, id: Int, in: Type) -> #(Context, Bool) { } /// follow any references to get the real type -pub fn resolve_type(c: Context, typ: Type) { +pub fn resolve_type(c: Context, typ: Type) -> Type { case typ { VariableType(x) -> { + // TODO: guaranteed to succeed? let assert Ok(x) = dict.get(c.type_vars, x) case x { Bound(x) -> resolve_type(c, x) @@ -2475,6 +2587,7 @@ pub fn resolve_type(c: Context, typ: Type) { pub fn resolve_type_deep(c: Context, typ: Type) { case typ { VariableType(x) -> { + // TODO: guaranteed to succeed? let assert Ok(x) = dict.get(c.type_vars, x) case x { Bound(x) -> resolve_type_deep(c, x) From f5eedb7f66cd94529fba94678f8f33b963864b50 Mon Sep 17 00:00:00 2001 From: Gert van Valkenhoef Date: Wed, 25 Feb 2026 09:08:01 +0100 Subject: [PATCH 2/8] schurhammer/gig#6: refactor - propagate errors in typed_ast --- src/gig/typed_ast.gleam | 298 +++++++++++++++++++--------------------- 1 file changed, 142 insertions(+), 156 deletions(-) diff --git a/src/gig/typed_ast.gleam b/src/gig/typed_ast.gleam index 37fe59b..c26e3f1 100644 --- a/src/gig/typed_ast.gleam +++ b/src/gig/typed_ast.gleam @@ -8,7 +8,7 @@ import gleam/dict.{type Dict} import gleam/int import gleam/list import gleam/option.{type Option, None, Some} -import gleam/result.{try} +import gleam/result import gleam/string pub const builtin = "gleam" @@ -338,6 +338,8 @@ pub type Error { WrongArity(expected_arg_count: Int, actual_arg_count: Int) LabelNotFound(String) TupleIndexOutOfBounds(tuple_size: Int, index: Int) + IncompatibleTypes(Type, Type) + RecursiveTypeError } pub type QualifiedName { @@ -491,12 +493,12 @@ pub fn infer_module( use #(c, alias) <- result.try(infer_alias_type(c, def.definition)) // update the placeholder type - use #(_, placeholder, _) <- result.map(resolve_global_type_name( + use #(_, placeholder, _) <- result.try(resolve_global_type_name( c, c.current_module, alias.name, )) - let c = unify(c, alias.aliased.typ, placeholder.typ) + use c <- result.map(unify(c, alias.aliased.typ, placeholder.typ)) #(c, [#(def, alias), ..aliases]) }), @@ -641,12 +643,12 @@ pub fn infer_module( let fun = def.definition // unify placeholder type - use placeholder <- result.map(resolve_global_name( + use placeholder <- result.try(resolve_global_name( c, c.current_module, fun.name, )) - let c = unify(c, placeholder.typ.typ, fun.typ.typ) + use c <- result.map(unify(c, placeholder.typ.typ, fun.typ.typ)) // generalise let typ = generalise(c, fun.typ.typ) @@ -810,17 +812,17 @@ fn infer_function( }) // infer body - use #(c, body) <- result.map(infer_body(c, n, fun.body)) + use #(c, body) <- result.try(infer_body(c, n, fun.body)) // compute function type let parameter_types = list.map(parameters, fn(x) { x.typ }) let typ = FunctionType(parameter_types, return_type) // unify the return type with the last statement - let c = case list.last(body) { + use c <- result.map(case list.last(body) { Ok(statement) -> unify(c, return_type, statement.typ) - Error(_) -> c - } + Error(_) -> Ok(c) + }) let name = fun.name @@ -1300,8 +1302,9 @@ fn infer_pattern( let #(c, elem_type) = new_type_var_ref(c) // Unify all element types with the element type variable - let c = - list.fold(elements, c, fn(c, elem) { unify(c, elem.typ, elem_type) }) + use c <- result.try( + list.try_fold(elements, c, fn(c, elem) { unify(c, elem.typ, elem_type) }), + ) // Create the list type let typ = NamedType("List", builtin, [elem_type]) @@ -1309,9 +1312,9 @@ fn infer_pattern( // Handle the tail pattern if present use #(c, n, tail) <- result.map(case tail { Some(tail_pattern) -> { - use #(c, n, tail) <- result.map(infer_pattern(c, n, tail_pattern)) + use #(c, n, tail) <- result.try(infer_pattern(c, n, tail_pattern)) // The tail should be a list of the same type - let c = unify(c, tail.typ, typ) + use c <- result.map(unify(c, tail.typ, typ)) #(c, n, Some(tail)) } None -> Ok(#(c, n, None)) @@ -1386,8 +1389,8 @@ fn infer_pattern( Ok(#(c, n, Utf32CodepointOption, Some(codepoint_type))) g.SizeOption(size) -> Ok(#(c, n, SizeOption(size), None)) g.SizeValueOption(pattern) -> { - use #(c, n, p) <- result.map(infer_pattern(c, n, pattern)) - let c = unify(c, p.typ, int_type) + use #(c, n, p) <- result.try(infer_pattern(c, n, pattern)) + use c <- result.map(unify(c, p.typ, int_type)) #(c, n, SizeValueOption(p), None) } g.UnitOption(unit) -> Ok(#(c, n, UnitOption(unit), None)) @@ -1409,8 +1412,8 @@ fn infer_pattern( None -> int_type } - use #(c, n, pattern) <- result.map(infer_pattern(c, n, pattern)) - let c = unify(c, pattern.typ, expected_type) + use #(c, n, pattern) <- result.try(infer_pattern(c, n, pattern)) + use c <- result.map(unify(c, pattern.typ, expected_type)) #(c, n, [#(pattern, options), ..segs]) }), ) @@ -1452,7 +1455,7 @@ fn infer_pattern( let arguments = list.reverse(arguments) // handle labels - use #(c, ordered_arguments) <- result.map(case with_spread { + use #(c, ordered_arguments) <- result.try(case with_spread { True -> { let #(c, args) = match_labels_optional(arguments, labels) @@ -1477,16 +1480,16 @@ fn infer_pattern( let arg_types = list.map(ordered_arguments, fn(x) { x.item.typ }) // handle 0 parameter variants are not functions - let #(c, typ) = case arg_types { - [] -> instantiate(c, poly) + use #(c, typ) <- result.map(case arg_types { + [] -> Ok(instantiate(c, poly)) _ -> { // unify the constructor function type with the types of args let #(c, fun_typ) = instantiate(c, poly) let #(c, typ) = new_type_var_ref(c) - let c = unify(c, fun_typ, FunctionType(arg_types, typ)) + use c <- result.map(unify(c, fun_typ, FunctionType(arg_types, typ))) #(c, typ) } - } + }) let pattern = PatternConstructor( @@ -1561,8 +1564,8 @@ fn infer_body( // if there is an annotation, the pattern must unify with the annotation use #(c, annotation) <- result.try(case annotation { Some(typ) -> { - use #(c, annotation) <- result.map(infer_annotation(c, typ)) - let c = unify(c, pattern.typ, annotation.typ) + use #(c, annotation) <- result.try(infer_annotation(c, typ)) + use c <- result.map(unify(c, pattern.typ, annotation.typ)) #(c, Some(annotation)) } None -> Ok(#(c, None)) @@ -1570,7 +1573,7 @@ fn infer_body( // the pattern must unify with both the annotation // and the assigned value - let c = unify(c, pattern.typ, value.typ) + use c <- result.try(unify(c, pattern.typ, value.typ)) // TODO check the right "kind" was used (needs exhaustive checking) let kind = case kind { @@ -1767,18 +1770,17 @@ fn infer_expression( } } g.NegateInt(_, value) -> { - use #(c, e) <- try(infer_expression(c, n, value)) - let c = unify(c, e.typ, int_type) - Ok(#(c, NegateInt(int_type, e))) + use #(c, e) <- result.try(infer_expression(c, n, value)) + use c <- result.map(unify(c, e.typ, int_type)) + #(c, NegateInt(int_type, e)) } g.NegateBool(_, value) -> { - use #(c, e) <- try(infer_expression(c, n, value)) - let c = unify(c, e.typ, bool_type) - Ok(#(c, NegateBool(bool_type, e))) + use #(c, e) <- result.try(infer_expression(c, n, value)) + use c <- result.map(unify(c, e.typ, bool_type)) + #(c, NegateBool(bool_type, e)) } g.Block(_, statements) -> { use #(c, statements) <- result.try(infer_body(c, n, statements)) - case list.last(statements) { Ok(last) -> Ok(#(c, Block(last.typ, statements))) Error(_) -> Error(EmptyBlock) @@ -1788,10 +1790,10 @@ fn infer_expression( case e { Some(e) -> { // the expression should be a string - use #(c, e) <- try(infer_expression(c, n, e)) - unify(c, e.typ, string_type) + use #(c, e) <- result.try(infer_expression(c, n, e)) + use c <- result.map(unify(c, e.typ, string_type)) let #(c, typ) = new_type_var_ref(c) - Ok(#(c, Panic(typ, Some(e)))) + #(c, Panic(typ, Some(e))) } None -> { let #(c, typ) = new_type_var_ref(c) @@ -1803,10 +1805,10 @@ fn infer_expression( case e { Some(e) -> { // the expression should be a string - use #(c, e) <- try(infer_expression(c, n, e)) - unify(c, e.typ, string_type) + use #(c, e) <- result.try(infer_expression(c, n, e)) + use c <- result.map(unify(c, e.typ, string_type)) let #(c, typ) = new_type_var_ref(c) - Ok(#(c, Todo(typ, Some(e)))) + #(c, Todo(typ, Some(e))) } None -> { let #(c, typ) = new_type_var_ref(c) @@ -1816,10 +1818,10 @@ fn infer_expression( } g.Tuple(_, elements) -> { // Infer type of all elements - use #(c, elements) <- try( + use #(c, elements) <- result.try( list.try_fold(elements, #(c, []), fn(acc, e) { let #(c, elements) = acc - use #(c, e) <- try(infer_expression(c, n, e)) + use #(c, e) <- result.try(infer_expression(c, n, e)) Ok(#(c, [e, ..elements])) }), ) @@ -1832,19 +1834,19 @@ fn infer_expression( } g.List(_, elements, rest) -> { // Infer types for all elements - use #(c, elements) <- try( + use #(c, elements) <- result.try( list.try_fold(elements, #(c, []), fn(acc, e) { let #(c, elements) = acc - use #(c, e) <- try(infer_expression(c, n, e)) + use #(c, e) <- result.try(infer_expression(c, n, e)) Ok(#(c, [e, ..elements])) }), ) let elements = list.reverse(elements) // Infer type for rest (if present) - use #(c, rest) <- try(case rest { + use #(c, rest) <- result.try(case rest { Some(t) -> { - use #(c, t) <- try(infer_expression(c, n, t)) + use #(c, t) <- result.try(infer_expression(c, n, t)) Ok(#(c, Some(t))) } None -> Ok(#(c, None)) @@ -1855,22 +1857,24 @@ fn infer_expression( let typ = NamedType("List", builtin, [elem_type]) // Unify all element types - let c = list.fold(elements, c, fn(c, e) { unify(c, e.typ, elem_type) }) + use c <- result.try( + list.try_fold(elements, c, fn(c, e) { unify(c, e.typ, elem_type) }), + ) // Unify rest type with list type (if rest is present) - let c = case rest { + use c <- result.map(case rest { Some(t) -> unify(c, t.typ, typ) - None -> c - } + None -> Ok(c) + }) - Ok(#(c, List(typ, elements, rest))) + #(c, List(typ, elements, rest)) } g.Fn(_, parameters, return_annotation, body) -> { infer_fn(c, n, parameters, return_annotation, body, None) } g.RecordUpdate(span, module, constructor, record, fields) -> { // Infer the type of the base record expression - use #(c, base_expr) <- try(infer_expression(c, n, record)) + use #(c, base_expr) <- result.try(infer_expression(c, n, record)) // Resolve the constructor type use #(res_module, constructor, poly, labels) <- result.try( @@ -1884,18 +1888,18 @@ fn infer_expression( constructor_type // Unify the base expression type with the constructor type - let c = unify(c, base_expr.typ, constructor_ret) + use c <- result.try(unify(c, base_expr.typ, constructor_ret)) // Infer types for all updated fields - use #(c, updated_fields) <- try( + use #(c, updated_fields) <- result.try( list.try_fold(fields, #(c, []), fn(acc, field) { let #(c, updated_fields) = acc let item = case field.item { Some(item) -> item None -> g.Variable(span, field.label) } - use #(c, value) <- try(infer_expression(c, n, item)) - Ok(#(c, [#(field.label, value), ..updated_fields])) + use #(c, value) <- result.map(infer_expression(c, n, item)) + #(c, [#(field.label, value), ..updated_fields]) }), ) let updated_fields = list.reverse(updated_fields) @@ -1909,20 +1913,20 @@ fn infer_expression( }), ) - let #(c, ordered_fields) = - ordered_fields - |> list.fold_right(#(c, []), fn(acc, x) { + use #(c, ordered_fields) <- result.map( + list.try_fold(ordered_fields, #(c, []), fn(acc, x) { let #(c, fields) = acc let #(given, expected) = x - let #(c, result) = case given { + use #(c, result) <- result.map(case given { Some(e) -> { - let c = unify(c, e.item.typ, expected) + use c <- result.map(unify(c, e.item.typ, expected)) #(c, Ok(e)) } - None -> #(c, Error(expected)) - } + None -> Ok(#(c, Error(expected))) + }) #(c, [result, ..fields]) - }) + }), + ) // The result type is the same as the constructor type let typ = constructor_ret @@ -1939,19 +1943,19 @@ fn infer_expression( ordered_fields: ordered_fields, ) - Ok(#(c, record_update)) + #(c, record_update) } g.FieldAccess(_, container, label) -> { let field_access = { // try to infer the value, otherwise it might be a module access - use #(c, value) <- try(infer_expression(c, n, container)) + use #(c, value) <- result.try(infer_expression(c, n, container)) // field access must be on a named type let value_typ = case resolve_type(c, value.typ) { NamedType(type_name, module, _) -> Ok(#(type_name, module)) _ -> Error(InvalidFieldAccess) } - use #(type_name, module) <- try(value_typ) + use #(type_name, module) <- result.try(value_typ) // find the custom type definition use custom <- result.try(resolve_custom_type(c, module, type_name)) @@ -1962,7 +1966,7 @@ fn infer_expression( [variant, ..] -> Ok(variant) _ -> Error(InvalidFieldAccess) } - use variant <- try(variant) + use variant <- result.try(variant) // find the matching field and index let field = @@ -1970,7 +1974,7 @@ fn infer_expression( |> list.index_map(fn(x, i) { #(x, i) }) |> list.find(fn(x) { { x.0 }.label == Some(label) }) |> result.replace_error(FieldNotFound) - use #(field, index) <- try(field) + use #(field, index) <- result.try(field) // create a getter function type let getter = FunctionType([custom.definition.typ.typ], field.item.typ) @@ -1979,9 +1983,9 @@ fn infer_expression( // unify the getter as if we're calling it on the value let #(c, typ) = new_type_var_ref(c) - let c = unify(c, getter, FunctionType([value.typ], typ)) + use c <- result.map(unify(c, getter, FunctionType([value.typ], typ))) - Ok(#(c, FieldAccess(typ, value, module, variant.name, label, index))) + #(c, FieldAccess(typ, value, module, variant.name, label, index)) } case field_access { Ok(access) -> Ok(access) @@ -2008,7 +2012,7 @@ fn infer_expression( } g.Call(span, function, arguments) -> { // infer the type of the function - use #(c, fun) <- try(infer_expression(c, n, function)) + use #(c, fun) <- result.try(infer_expression(c, n, function)) // handle labels let labels = case fun { @@ -2043,7 +2047,7 @@ fn infer_expression( _ -> Ok(list.map(args, fn(arg) { #(None, arg) })) }) // infer the type of all args - use #(c, args) <- try( + use #(c, args) <- result.try( list.try_fold(args, #(c, []), fn(acc, type_hint_and_field) { let #(c, args) = acc let #(type_hint, field_arg) = type_hint_and_field @@ -2054,16 +2058,16 @@ fn infer_expression( infer_fn(c, n, parameters, return_annotation, body, type_hint) _ -> infer_expression(c, n, field_arg.item) } - use #(c, inferred_arg) <- try(result) + use #(c, inferred_arg) <- result.try(result) - let c = case type_hint { + use c <- result.map(case type_hint { Some(hint) -> unify(c, hint, inferred_arg.typ) - None -> c - } + None -> Ok(c) + }) let field_with_inferred = Field(field_arg.label, inferred_arg) - Ok(#(c, [field_with_inferred, ..args])) + #(c, [field_with_inferred, ..args]) }), ) let args = list.reverse(args) @@ -2071,11 +2075,11 @@ fn infer_expression( let ordered_arguments = list.map(args, fn(field) { field.item }) // unify the function type with the types of args let #(c, typ) = new_type_var_ref(c) - let c = unify(c, fun.typ, FunctionType(arg_types, typ)) - Ok(#(c, Call(typ, fun, ordered_arguments))) + use c <- result.map(unify(c, fun.typ, FunctionType(arg_types, typ))) + #(c, Call(typ, fun, ordered_arguments)) } g.TupleIndex(_, tuple, index) -> { - use #(c, tuple) <- try(infer_expression(c, n, tuple)) + use #(c, tuple) <- result.try(infer_expression(c, n, tuple)) case resolve_type(c, tuple.typ) { TupleType(elements) -> { tuple_index_type(c, elements, index) @@ -2098,7 +2102,7 @@ fn infer_expression( infer_expression(c, n, lambda) } g.BitString(_, segments) -> { - use #(c, segs) <- try( + use #(c, segs) <- result.try( list.try_fold(segments, #(c, []), fn(acc, seg) { let #(c, segs) = acc let #(expression, options) = seg @@ -2117,8 +2121,8 @@ fn infer_expression( g.SignedOption -> Ok(#(c, SignedOption, None)) g.SizeOption(size) -> Ok(#(c, SizeOption(size), None)) g.SizeValueOption(e) -> { - use #(c, e) <- result.map(infer_expression(c, n, e)) - let c = unify(c, e.typ, int_type) + use #(c, e) <- result.try(infer_expression(c, n, e)) + use c <- result.map(unify(c, e.typ, int_type)) #(c, SizeValueOption(e), None) } g.UnitOption(unit) -> Ok(#(c, UnitOption(unit), None)) @@ -2148,19 +2152,19 @@ fn infer_expression( Some(typ) -> typ None -> int_type } - use #(c, expression) <- try(infer_expression(c, n, expression)) - let c = unify(c, expression.typ, typ) - Ok(#(c, [#(expression, options), ..segs])) + use #(c, expression) <- result.try(infer_expression(c, n, expression)) + use c <- result.map(unify(c, expression.typ, typ)) + #(c, [#(expression, options), ..segs]) }), ) let segs = list.reverse(segs) Ok(#(c, BitString(bit_array_type, segs))) } g.Case(_, subjects, clauses) -> { - use #(c, subjects) <- try( + use #(c, subjects) <- result.try( list.try_fold(subjects, #(c, []), fn(acc, sub) { let #(c, subjects) = acc - use #(c, sub) <- try(infer_expression(c, n, sub)) + use #(c, sub) <- result.try(infer_expression(c, n, sub)) Ok(#(c, [sub, ..subjects])) }), ) @@ -2169,7 +2173,7 @@ fn infer_expression( // all of the branches should unify with the case type let #(c, typ) = new_type_var_ref(c) - use #(c, clauses) <- try( + use #(c, clauses) <- result.try( list.try_fold(clauses, #(c, []), fn(acc, clause) { let #(c, clauses) = acc @@ -2191,9 +2195,9 @@ fn infer_expression( list.try_fold(sub_pats, #(c, n, []), fn(acc, sub_pat) { let #(c, n, pats) = acc let #(sub, pat) = sub_pat - use #(c, n, pat) <- result.map(infer_pattern(c, n, pat)) + use #(c, n, pat) <- result.try(infer_pattern(c, n, pat)) // the pattern type should match the corresponding subject - let c = unify(c, pat.typ, sub.typ) + use c <- result.map(unify(c, pat.typ, sub.typ)) #(c, n, [pat, ..pats]) }), ) @@ -2211,22 +2215,22 @@ fn infer_expression( let patterns = list.reverse(patterns) // if the guard exists ensure it has a boolean result - use #(c, guard) <- try(case clause.guard { + use #(c, guard) <- result.try(case clause.guard { Some(guard) -> { - use #(c, guard) <- try(infer_expression(c, n, guard)) - let c = unify(c, guard.typ, bool_type) - Ok(#(c, Some(guard))) + use #(c, guard) <- result.try(infer_expression(c, n, guard)) + use c <- result.map(unify(c, guard.typ, bool_type)) + #(c, Some(guard)) } None -> Ok(#(c, None)) }) - use #(c, body) <- try(infer_expression(c, n, clause.body)) + use #(c, body) <- result.try(infer_expression(c, n, clause.body)) // the body should unify with the case type - let c = unify(c, typ, body.typ) + use c <- result.map(unify(c, typ, body.typ)) let santa = Clause(patterns:, guard:, body:) - Ok(#(c, [santa, ..clauses])) + #(c, [santa, ..clauses]) }), ) let clauses = list.reverse(clauses) @@ -2301,19 +2305,23 @@ fn infer_expression( ) } - use #(c, left) <- try(infer_expression(c, n, left)) - use #(c, right) <- try(infer_expression(c, n, right)) + use #(c, left) <- result.try(infer_expression(c, n, left)) + use #(c, right) <- result.try(infer_expression(c, n, right)) // unify the function type with the types of args let #(c, typ) = new_type_var_ref(c) - let c = unify(c, fun_typ, FunctionType([left.typ, right.typ], typ)) + use c <- result.map(unify( + c, + fun_typ, + FunctionType([left.typ, right.typ], typ), + )) - Ok(#(c, BinaryOperator(typ, name, left, right))) + #(c, BinaryOperator(typ, name, left, right)) } g.Echo(_, expression) -> { case expression { Some(expr) -> { - use #(c, expr) <- try(infer_expression(c, n, expr)) + use #(c, expr) <- result.try(infer_expression(c, n, expr)) Ok(#(c, Echo(expr.typ, Some(expr)))) } None -> { @@ -2377,10 +2385,10 @@ fn infer_fn( let typ = FunctionType(parameter_types, return_type) // unify parameters with type hint - let c = case hint { + use c <- result.try(case hint { Some(hint) -> unify(c, typ, hint) - None -> c - } + None -> Ok(c) + }) // put params into local env let n = @@ -2392,13 +2400,13 @@ fn infer_fn( }) // infer body - use #(c, body) <- result.map(infer_body(c, n, body)) + use #(c, body) <- result.try(infer_body(c, n, body)) // unify the return type with the last statement - let c = case list.last(body) { + use c <- result.map(case list.last(body) { Ok(statement) -> unify(c, return_type, statement.typ) - Error(_) -> c - } + Error(_) -> Ok(c) + }) let fun = Fn(typ:, parameters:, return:, body:) #(c, fun) @@ -2465,76 +2473,54 @@ fn do_instantiate(c: Context, n: PolyEnv, typ: Type) -> Type { } } -fn unify(c: Context, a: Type, b: Type) -> Context { +fn unify(c: Context, a: Type, b: Type) -> Result(Context, Error) { let a = resolve_type(c, a) let b = resolve_type(c, b) case a, b { VariableType(ref), b -> case a == b { - True -> c + True -> Ok(c) False -> { // TODO: guaranteed to succeed? let assert Unbound(aid) = get_type_var(c, ref) let #(c, occurs) = occurs(c, aid, b) case occurs { - True -> { - panic as error(c, "recursive type") - } - False -> { - set_type_var(c, ref, Bound(b)) - } + True -> Error(RecursiveTypeError) + False -> Ok(set_type_var(c, ref, Bound(b))) } } } a, VariableType(_) -> unify(c, b, a) NamedType(aname, amodule, _), NamedType(bname, bmodule, _) if aname != bname || amodule != bmodule - -> { - panic as error( - c, - "failed to unify types \n\n" - <> string.inspect(a) - <> "\n" - <> string.inspect(b), - ) - } - NamedType(_, _, aargs), NamedType(_, _, bargs) -> { - case list.strict_zip(aargs, bargs) { - Ok(args) -> list.fold(args, c, fn(c, x) { unify(c, x.0, x.1) }) - Error(_) -> { - panic as error(c, "incorrect number of type arguments") - } - } - } + -> Error(IncompatibleTypes(a, b)) + NamedType(_, _, aargs), NamedType(_, _, bargs) -> + unify_arguments(c, aargs, bargs) FunctionType(aargs, aret), FunctionType(bargs, bret) -> { - let c = unify(c, aret, bret) - case list.strict_zip(aargs, bargs) { - Ok(args) -> list.fold(args, c, fn(c, x) { unify(c, x.0, x.1) }) - Error(_) -> { - panic as error(c, "incorrect number of function arguments") - } - } + use c <- result.try(unify(c, aret, bret)) + unify_arguments(c, aargs, bargs) } TupleType(aelements), TupleType(belements) -> { - case list.strict_zip(aelements, belements) { - Ok(args) -> list.fold(args, c, fn(c, x) { unify(c, x.0, x.1) }) - Error(_) -> { - panic as error(c, "incorrect tuple size") - } - } - } - _, _ -> { - panic as error( - c, - "failed to unify types \n\n" - <> string.inspect(a) - <> "\n" - <> string.inspect(b), - ) + unify_arguments(c, aelements, belements) } + _, _ -> Error(IncompatibleTypes(a, b)) } } +fn unify_arguments( + c: Context, + aargs: List(Type), + bargs: List(Type), +) -> Result(Context, Error) { + use args <- result.try( + list.strict_zip(aargs, bargs) + |> result.map_error(fn(_) { + WrongArity(list.length(aargs), list.length(bargs)) + }), + ) + list.try_fold(args, c, fn(c, x) { unify(c, x.0, x.1) }) +} + fn occurs(c: Context, id: Int, in: Type) -> #(Context, Bool) { case in { VariableType(ref) -> From ab9771473ad740d93070e066f993e79f7e09333d Mon Sep 17 00:00:00 2001 From: Gert van Valkenhoef Date: Wed, 25 Feb 2026 11:04:06 +0100 Subject: [PATCH 3/8] schurhammer/gig#6: refactor - propagate errors in typed_ast --- src/gig/typed_ast.gleam | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gig/typed_ast.gleam b/src/gig/typed_ast.gleam index c26e3f1..ab236fd 100644 --- a/src/gig/typed_ast.gleam +++ b/src/gig/typed_ast.gleam @@ -1927,6 +1927,7 @@ fn infer_expression( #(c, [result, ..fields]) }), ) + let ordered_fields = list.reverse(ordered_fields) // The result type is the same as the constructor type let typ = constructor_ret From 67e52126dc2e7f9eee0cdb7680aba197da7517e6 Mon Sep 17 00:00:00 2001 From: Gert van Valkenhoef Date: Wed, 25 Feb 2026 12:52:14 +0100 Subject: [PATCH 4/8] schurhammer/gig#6: refactor - add span to typed_ast.Error --- src/gig/typed_ast.gleam | 98 +++++++++++++++++++++++++---------------- 1 file changed, 59 insertions(+), 39 deletions(-) diff --git a/src/gig/typed_ast.gleam b/src/gig/typed_ast.gleam index ab236fd..d2f337c 100644 --- a/src/gig/typed_ast.gleam +++ b/src/gig/typed_ast.gleam @@ -325,21 +325,21 @@ pub type Annotation { // TODO: errors should probably get a Span pub type Error { - UnresolvedModule(name: String) - UnresolvedGlobal(name: String) - UnresolvedType(name: String) - UnresolvedFunction(name: String) - EmptyBlock - InvalidTupleAccess - InvalidFieldAccess - FieldNotFound - UnresolvedTypeVariable(String) - NotAFunction(ResolvedGlobal) - WrongArity(expected_arg_count: Int, actual_arg_count: Int) - LabelNotFound(String) - TupleIndexOutOfBounds(tuple_size: Int, index: Int) - IncompatibleTypes(Type, Type) - RecursiveTypeError + UnresolvedModule(span: Span, name: String) + UnresolvedGlobal(span: Span, name: String) + UnresolvedType(span: Span, name: String) + UnresolvedFunction(span: Span, name: String) + EmptyBlock(span: Span) + InvalidTupleAccess(span: Span) + InvalidFieldAccess(span: Span) + FieldNotFound(span: Span) + UnresolvedTypeVariable(span: Span, name: String) + NotAFunction(span: Span, name: String) + WrongArity(span: Span, expected_arg_count: Int, actual_arg_count: Int) + LabelNotFound(span: Span, name: String) + TupleIndexOutOfBounds(span: Span, tuple_size: Int, index: Int) + IncompatibleTypes(span: Span, type_a: Type, type_b: Type) + RecursiveTypeError(span: Span) } pub type QualifiedName { @@ -618,7 +618,7 @@ pub fn infer_module( use group <- result.try( list.try_map(group, fn(fun_name) { list.find(module.functions, fn(f) { f.definition.name == fun_name }) - |> result.replace_error(UnresolvedFunction(fun_name)) + |> result.replace_error(UnresolvedFunction(c.current_span, fun_name)) }), ) @@ -1073,7 +1073,11 @@ fn do_infer_annotation( use mapping <- result.map( list.strict_zip(poly.vars, param_types) |> result.map_error(fn(_) { - WrongArity(list.length(poly.vars), list.length(param_types)) + WrongArity( + c.current_span, + list.length(poly.vars), + list.length(param_types), + ) }) |> result.map(dict.from_list), ) @@ -1110,7 +1114,7 @@ fn do_infer_annotation( g.VariableType(_, name) -> { use typ <- result.map( dict.get(n, name) - |> result.replace_error(UnresolvedTypeVariable(name)), + |> result.replace_error(UnresolvedTypeVariable(c.current_span, name)), ) #(c, VariableAnno(typ, name)) } @@ -1179,7 +1183,7 @@ pub fn resolve_global_name( ) -> Result(ResolvedGlobal, Error) { use module <- result.try(get_module(c, module_name)) dict.get(module.value_env, name) - |> result.replace_error(UnresolvedGlobal(name)) + |> result.replace_error(UnresolvedGlobal(c.current_span, name)) } /// Resolve a type name from the global environment @@ -1215,7 +1219,10 @@ pub fn resolve_global_type_name( use module <- result.try(get_module(c, module_name)) use #(typ, variants) <- result.map( dict.get(module.type_env, name) - |> result.replace_error(UnresolvedType(module_name <> "." <> name)), + |> result.replace_error(UnresolvedType( + c.current_span, + module_name <> "." <> name, + )), ) #(QualifiedName(module_name, name), typ, variants) } @@ -1231,13 +1238,13 @@ fn resolve_constructor_name(c: Context, mod: Option(String), name: String) { /// Resolve a module alias to its fully qualified name fn resolve_module(c: Context, module_name: String) -> Result(String, Error) { dict.get(get_current_module(c).module_env, module_name) - |> result.replace_error(UnresolvedModule(module_name)) + |> result.replace_error(UnresolvedModule(c.current_span, module_name)) } /// Get a module by its fully qualified name fn get_module(c: Context, module_name: String) -> Result(Module, Error) { dict.get(c.modules, module_name) - |> result.replace_error(UnresolvedModule(module_name)) + |> result.replace_error(UnresolvedModule(c.current_span, module_name)) } fn new_temp_var(c: Context) -> #(Context, String) { @@ -1512,7 +1519,11 @@ fn resolve_constructor(c: Context, module: Option(String), constructor: String) case constructor { FunctionGlobal(module:, name:, typ:, labels:) -> Ok(#(module, name, typ, labels)) - ConstantGlobal(..) -> Error(NotAFunction(constructor)) + ConstantGlobal(..) -> + Error(NotAFunction( + c.current_span, + constructor.module <> "." <> constructor.name, + )) } } @@ -1647,6 +1658,7 @@ fn infer_body( } } +// TODO: move this function out fn error(c: Context, message: String) -> String { let start = c.current_span.start let end = c.current_span.end @@ -1711,7 +1723,7 @@ fn match_labels( |> result.try_recover(fn(_) { listx.pop(args, fn(a) { a.label == None }) }) |> result.map_error(fn(_) { case p { - Some(l) -> LabelNotFound(l) + Some(l) -> LabelNotFound(c.current_span, l) // TODO: return WrongArity None -> panic as error(c, "not enough arguments given") } @@ -1783,7 +1795,7 @@ fn infer_expression( use #(c, statements) <- result.try(infer_body(c, n, statements)) case list.last(statements) { Ok(last) -> Ok(#(c, Block(last.typ, statements))) - Error(_) -> Error(EmptyBlock) + Error(_) -> Error(EmptyBlock(c.current_span)) } } g.Panic(_, e) -> { @@ -1909,7 +1921,11 @@ fn infer_expression( use ordered_fields <- result.try( list.strict_zip(ordered_fields, constructor_args) |> result.map_error(fn(_) { - WrongArity(list.length(constructor_args), list.length(ordered_fields)) + WrongArity( + c.current_span, + list.length(constructor_args), + list.length(ordered_fields), + ) }), ) @@ -1954,7 +1970,7 @@ fn infer_expression( // field access must be on a named type let value_typ = case resolve_type(c, value.typ) { NamedType(type_name, module, _) -> Ok(#(type_name, module)) - _ -> Error(InvalidFieldAccess) + _ -> Error(InvalidFieldAccess(c.current_span)) } use #(type_name, module) <- result.try(value_typ) @@ -1965,7 +1981,7 @@ fn infer_expression( let variant = case custom.definition.variants { // TODO proper implementation checking all variants [variant, ..] -> Ok(variant) - _ -> Error(InvalidFieldAccess) + _ -> Error(InvalidFieldAccess(c.current_span)) } use variant <- result.try(variant) @@ -1974,7 +1990,7 @@ fn infer_expression( variant.fields |> list.index_map(fn(x, i) { #(x, i) }) |> list.find(fn(x) { { x.0 }.label == Some(label) }) - |> result.replace_error(FieldNotFound) + |> result.replace_error(FieldNotFound(c.current_span)) use #(field, index) <- result.try(field) // create a getter function type @@ -2040,7 +2056,7 @@ fn infer_expression( use args <- result.map( list.strict_zip(params, args) |> result.map_error(fn(_) { - WrongArity(list.length(params), list.length(args)) + WrongArity(c.current_span, list.length(params), list.length(args)) }), ) args @@ -2086,7 +2102,7 @@ fn infer_expression( tuple_index_type(c, elements, index) |> result.map(fn(typ) { #(c, TupleIndex(typ, tuple, index)) }) } - _ -> Error(InvalidTupleAccess) + _ -> Error(InvalidTupleAccess(c.current_span)) } } g.FnCapture(span, label, function, arguments_before, arguments_after) -> { @@ -2189,7 +2205,11 @@ fn infer_expression( use sub_pats <- result.try( list.strict_zip(subjects, pat) |> result.map_error(fn(_) { - WrongArity(list.length(subjects), list.length(pat)) + WrongArity( + c.current_span, + list.length(subjects), + list.length(pat), + ) }), ) use #(c, n, pat) <- result.map( @@ -2336,17 +2356,17 @@ fn infer_expression( fn resolve_custom_type(c: Context, module: String, type_name: String) { use mod <- result.try(get_module(c, module)) list.find(mod.custom_types, fn(x) { x.definition.name == type_name }) - |> result.replace_error(UnresolvedType(type_name)) + |> result.replace_error(UnresolvedType(c.current_span, type_name)) } fn tuple_index_type( - _c: Context, + c: Context, elements: List(Type), index: Int, ) -> Result(Type, Error) { index_into_list(elements, index) |> result.map_error(fn(_) { - TupleIndexOutOfBounds(list.length(elements), index) + TupleIndexOutOfBounds(c.current_span, list.length(elements), index) }) } @@ -2486,7 +2506,7 @@ fn unify(c: Context, a: Type, b: Type) -> Result(Context, Error) { let assert Unbound(aid) = get_type_var(c, ref) let #(c, occurs) = occurs(c, aid, b) case occurs { - True -> Error(RecursiveTypeError) + True -> Error(RecursiveTypeError(c.current_span)) False -> Ok(set_type_var(c, ref, Bound(b))) } } @@ -2494,7 +2514,7 @@ fn unify(c: Context, a: Type, b: Type) -> Result(Context, Error) { a, VariableType(_) -> unify(c, b, a) NamedType(aname, amodule, _), NamedType(bname, bmodule, _) if aname != bname || amodule != bmodule - -> Error(IncompatibleTypes(a, b)) + -> Error(IncompatibleTypes(c.current_span, a, b)) NamedType(_, _, aargs), NamedType(_, _, bargs) -> unify_arguments(c, aargs, bargs) FunctionType(aargs, aret), FunctionType(bargs, bret) -> { @@ -2504,7 +2524,7 @@ fn unify(c: Context, a: Type, b: Type) -> Result(Context, Error) { TupleType(aelements), TupleType(belements) -> { unify_arguments(c, aelements, belements) } - _, _ -> Error(IncompatibleTypes(a, b)) + _, _ -> Error(IncompatibleTypes(c.current_span, a, b)) } } @@ -2516,7 +2536,7 @@ fn unify_arguments( use args <- result.try( list.strict_zip(aargs, bargs) |> result.map_error(fn(_) { - WrongArity(list.length(aargs), list.length(bargs)) + WrongArity(c.current_span, list.length(aargs), list.length(bargs)) }), ) list.try_fold(args, c, fn(c, x) { unify(c, x.0, x.1) }) From c37598270f701f6b15a150a5a6f26e279bd2dfc7 Mon Sep 17 00:00:00 2001 From: Gert van Valkenhoef Date: Wed, 25 Feb 2026 20:42:41 +0100 Subject: [PATCH 5/8] schurhammer/gig#6: refactor - add inspect_error --- src/gig/compiler.gleam | 52 ++++++++++++++- src/gig/typed_ast.gleam | 137 +++++++++++++++++++++------------------- 2 files changed, 120 insertions(+), 69 deletions(-) diff --git a/src/gig/compiler.gleam b/src/gig/compiler.gleam index 36daf8c..d0d033d 100644 --- a/src/gig/compiler.gleam +++ b/src/gig/compiler.gleam @@ -5,6 +5,7 @@ import gig/headers import gig/mono import gig/patch import gig/typed_ast +import gleam/bit_array import gleam/dict import gleam/set import listx @@ -311,9 +312,10 @@ fn infer_file( // infer this file io.println("Check " <> source) - let assert Ok(c) = typed_ast.infer_module(c, module, module_id, source_text) - - #(c, done) + case typed_ast.infer_module(c, module, module_id, source_text) { + Ok(c) -> #(c, done) + Error(err) -> panic as error(source, err.span, typed_ast.inspect_error(err)) + } } fn parse_module(file: String, input: String) { @@ -337,3 +339,47 @@ fn parse_module(file: String, input: String) { } } } + +fn error(source: String, span: typed_ast.Span, message: String) -> String { + let start = span.start + let end = span.end + + let context_before = case start { + start if start < 100 -> string.slice(source, 0, start) + start -> string.slice(source, start - 100, 100) + } + let context_before = string.split(context_before, "\n") + let context_before = + context_before + |> list.drop(list.length(context_before) - 3) + |> list.drop_while(fn(x) { x == "" }) + |> string.join("\n") + + let context_after = + source + |> string.slice(end, 100) + |> string.split("\n") + |> list.take(3) + |> string.join("\n") + + let error_part = string.slice(source, start, end - start) + + // TODO this unicode escape sequence is not valid in c, needs compilation + // let escape = "\u{001b}[" + let assert Ok(escape) = bit_array.to_string(<<27, 91>>) + let clear = escape <> "0m" + let red_underline = escape <> "4;31m" + + "Error: " + <> message + // <> "\n\nat: " + // <> c.current_module + // <> "." + // <> c.current_definition + <> "\n\n" + <> context_before + <> red_underline + <> error_part + <> clear + <> context_after +} diff --git a/src/gig/typed_ast.gleam b/src/gig/typed_ast.gleam index d2f337c..5a739e2 100644 --- a/src/gig/typed_ast.gleam +++ b/src/gig/typed_ast.gleam @@ -1,7 +1,6 @@ import gig/call_graph import gig/graph import glance.{Span} as g -import gleam/bit_array import listx import gleam/dict.{type Dict} @@ -332,7 +331,7 @@ pub type Error { EmptyBlock(span: Span) InvalidTupleAccess(span: Span) InvalidFieldAccess(span: Span) - FieldNotFound(span: Span) + FieldNotFound(span: Span, name: String) UnresolvedTypeVariable(span: Span, name: String) NotAFunction(span: Span, name: String) WrongArity(span: Span, expected_arg_count: Int, actual_arg_count: Int) @@ -340,6 +339,7 @@ pub type Error { TupleIndexOutOfBounds(span: Span, tuple_size: Int, index: Int) IncompatibleTypes(span: Span, type_a: Type, type_b: Type) RecursiveTypeError(span: Span) + BitPatternSegmentTypeOverSpecified(span: Span) } pub type QualifiedName { @@ -665,6 +665,51 @@ pub fn infer_module( }) } +/// Returns a human-readable string description of the error. +/// Does not include the span (location) of the error. +pub fn inspect_error(error: Error) { + case error { + UnresolvedModule(name:, ..) -> "Module with name '" <> name <> "' not found" + UnresolvedGlobal(name:, ..) -> "Global with name '" <> name <> "' not found" + UnresolvedType(name:, ..) -> "Type with name '" <> name <> "' not found" + UnresolvedFunction(name:, ..) -> + "Function with name '" <> name <> "' not found" + EmptyBlock(..) -> "Block is empty" + InvalidTupleAccess(..) -> "Attempted tuple access on a non-tuple type" + InvalidFieldAccess(..) -> "Attempted field access on a non-record type" + FieldNotFound(name:, ..) -> + "This record does not have a field named '" <> name <> "'" + UnresolvedTypeVariable(name:, ..) -> + "Type variable with name '" <> name <> "' not found" + NotAFunction(name:, ..) -> "The variable '" <> name <> "' is not a function" + WrongArity(expected_arg_count:, actual_arg_count:, ..) -> + "Function with arity " + <> int.to_string(expected_arg_count) + <> " called with " + <> int.to_string(actual_arg_count) + <> " arguments" + LabelNotFound(name:, ..) -> + "The called function does not have an argument with label '" + <> name + <> "'" + TupleIndexOutOfBounds(tuple_size:, index:, ..) -> + "Tuple index " + <> int.to_string(index) + <> " exceeds the size of the tuple (" + <> int.to_string(tuple_size) + <> ")" + IncompatibleTypes(type_a:, type_b:, ..) -> + "Incompatible types: a = " + <> string.inspect(type_a) + <> ", b = " + <> string.inspect(type_b) + RecursiveTypeError(..) -> + "Encountered a cyclical dependency between type variables" + BitPatternSegmentTypeOverSpecified(_) -> + "Bit pattern segment type set multiple times" + } +} + fn generalise(c: Context, typ: Type) { let tvs = list.unique(find_tvs(c, typ)) @@ -1375,7 +1420,7 @@ fn infer_pattern( use #(c, n, options, typ) <- result.try( list.try_fold(options, #(c, n, [], None), fn(acc, option) { let #(c, n, options, typ) = acc - use #(c, n, option, option_type) <- result.map(case option { + use #(c, n, option, option_type) <- result.try(case option { g.BigOption -> Ok(#(c, n, BigOption, None)) g.LittleOption -> Ok(#(c, n, LittleOption, None)) g.NativeOption -> Ok(#(c, n, NativeOption, None)) @@ -1402,12 +1447,12 @@ fn infer_pattern( } g.UnitOption(unit) -> Ok(#(c, n, UnitOption(unit), None)) }) - let typ = case typ, option_type { + use typ <- result.map(case typ, option_type { Some(_), Some(_) -> - panic as error(c, "segment type was set multiple times") - Some(_), None -> typ - _, _ -> option_type - } + Error(BitPatternSegmentTypeOverSpecified(c.current_span)) + Some(_), None -> Ok(typ) + _, _ -> Ok(option_type) + }) #(c, n, [option, ..options], typ) }), ) @@ -1658,65 +1703,27 @@ fn infer_body( } } -// TODO: move this function out -fn error(c: Context, message: String) -> String { - let start = c.current_span.start - let end = c.current_span.end - - let context_before = case start { - start if start < 100 -> string.slice(c.module_source, 0, start) - start -> string.slice(c.module_source, start - 100, 100) - } - let context_before = string.split(context_before, "\n") - let context_before = - context_before - |> list.drop(list.length(context_before) - 3) - |> list.drop_while(fn(x) { x == "" }) - |> string.join("\n") - - let context_after = - c.module_source - |> string.slice(end, 100) - |> string.split("\n") - |> list.take(3) - |> string.join("\n") - - let error_part = string.slice(c.module_source, start, end - start) - - // TODO this unicode escape sequence is not valid in c, needs compilation - // let escape = "\u{001b}[" - let assert Ok(escape) = bit_array.to_string(<<27, 91>>) - let clear = escape <> "0m" - let red_underline = escape <> "4;31m" - - "Error: " - <> message - <> "\n\nat: " - <> c.current_module - <> "." - <> c.current_definition - <> "\n\n" - <> context_before - <> red_underline - <> error_part - <> clear - <> context_after +fn match_labels( + c: Context, + args: List(Field(a)), + params: List(Option(String)), +) -> Result(List(Field(a)), Error) { + do_match_labels(c, args, params, #(list.length(params), list.length(args))) } -fn match_labels( +fn do_match_labels( c: Context, args: List(Field(a)), params: List(Option(String)), + lens: #(Int, Int), ) -> Result(List(Field(a)), Error) { // find the labels in the order specified by parameters // either we find the matching label or default to the first unlabelled arg - // TODO: keep track of original number of args/params for WrongArity error case params { [] -> case args { [] -> Ok([]) - // TODO: return WrongArity - _ -> panic as error(c, "too many arguments given") + _ -> Error(WrongArity(c.current_span, lens.0, lens.1)) } [p, ..p_rest] -> listx.pop(args, fn(a) { a.label == p }) @@ -1724,8 +1731,7 @@ fn match_labels( |> result.map_error(fn(_) { case p { Some(l) -> LabelNotFound(c.current_span, l) - // TODO: return WrongArity - None -> panic as error(c, "not enough arguments given") + None -> WrongArity(c.current_span, lens.0, lens.1) } }) |> result.try(fn(r) { @@ -1990,7 +1996,7 @@ fn infer_expression( variant.fields |> list.index_map(fn(x, i) { #(x, i) }) |> list.find(fn(x) { { x.0 }.label == Some(label) }) - |> result.replace_error(FieldNotFound(c.current_span)) + |> result.replace_error(FieldNotFound(c.current_span, label)) use #(field, index) <- result.try(field) // create a getter function type @@ -2127,7 +2133,7 @@ fn infer_expression( list.try_fold(options, #(c, [], None), fn(acc, option) { let #(c, options, typ) = acc // TODO handle all options - use #(c, option, option_type) <- result.map(case option { + use #(c, option, option_type) <- result.try(case option { g.BigOption -> Ok(#(c, BigOption, None)) g.BytesOption -> Ok(#(c, BytesOption, Some(bit_array_type))) g.BitsOption -> Ok(#(c, BitsOption, Some(bit_array_type))) @@ -2154,13 +2160,12 @@ fn infer_expression( Ok(#(c, Utf8CodepointOption, Some(codepoint_type))) g.Utf8Option -> Ok({ #(c, Utf8Option, Some(string_type)) }) }) - // TODO: wrap in a result? - let typ = case typ, option_type { + use typ <- result.map(case typ, option_type { Some(_), Some(_) -> - panic as error(c, "segment type was set multiple times") - Some(_), None -> typ - _, _ -> option_type - } + Error(BitPatternSegmentTypeOverSpecified(c.current_span)) + Some(_), None -> Ok(typ) + _, _ -> Ok(option_type) + }) #(c, [option, ..options], typ) }), ) @@ -2306,7 +2311,7 @@ fn infer_expression( ) // Functions - g.Pipe -> panic as error(c, "pipe should be handeled elsewhere") + g.Pipe -> panic as "pipe should be handeled elsewhere" // Maths g.AddInt | g.SubInt | g.MultInt | g.DivInt | g.RemainderInt -> #( From 9483a084d59fccf168b182a41137f49461464d63 Mon Sep 17 00:00:00 2001 From: Gert van Valkenhoef Date: Thu, 26 Feb 2026 08:04:11 +0100 Subject: [PATCH 6/8] schurhammer/gig#6: refactor - add full location to errors --- src/gig/compiler.gleam | 21 +++++---- src/gig/typed_ast.gleam | 94 ++++++++++++++++++++++------------------- 2 files changed, 64 insertions(+), 51 deletions(-) diff --git a/src/gig/compiler.gleam b/src/gig/compiler.gleam index d0d033d..584a52c 100644 --- a/src/gig/compiler.gleam +++ b/src/gig/compiler.gleam @@ -314,7 +314,8 @@ fn infer_file( io.println("Check " <> source) case typed_ast.infer_module(c, module, module_id, source_text) { Ok(c) -> #(c, done) - Error(err) -> panic as error(source, err.span, typed_ast.inspect_error(err)) + Error(err) -> + panic as error(source, err.location, typed_ast.inspect_error(err)) } } @@ -340,9 +341,13 @@ fn parse_module(file: String, input: String) { } } -fn error(source: String, span: typed_ast.Span, message: String) -> String { - let start = span.start - let end = span.end +fn error( + source: String, + location: typed_ast.Location, + message: String, +) -> String { + let start = location.span.start + let end = location.span.end let context_before = case start { start if start < 100 -> string.slice(source, 0, start) @@ -372,10 +377,10 @@ fn error(source: String, span: typed_ast.Span, message: String) -> String { "Error: " <> message - // <> "\n\nat: " - // <> c.current_module - // <> "." - // <> c.current_definition + <> "\n\nat: " + <> location.module + <> "." + <> location.definition <> "\n\n" <> context_before <> red_underline diff --git a/src/gig/typed_ast.gleam b/src/gig/typed_ast.gleam index 5a739e2..305d2a1 100644 --- a/src/gig/typed_ast.gleam +++ b/src/gig/typed_ast.gleam @@ -78,6 +78,10 @@ pub type FunctionDefinition { pub type Span = g.Span +pub type Location { + Location(module: String, definition: String, span: Span) +} + pub type Statement { Use(typ: Type, patterns: List(Pattern), function: Expression) Assignment( @@ -324,22 +328,22 @@ pub type Annotation { // TODO: errors should probably get a Span pub type Error { - UnresolvedModule(span: Span, name: String) - UnresolvedGlobal(span: Span, name: String) - UnresolvedType(span: Span, name: String) - UnresolvedFunction(span: Span, name: String) - EmptyBlock(span: Span) - InvalidTupleAccess(span: Span) - InvalidFieldAccess(span: Span) - FieldNotFound(span: Span, name: String) - UnresolvedTypeVariable(span: Span, name: String) - NotAFunction(span: Span, name: String) - WrongArity(span: Span, expected_arg_count: Int, actual_arg_count: Int) - LabelNotFound(span: Span, name: String) - TupleIndexOutOfBounds(span: Span, tuple_size: Int, index: Int) - IncompatibleTypes(span: Span, type_a: Type, type_b: Type) - RecursiveTypeError(span: Span) - BitPatternSegmentTypeOverSpecified(span: Span) + UnresolvedModule(location: Location, name: String) + UnresolvedGlobal(location: Location, name: String) + UnresolvedType(location: Location, name: String) + UnresolvedFunction(location: Location, name: String) + EmptyBlock(location: Location) + InvalidTupleAccess(location: Location) + InvalidFieldAccess(location: Location) + FieldNotFound(location: Location, name: String) + UnresolvedTypeVariable(location: Location, name: String) + NotAFunction(location: Location, name: String) + WrongArity(location: Location, expected_arg_count: Int, actual_arg_count: Int) + LabelNotFound(location: Location, name: String) + TupleIndexOutOfBounds(location: Location, tuple_size: Int, index: Int) + IncompatibleTypes(location: Location, type_a: Type, type_b: Type) + RecursiveTypeError(location: Location) + BitPatternSegmentTypeOverSpecified(location: Location) } pub type QualifiedName { @@ -618,7 +622,7 @@ pub fn infer_module( use group <- result.try( list.try_map(group, fn(fun_name) { list.find(module.functions, fn(f) { f.definition.name == fun_name }) - |> result.replace_error(UnresolvedFunction(c.current_span, fun_name)) + |> result.replace_error(UnresolvedFunction(location(c), fun_name)) }), ) @@ -1119,7 +1123,7 @@ fn do_infer_annotation( list.strict_zip(poly.vars, param_types) |> result.map_error(fn(_) { WrongArity( - c.current_span, + location(c), list.length(poly.vars), list.length(param_types), ) @@ -1159,7 +1163,7 @@ fn do_infer_annotation( g.VariableType(_, name) -> { use typ <- result.map( dict.get(n, name) - |> result.replace_error(UnresolvedTypeVariable(c.current_span, name)), + |> result.replace_error(UnresolvedTypeVariable(location(c), name)), ) #(c, VariableAnno(typ, name)) } @@ -1228,7 +1232,7 @@ pub fn resolve_global_name( ) -> Result(ResolvedGlobal, Error) { use module <- result.try(get_module(c, module_name)) dict.get(module.value_env, name) - |> result.replace_error(UnresolvedGlobal(c.current_span, name)) + |> result.replace_error(UnresolvedGlobal(location(c), name)) } /// Resolve a type name from the global environment @@ -1265,7 +1269,7 @@ pub fn resolve_global_type_name( use #(typ, variants) <- result.map( dict.get(module.type_env, name) |> result.replace_error(UnresolvedType( - c.current_span, + location(c), module_name <> "." <> name, )), ) @@ -1283,13 +1287,13 @@ fn resolve_constructor_name(c: Context, mod: Option(String), name: String) { /// Resolve a module alias to its fully qualified name fn resolve_module(c: Context, module_name: String) -> Result(String, Error) { dict.get(get_current_module(c).module_env, module_name) - |> result.replace_error(UnresolvedModule(c.current_span, module_name)) + |> result.replace_error(UnresolvedModule(location(c), module_name)) } /// Get a module by its fully qualified name fn get_module(c: Context, module_name: String) -> Result(Module, Error) { dict.get(c.modules, module_name) - |> result.replace_error(UnresolvedModule(c.current_span, module_name)) + |> result.replace_error(UnresolvedModule(location(c), module_name)) } fn new_temp_var(c: Context) -> #(Context, String) { @@ -1449,7 +1453,7 @@ fn infer_pattern( }) use typ <- result.map(case typ, option_type { Some(_), Some(_) -> - Error(BitPatternSegmentTypeOverSpecified(c.current_span)) + Error(BitPatternSegmentTypeOverSpecified(location(c))) Some(_), None -> Ok(typ) _, _ -> Ok(option_type) }) @@ -1566,7 +1570,7 @@ fn resolve_constructor(c: Context, module: Option(String), constructor: String) Ok(#(module, name, typ, labels)) ConstantGlobal(..) -> Error(NotAFunction( - c.current_span, + location(c), constructor.module <> "." <> constructor.name, )) } @@ -1723,15 +1727,15 @@ fn do_match_labels( [] -> case args { [] -> Ok([]) - _ -> Error(WrongArity(c.current_span, lens.0, lens.1)) + _ -> Error(WrongArity(location(c), lens.0, lens.1)) } [p, ..p_rest] -> listx.pop(args, fn(a) { a.label == p }) |> result.try_recover(fn(_) { listx.pop(args, fn(a) { a.label == None }) }) |> result.map_error(fn(_) { case p { - Some(l) -> LabelNotFound(c.current_span, l) - None -> WrongArity(c.current_span, lens.0, lens.1) + Some(l) -> LabelNotFound(location(c), l) + None -> WrongArity(location(c), lens.0, lens.1) } }) |> result.try(fn(r) { @@ -1801,7 +1805,7 @@ fn infer_expression( use #(c, statements) <- result.try(infer_body(c, n, statements)) case list.last(statements) { Ok(last) -> Ok(#(c, Block(last.typ, statements))) - Error(_) -> Error(EmptyBlock(c.current_span)) + Error(_) -> Error(EmptyBlock(location(c))) } } g.Panic(_, e) -> { @@ -1928,7 +1932,7 @@ fn infer_expression( list.strict_zip(ordered_fields, constructor_args) |> result.map_error(fn(_) { WrongArity( - c.current_span, + location(c), list.length(constructor_args), list.length(ordered_fields), ) @@ -1976,7 +1980,7 @@ fn infer_expression( // field access must be on a named type let value_typ = case resolve_type(c, value.typ) { NamedType(type_name, module, _) -> Ok(#(type_name, module)) - _ -> Error(InvalidFieldAccess(c.current_span)) + _ -> Error(InvalidFieldAccess(location(c))) } use #(type_name, module) <- result.try(value_typ) @@ -1987,7 +1991,7 @@ fn infer_expression( let variant = case custom.definition.variants { // TODO proper implementation checking all variants [variant, ..] -> Ok(variant) - _ -> Error(InvalidFieldAccess(c.current_span)) + _ -> Error(InvalidFieldAccess(location(c))) } use variant <- result.try(variant) @@ -1996,7 +2000,7 @@ fn infer_expression( variant.fields |> list.index_map(fn(x, i) { #(x, i) }) |> list.find(fn(x) { { x.0 }.label == Some(label) }) - |> result.replace_error(FieldNotFound(c.current_span, label)) + |> result.replace_error(FieldNotFound(location(c), label)) use #(field, index) <- result.try(field) // create a getter function type @@ -2062,7 +2066,7 @@ fn infer_expression( use args <- result.map( list.strict_zip(params, args) |> result.map_error(fn(_) { - WrongArity(c.current_span, list.length(params), list.length(args)) + WrongArity(location(c), list.length(params), list.length(args)) }), ) args @@ -2108,7 +2112,7 @@ fn infer_expression( tuple_index_type(c, elements, index) |> result.map(fn(typ) { #(c, TupleIndex(typ, tuple, index)) }) } - _ -> Error(InvalidTupleAccess(c.current_span)) + _ -> Error(InvalidTupleAccess(location(c))) } } g.FnCapture(span, label, function, arguments_before, arguments_after) -> { @@ -2162,7 +2166,7 @@ fn infer_expression( }) use typ <- result.map(case typ, option_type { Some(_), Some(_) -> - Error(BitPatternSegmentTypeOverSpecified(c.current_span)) + Error(BitPatternSegmentTypeOverSpecified(location(c))) Some(_), None -> Ok(typ) _, _ -> Ok(option_type) }) @@ -2211,7 +2215,7 @@ fn infer_expression( list.strict_zip(subjects, pat) |> result.map_error(fn(_) { WrongArity( - c.current_span, + location(c), list.length(subjects), list.length(pat), ) @@ -2361,7 +2365,7 @@ fn infer_expression( fn resolve_custom_type(c: Context, module: String, type_name: String) { use mod <- result.try(get_module(c, module)) list.find(mod.custom_types, fn(x) { x.definition.name == type_name }) - |> result.replace_error(UnresolvedType(c.current_span, type_name)) + |> result.replace_error(UnresolvedType(location(c), type_name)) } fn tuple_index_type( @@ -2371,7 +2375,7 @@ fn tuple_index_type( ) -> Result(Type, Error) { index_into_list(elements, index) |> result.map_error(fn(_) { - TupleIndexOutOfBounds(c.current_span, list.length(elements), index) + TupleIndexOutOfBounds(location(c), list.length(elements), index) }) } @@ -2511,7 +2515,7 @@ fn unify(c: Context, a: Type, b: Type) -> Result(Context, Error) { let assert Unbound(aid) = get_type_var(c, ref) let #(c, occurs) = occurs(c, aid, b) case occurs { - True -> Error(RecursiveTypeError(c.current_span)) + True -> Error(RecursiveTypeError(location(c))) False -> Ok(set_type_var(c, ref, Bound(b))) } } @@ -2519,7 +2523,7 @@ fn unify(c: Context, a: Type, b: Type) -> Result(Context, Error) { a, VariableType(_) -> unify(c, b, a) NamedType(aname, amodule, _), NamedType(bname, bmodule, _) if aname != bname || amodule != bmodule - -> Error(IncompatibleTypes(c.current_span, a, b)) + -> Error(IncompatibleTypes(location(c), a, b)) NamedType(_, _, aargs), NamedType(_, _, bargs) -> unify_arguments(c, aargs, bargs) FunctionType(aargs, aret), FunctionType(bargs, bret) -> { @@ -2529,7 +2533,7 @@ fn unify(c: Context, a: Type, b: Type) -> Result(Context, Error) { TupleType(aelements), TupleType(belements) -> { unify_arguments(c, aelements, belements) } - _, _ -> Error(IncompatibleTypes(c.current_span, a, b)) + _, _ -> Error(IncompatibleTypes(location(c), a, b)) } } @@ -2541,7 +2545,7 @@ fn unify_arguments( use args <- result.try( list.strict_zip(aargs, bargs) |> result.map_error(fn(_) { - WrongArity(c.current_span, list.length(aargs), list.length(bargs)) + WrongArity(location(c), list.length(aargs), list.length(bargs)) }), ) list.try_fold(args, c, fn(c, x) { unify(c, x.0, x.1) }) @@ -2617,3 +2621,7 @@ pub fn resolve_type_deep(c: Context, typ: Type) { TupleType(list.map(elements, resolve_type_deep(c, _))) } } + +fn location(c: Context) { + Location(c.current_module, c.current_definition, c.current_span) +} From d00c13107b7182dd56e4761f1a4f0b3700b672d8 Mon Sep 17 00:00:00 2001 From: Gert van Valkenhoef Date: Thu, 26 Feb 2026 08:18:24 +0100 Subject: [PATCH 7/8] schurhammer/gig#6: refactor - propagate errors in typed_ast --- src/gig/typed_ast.gleam | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/gig/typed_ast.gleam b/src/gig/typed_ast.gleam index 305d2a1..645dcbe 100644 --- a/src/gig/typed_ast.gleam +++ b/src/gig/typed_ast.gleam @@ -326,7 +326,6 @@ pub type Annotation { HoleAnno(typ: Type, name: String) } -// TODO: errors should probably get a Span pub type Error { UnresolvedModule(location: Location, name: String) UnresolvedGlobal(location: Location, name: String) @@ -914,7 +913,7 @@ fn infer_alias_type( let #(c, n, args) = acc let #(c, typ) = new_type_var_ref(c) let n = dict.insert(n, name, typ) - // TODO: does this need wrapping in a result or is it guaranteed to succeed? + // assert: new_type_var_ref always returns a VariableType let assert VariableType(ref) = typ #(c, n, [ref.id, ..args]) }) @@ -1905,9 +1904,12 @@ fn infer_expression( // Instantiate the constructor type let #(c, constructor_type) = instantiate(c, poly) - // TODO: wrap in a result, or is this guaranteed to succeed? - let assert FunctionType(constructor_args, constructor_ret) = - constructor_type + use #(constructor_args, constructor_ret) <- result.try( + case constructor_type { + FunctionType(parameters:, return:) -> Ok(#(parameters, return)) + _ -> Error(NotAFunction(location(c), constructor)) + }, + ) // Unify the base expression type with the constructor type use c <- result.try(unify(c, base_expr.typ, constructor_ret)) @@ -2446,7 +2448,7 @@ type PolyEnv = Dict(Int, Type) fn get_type_var(c: Context, var: Ref) { - // TODO: guaranteed to succeed? + // assert: this function is only called for previously created type variables let assert Ok(x) = dict.get(c.type_vars, var) x } @@ -2511,7 +2513,7 @@ fn unify(c: Context, a: Type, b: Type) -> Result(Context, Error) { case a == b { True -> Ok(c) False -> { - // TODO: guaranteed to succeed? + // assert: since a resolves to VariableType(ref), ref is Unbound let assert Unbound(aid) = get_type_var(c, ref) let #(c, occurs) = occurs(c, aid, b) case occurs { @@ -2587,9 +2589,7 @@ fn occurs(c: Context, id: Int, in: Type) -> #(Context, Bool) { pub fn resolve_type(c: Context, typ: Type) -> Type { case typ { VariableType(x) -> { - // TODO: guaranteed to succeed? - let assert Ok(x) = dict.get(c.type_vars, x) - case x { + case get_type_var(c, x) { Bound(x) -> resolve_type(c, x) Unbound(..) -> typ } @@ -2603,9 +2603,7 @@ pub fn resolve_type(c: Context, typ: Type) -> Type { pub fn resolve_type_deep(c: Context, typ: Type) { case typ { VariableType(x) -> { - // TODO: guaranteed to succeed? - let assert Ok(x) = dict.get(c.type_vars, x) - case x { + case get_type_var(c, x) { Bound(x) -> resolve_type_deep(c, x) Unbound(..) -> typ } From c1601cdc90cb1f2f185a38ac23b4b0342a3c0e2c Mon Sep 17 00:00:00 2001 From: Julian Schurhammer Date: Thu, 26 Feb 2026 20:48:46 +1300 Subject: [PATCH 8/8] fix: error needs source_text not source --- src/gig/compiler.gleam | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gig/compiler.gleam b/src/gig/compiler.gleam index 584a52c..8a7d195 100644 --- a/src/gig/compiler.gleam +++ b/src/gig/compiler.gleam @@ -315,7 +315,7 @@ fn infer_file( case typed_ast.infer_module(c, module, module_id, source_text) { Ok(c) -> #(c, done) Error(err) -> - panic as error(source, err.location, typed_ast.inspect_error(err)) + panic as error(source_text, err.location, typed_ast.inspect_error(err)) } }