Download
920 lines 38.7 KiBMoonScriptModified 2022-10-10 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920
Types = require 'types'
import log, viz, id, node_assert, node_error, get_node_pos, print_err, each_tag from require 'util'
import Measure, register_unit_alias from require 'units'
concat = table.concat
local assign_types

parse_type = =>
    return @__parsed_type if @__parsed_type
    switch @__tag
        when "Var","TypeVar"
            if @__declaration
                assign_types @__declaration.__parent unless @__declaration.__type
                @__parsed_type = node_assert @__declaration.__type.type, @, "Couldn't figure out this type"
            else
                error "This type variable wasn't figured out in time"
                node_error @, "This type variable wasn't figured out in time"
                -- @__parsed_type = Types.NamedType(@[0])
        when "OptionalType"
            nonnil = node_assert parse_type(@nonnil), @nonnil, "Couldn't parse this type"
            @__parsed_type = Types.OptionalType(nonnil)
        when "MeasureType"
            m = Measure(1, @[0]\gsub("[<>]",""))
            @__parsed_type = Types.MeasureType(m.str)\normalized!
        when "TupleType"
            t = Types.StructType("")
            @__parsed_type = t
            i = 1
            for memgroup in *@members
                member_type = parse_type memgroup.type
                return unless member_type
                if memgroup.names
                    for name in *memgroup.names
                        t\add_member name[0], member_type, (name.inline and true or false)
                else
                    t\add_member i, member_type, false
                    i += 1
        when "TableType"
            key = node_assert parse_type(@keyType), @keyType, "Couldn't parse this type"
            val = node_assert parse_type(@valueType), @valueType, "Couldn't parse this type"
            @__parsed_type = Types.TableType(key, val)
        when "ListType"
            item = node_assert parse_type(@itemType), @itemType, "Couldn't parse this type"
            @__parsed_type = Types.ListType(item)
        when "FnType"
            ret_type = if @returnType
                node_assert parse_type(@returnType), @returnType, "Couldn't parse this type"
            else
                Types.NilType
            arg_types = {}
            arg_names = {}
            for arg in *@args
                table.insert arg_names, arg.name[0] if arg.name
                arg_t = node_assert parse_type(arg.type), arg, "Couldn't parse this type"
                table.insert arg_types, arg_t

            if #arg_names == 0 and #arg_types > 0
                arg_names = nil
            @__parsed_type = Types.FnType(arg_types, ret_type, arg_names)
        else
            error "Not implemented: #{@__tag}"
    return @__parsed_type

get_fn_type = (fndec)->
    ret_type = node.returnType and parse_type(node.returnType) or Types.NilType
    return Types.FnType([parse_type a.type for a in *node.args], ret_type, [a.name[0] for a in *node.args])

bind_var = (scope, var)->
    assert var.__tag == "Var" or var.__tag == "TypeVar", "Not a Var: #{var.__tag}"
    switch scope.__tag
        when "Var"
            if scope[0] == var[0]
                scope.__declaration = var
        when "FnDecl","Lambda","ConvertDecl"
            if scope.selfVar and scope.selfVar[0] == var[0]
                return
            for arg in *scope.args
                if arg.name[0] == var[0]
                    -- Don't hook up shadowed args
                    return
            bind_var scope.body, var
        when "FnType"
            return
        else
            for k,child in pairs scope
                continue if type(child) != "table" or (type(k) == "string" and k\match("^__"))
                bind_var child, var

bind_type = (scope, typevar)->
    switch scope.__tag
        when "TypeVar"
            if scope[0] == typevar[0]
                scope.__declaration = typevar
        when "TypeDeclaration","StructDeclaration","UnionDeclaration","EnumDeclaration","UnitDeclaration"
            return if scope.name[0] == typevar[0] -- Shadowing
            for k,child in pairs scope
                continue if type(child) != "table" or (type(k) == "string" and k\match("^__"))
                bind_type child, typevar
        else
            for k,child in pairs scope
                continue if type(child) != "table" or (type(k) == "string" and k\match("^__"))
                bind_type child, typevar

bind_all_types = =>
    switch @__tag
        when "TypeDeclaration","StructDeclaration","UnionDeclaration","EnumDeclaration","UnitDeclaration"
            @name.__declaration = @name
            bind_var @__parent, @name
            bind_type @__parent, @name
            for k,child in pairs @
                continue if type(child) != "table" or (type(k) == "string" and k\match("^__"))
                bind_var child, @name
                bind_type child, @name
        else
            for k,child in pairs @
                continue if type(child) != "table" or (type(k) == "string" and k\match("^__"))
                bind_all_types child

table.find = (t, obj)->
    for k,v in pairs t
        if v == obj
            return k

bind_variables = =>
    switch @__tag
        when "Declaration"
            @var.__declaration = @var
            switch @__parent.__tag
                when "Block"
                    pos = table.find(@__parent, @)
                    for i=pos+1,#@__parent
                        bind_var @__parent[i], @var
                    if @__parent.__parent
                        switch @__parent.__parent.__tag
                            when "For","While","Repeat"
                                if @__parent == @__parent.__parent.body and @__parent.__parent.between
                                    bind_var @__parent.__parent.between, @var
                when "Clause"
                    bind_var @__parent.body, @var
            bind_variables @var
            bind_variables @value
        when "FnDecl","Lambda","ConvertDecl"
            @name.__declaration = @name if @name
            if @selfVar
                @selfVar.__declaration = @selfVar
                bind_var @body, @selfVar
            for arg in *@args
                arg.name.__declaration = arg.name
                bind_var @body, arg.name
            bind_var @body, @name if @name
            bind_var @__parent, @name if @name
            bind_variables @body
            if @__parent.__parent
                switch @__parent.__parent.__tag
                    when "For","While","Repeat"
                        if @__parent == @__parent.__parent.body and @__parent.__parent.between
                            bind_var @__parent.__parent.between, @name
        when "TypeDeclaration","StructDeclaration","UnionDeclaration","EnumDeclaration","UnitDeclaration"
            for k,child in pairs @
                continue if type(child) != "table" or (type(k) == "string" and k\match("^__"))
                bind_variables child
        when "Extern"
            @name.__declaration = @name
            @name.__register = "$"..@name[0]
            bind_var @__parent, @name
            if @__parent.__parent
                switch @__parent.__parent.__tag
                    when "For","While","Repeat"
                        if @__parent == @__parent.__parent.body and @__parent.__parent.between
                            bind_var @__parent.__parent.between, @name
        when "Use"
            error "Not implemented"
        when "For"
            if @index
                @index.__declaration = @index
                bind_var @body, @index
                bind_var @between, @index if @between
                bind_var @filter, @index if @filter
            if @val
                @val.__declaration = @val
                bind_var @body, @val
                bind_var @between, @val if @between
                bind_var @filter, @val if @filter

            bind_variables @body
            bind_variables @between if @between
            bind_variables @filter if @filter
        else
            for k,child in pairs @
                continue if type(child) != "table" or (type(k) == "string" and k\match("^__"))
                bind_variables child

assign_types = =>
    return unless type(@) == "table"
    switch @__tag
        when "Nil"
            @__type = Types.NilType
        when "Bool"
            @__type = Types.Bool
        when "String","Escape","Newline","FieldName"
            for interp in *(@content or {})
                assign_types interp
            @__type = Types.String
        when "Interp"
            assign_types @value
            @__type = @value.__type
        when "DSL"
            assign_types @content
            if not @name or @name[0] == ""
                @__type = Types.String
            else
                @__type = Types.DerivedType(@name[0], Types.String)
        when "TypeOf"
            assign_types @expression
            @__type = Types.TypeValue(@expression.__type) if @expression.__type
        when "SizeOf"
            assign_types @expression
            @__type = Types.Int
        when "Range"
            assign_types @first if @first
            assign_types @next if @next
            assign_types @last if @last
            @__type = Types.Range
        when "Pass"
            @__type = Types.NilType
        when "Stop","Skip","Fail"
            for k,child in pairs @
                continue if type(child) != "table" or (type(k) == "string" and k\match("^__"))
                assign_types child
            @__type = Types.Abort
        when "Return"
            assign_types @value if @value
            @__type = Types.Abort
        when "Float"
            -- TODO: support 0.5:Num32 without casting
            @__type = Types.Num
        when "Percent"
            @__type = Types.Percent
        when "Measure"
            m = Measure(1, @units[0]\gsub("[<>]",""))
            @__type = Types.MeasureType(m.str)\normalized!
            @__units = m

        when "Int"
            -- TODO: support 0.5:Int8 without casting
            @__type = Types.Int

        when "Declaration"
            assign_types @value
            @__type = @value.__type
            @var.__type = @value.__type

        when "Extern"
            @name.__type = parse_type @type

        when "Cast","As"
            assign_types @expr
            @__type = parse_type(@type)

        when "Var"
            if @__declaration
                @__type = @__declaration.__type

        when "Lambda"
            for arg in *@args
                arg.__type = parse_type(arg.type)
                arg.name.__type = arg.__type
            assign_types @body
            if @body and @body.__type
                @__type = Types.FnType([a.__type for a in *@args], @body.__type, [a.name[0] for a in *@args])

        when "FnDecl"
            if @selfVar
                node_assert @__parent.__tag == "StructDeclaration" or @__parent.__tag == "UnionDeclaration", @, "Method definition is not inside a struct or union"
                return unless @__parent.__type
                @selfVar.__type = @__parent.__type.type
            for arg in *@args
                arg.__type = parse_type(arg.type)
                arg.name.__type = arg.__type
            ret = @returnType and parse_type(@returnType) or Types.NilType
            arg_types = [a.__type for a in *@args]
            arg_names = [a.name[0] for a in *@args]
            if @selfVar
                table.insert arg_types, 1, @selfVar.__type
                table.insert arg_names, 1, @selfVar[0]
            @__type = Types.FnType(arg_types, ret, arg_names)
            @name.__type = @__type
            assign_types @body

        when "ConvertDecl"
            assert #@args == 1
            @args[1].__type = parse_type(@args[1].type)
            @args[1].name.__type = @args[1].__type
            ret = @returnType and parse_type(@returnType) or Types.NilType
            arg_types = [a.__type for a in *@args]
            arg_names = [a.name[0] for a in *@args]
            @__type = Types.FnType(arg_types, ret, arg_names)
            assign_types @body

        when "FnCall"
            assign_types @fn
            for arg in *@
                assign_types arg
            if not @__type and @fn.__type
                node_assert @fn.__type\is_a(Types.FnType), @fn, "Not a function: #{@fn.__type}"
                @__type = @fn.__type.return_type

        when "StructDeclaration"
            t = Types.StructType(@name[0])
            @__type = Types.TypeValue(t)
            @name.__type = @__type if @name
            for member in *@
                if member.__tag == "StructField"
                    member_type = node_assert parse_type(member.type), member.type, "Couldn't parse this type"
                    for name in *member.names
                        name.__type = member_type
                        t\add_member name[0], member_type, (name.inline and true or false)
            @__methods = {}
            @__staticmethods = {}
            @__staticvars = {}
            for member in *@
                if member.__tag == "FnDecl"
                    if member.selfVar
                        member.selfVar.__type = t
                        @__methods[member.name[0]] = member
                    else
                        @__staticmethods[member.name[0]] = member
                    assign_types member
                elseif member.__tag == "Declaration"
                    assign_types member
                    @__staticvars[member.var[0]] = member.var

        when "EnumDeclaration"
            t = Types.EnumType(@name[0])
            @__type = Types.TypeValue(t)
            @name.__type = @__type if @name
            for member in *@
                if member.__tag == "EnumField"
                    value = member.value and tonumber(member.value[0])
                    t\add_field member.name[0], value

        when "Struct"
            for member in *@
                assign_types member
            if @name
                if @name.__declaration
                    struct_dec = @name.__declaration.__parent
                    node_assert struct_dec.__tag == "StructDeclaration", @name, "This isn't a struct name, it's a #{struct_dec.__tag}"
                    @__type = struct_dec.__type.type
            else
                t = Types.StructType("")
                i = 1
                for member in *@
                    return unless member.value.__type
                    if member.name
                        t\add_member member.name[0], member.value.__type
                    else
                        t\add_member i, member.value.__type
                        i += 1
                @__type = t

        when "UnionDeclaration"
            t = Types.UnionType(@name[0])
            @__type = Types.TypeValue(t)
            @name.__type = @__type if @name
            for member in *@
                if member.__tag == "UnionField"
                    member_type = parse_type member.type
                    for name in *member.names
                        name.__type = member_type
                        t\add_member name[0], member_type, (name.inline and true or false)

            @__methods = {}
            @__staticmethods = {}
            @__staticvars = {}
            for member in *@
                if member.__tag == "FnDecl"
                    if member.selfVar
                        member.selfVar.__type = t
                        @__methods[member.name[0]] = member
                    else
                        @__staticmethods[member.name[0]] = member
                    assign_types member
                elseif member.__tag == "Declaration"
                    assign_types member
                    @__staticvars[member.var[0]] = member.var

        when "TypeDeclaration"
            derived = parse_type(@derivesFrom)
            if derived
                @__type = Types.TypeValue(Types.DerivedType(@name[0], derived))
                @name.__type = @__type

        when "List"
            if @type
                @__type = parse_type(@type)

            item_type = (item)->
                if item.__tag == "For" or item.__tag == "While"
                    return item.body[1].__type
                elseif item.__tag == "If"
                    return item[1].body[1].__type
                else
                    return item.__type

            for item in *@
                assign_types item
                -- node_assert item_type(item), item, "Couldn't get the type here #{item.__tag}"

            t = @__type and @__type.item_type or nil
            for item in *@
                t2 = item_type(item)
                return unless t2
                t = node_assert t2\orelse(t), item, "This list item has type #{t2}, but earlier items have type #{t}"

            @__type = Types.ListType(t)

        when "Table"
            if @type
                @__type = Types.TableType(parse_type(@type.keyType), parse_type(@type.valueType))
            
            for entry in *@
                assign_types entry

            kv_types = (item)->
                if item.__tag == "For" or item.__tag == "While"
                    node_assert item.body[1].__tag == "TableEntry", item.body, "Not a valid table comprehension value (expected `[k]=v`)"
                    return item.body[1].key.__type, item.body[1].value.__type
                elseif item.__tag == "If"
                    node_assert item[1].body[1].__tag == "TableEntry", item[1].body, "Not a valid table comprehension value (expected `[k]=v`)"
                    return item[1].body[1].key.__type, item[1].body[1].value.__type
                else
                    node_assert item.__tag == "TableEntry", "Not a valid table entry, expected [k]=v or k=v"
                    return item.key.__type, item.value.__type

            kt,vt = if @__type
                @__type.key_type, @__type.value_type
            else
                nil, nil
            for entry in *@
                kt2,vt2 = kv_types(entry)
                return unless kt2 and vt2
                kt = node_assert kt2\orelse(kt), entry.key, "This table key has type #{kt2}, but earlier keys have type #{kt}"
                vt = node_assert vt2\orelse(vt), entry.value, "This table value has type #{vt2}, but earlier values have type #{vt}"

            @__type = Types.TableType(kt, vt)
                
        when "IndexedTerm"
            assign_types @value
            assign_types @index
            return unless @value.__type

            t = @value.__type
            index_type = @index.__type

            if t\works_like_a(Types.ListType)
                return unless index_type
                if index_type == Types.Int
                    @__type = t.item_type
                elseif index_type == Types.Range
                    @__type = t
                elseif @index.__tag == "FieldName"
                    ListMethods = require 'list_methods'
                    -- List methods:
                    if t_fn = ListMethods.types[@index[0]]
                        @__type = t_fn(t)
                        @__method = ListMethods.methods[@index[0]]
                        @__inline_method = assert(ListMethods.methods[@index[0]], "List method isn't implemented: #{@index[0]}")
                    else
                        node_error @index, "#{@index[0]} is not a valid List method"
                else
                    node_error @index, "Index has type #{index_type}, but expected Int or Range"
            elseif t\works_like_a(Types.TableType)
                return unless index_type
                if @index.__tag == "FieldName"
                    TableMethods = require 'table_methods'
                    if t_fn = TableMethods.types[@index[0]]
                        @__type = t_fn(t)
                        @__method = TableMethods.methods[@index[0]]
                        @__inline_method = TableMethods.methods[@index[0]]
                    else
                        node_error @index, "#{@index[0]} is not a valid Table method"
                else
                    node_assert index_type == t.key_type, @index, "This table has type #{t}, but is being indexed with #{index_type}"
                    @__type = t.value_type
            elseif t\works_like_a(Types.StructType)
                if @index.__tag == "FieldName"
                    member_name = @index[0]
                    member_type = if t.members[member_name]
                        t.members[member_name].type
                    else
                        root = @
                        while root.__parent
                            root = root.__parent

                        method_type = nil
                        for dec in coroutine.wrap(-> each_tag(root, "StructDeclaration"))
                            assign_types dec
                            if dec.__type.type == t
                                method = dec.__methods[member_name]
                                if method
                                    method_type = method.name.__type
                                    @__type = method_type
                                    @__method = method.name
                                    @__declaration = method.name
                                    break
                        method_type

                    if member_type
                        @__type = member_type
                elseif @index.__tag == "Int"
                    i = tonumber(@index[0])
                    member_type = node_assert(t.members[i], @, "Not a valid #{t} field: #{i}").type
                    node_assert member_type, @index, "#{t} doesn't have a member #{i}"
                    @__type = member_type
                else
                    node_error @index, "Structs can only be indexed by a field name or Int literal"
            elseif t\works_like_a(Types.UnionType)
                if @index.__tag == "FieldName"
                    member_name = @index[0]
                    member_type = if t.members[member_name]
                        Types.OptionalType(t.members[member_name].type)
                    else
                        root = @
                        while root.__parent
                            root = root.__parent

                        method_type = nil
                        for dec in coroutine.wrap(-> each_tag(root, "UnionDeclaration"))
                            assign_types dec
                            continue unless dec.__methods
                            if dec.__type.type == t
                                method = dec.__methods[member_name]
                                if method
                                    method_type = method.name.__type
                                    @__type = method_type
                                    @__method = method.name
                                    @__declaration = method.name
                                    break
                        method_type

                    if member_type
                        @__type = member_type
                else
                    node_error @index, "Unions can only be indexed by a field name"
            elseif t\works_like_a(Types.String)
                return unless index_type
                if index_type == Types.Int
                    @__type = Types.Int
                elseif index_type == Types.Range
                    @__type = t
                elseif @index.__tag == "FieldName"
                    StringMethods = require 'string_methods'
                    if get_fn_t = StringMethods.types[@index[0]]
                        fn_t = get_fn_t(t)
                        @__type = fn_t
                        @__method = StringMethods.methods[@index[0]]
                        @__inline_method = StringMethods.methods[@index[0]]
                    else
                        node_error @index, "`#{@index[0]}` is not a valid String method"
                else
                    node_error @index, "Strings can only be indexed by Ints or Ranges"
            elseif t\is_a(Types.TypeValue)
                if t.type\is_a(Types.EnumType)
                    node_assert @index.__tag == "FieldName", @index, "The Enum class #{t} can only be indexed by a valid field name"
                    node_assert t.type.field_values[@index[0]], "#{t.type}.#{@index[0]} is not a valid field in the Enum #{t.type}"
                    @__type = t.type
                elseif t.type\is_a(Types.UnionType)
                    node_assert @index.__tag == "FieldName", @index, "The Union class #{t} can only be indexed by a valid field name"
                    dec = @value.__declaration.__parent
                    if member = t.type.members[@index[0]]
                        @__type = Types.FnType({t, member.type}, t.type)
                        @__inline_method = (code)=>
                            val_reg = code\add_value @[1]
                            union,val_loc = code\fresh_locals "#{t.type.name}", "val_loc"
                            code\add "#{union} =l call $gc_alloc(l #{t.type.memory_size})\n"
                            code\add "storel #{member.index}, #{union}\n"
                            code\add "#{val_loc} =l add #{union}, 8\n"
                            code\add "#{member.type.store} #{val_reg}, #{val_loc}\n"
                            return union
                    elseif staticmethod = dec.__staticmethods[@index[0]]
                        method_type = assert staticmethod.name.__type
                        @__staticmethod = staticmethod.name
                        @__declaration = staticmethod.name
                        @__type = method_type
                    elseif staticvar = dec.__staticvars[@index[0]]
                        @__declaration = staticvar
                        @__type = staticvar.__type
                    else
                        node_error @index, "#{t.type}.#{@index[0]} is not a valid member in the Union #{t.type}"
                elseif t.type\is_a(Types.StructType)
                    member_name = @index[0]
                    dec = @value.__declaration.__parent
                    if method = dec.__staticmethods[member_name]
                        method_type = assert method.name.__type
                        @__staticmethod = method.name
                        @__declaration = method.name
                        @__type = method_type
                    elseif staticvar = dec.__staticvars[member_name]
                        @__declaration = staticvar
                        @__type = staticvar.__type
                    else
                        node_error @index, "No such method on #{t.type}"
                else
                    node_error @, "Only Enum and Union types can be indexed, not #{t.type\verbose_type!}"
            elseif t\is_a(Types.Percent)
                node_assert @index.__tag == "FieldName", @index, "Percents cannot be indexed"
                PercentMethods = require 'percent_methods'
                if get_fn_t = PercentMethods.types[@index[0]]
                    fn_t = get_fn_t(t)
                    @__type = fn_t
                    @__method = PercentMethods.methods[@index[0]]
                    @__inline_method = PercentMethods.methods[@index[0]]
                else
                    node_error @index, "#{@index[0]} is not a valid Percent method"
            elseif t\is_a(Types.MeasureType)
                node_assert @index.__tag == "FieldName", @index, "Measures cannot be indexed"
                MeasureMethods = require 'measure_methods'
                if get_fn_t = MeasureMethods.types[@index[0]]
                    fn_t = get_fn_t(t)
                    @__type = fn_t
                    @__method = MeasureMethods.methods[@index[0]]
                    @__inline_method = MeasureMethods.methods[@index[0]]
                else
                    node_error @index, "#{@index[0]} is not a valid Measure method"
            elseif t\is_a(Types.Num)
                node_assert @index.__tag == "FieldName", @index, "Nums cannot be indexed"
                NumberMethods = require 'number_methods'
                if t_fn = NumberMethods.types[@index[0]]
                    @__type = t_fn(t)
                    @__method = NumberMethods.methods[@index[0]]
                    @__inline_method = NumberMethods.methods[@index[0]]
                else
                    node_error @index, "#{@index[0]} is not a valid Number method"
            else
                node_error @value, "Indexing is not valid on type #{t}"

        when "Declaration"
            assign_types @value
            @__type = @value.__type
            @key.__type = @__type

        when "Or"
            for item in *@
                assign_types item

            t = nil
            for i, item in ipairs @
                return unless item.__type
                node_assert not t or t\is_a(Types.Bool) or t\is_a(Types.OptionalType), item,
                    "This code is unreachable because the previous value is a #{t}, which is guaranteed to be truthy"
                if not t or t == Types.NilType
                    t = item.__type
                elseif t\is_a(Types.OptionalType)
                    if item.__type == t.nonnil
                        t = t.nonnil
                    elseif item.__type == Types.Abort
                        t = t.nonnil
                        node_assert i == #@, @[i+1], "This code is unreachable"
                    else
                        node_assert item.__type\is_a(t), item, "Expected a value of type `#{t}`, but got `#{item.__type}`"
                elseif item.__type\is_a(Types.Abort)
                    node_assert i == #@, @[i+1], "This code is unreachable"
                elseif item.__type == Types.NilType
                    t = if t
                        Types.OptionalType(t)
                    else
                        Types.NilType
                else
                    node_assert item.__type\is_a(t), item, "Expected a value of type `#{t}`, but got `#{item.__type}`"

            @__type = t

        when "And","Xor"
            items = if @__tag == "Xor"
                {@lhs, @rhs}
            else
                @
            assign_types items[1]
            t = items[1].__type
            return unless t
            for i,item in ipairs items
                assign_types item
                if item.__type == Types.Abort
                    if t != Types.Bool
                        t = Types.Abort
                elseif item.__type\is_a(Types.OptionalType)
                    node_assert item.__type.nonnil\is_a(t), item, "Type mismatch with #{t}"
                    t = Types.OptionalType(t)
                elseif t\is_a(Types.OptionalType)
                    node_assert item.__type\is_a(t), item, "Type mismatch with #{t}"
                else
                    node_assert item.__type == t, item, "Type mismatch with #{t}"
            @__type = t

        when "Equal","NotEqual","Less","LessEq","Greater","GreaterEq"
            assign_types @lhs
            assign_types @rhs
            @__type = Types.Bool

        when "In"
            assign_types @haystack
            assign_types @needle
            @__type = Types.Bool

        when "If"
            for clause in *@
                assign_types clause.condition
                assign_types clause.body
            if @elseBody
                assign_types @elseBody
            t = nil
            for clause in *@
                continue if clause.body.__type == Types.Abort
                return unless clause.body.__type
                t = node_assert clause.body.__type\orelse(t), item, "Type mismatch with #{t}"
            if @elseBody and @elseBody.__type != Types.Abort
                t = node_assert @elseBody.__type\orelse(t), item, "Type mismatch with #{t}"
            elseif not @elseBody
                t = Types.NilType\orelse(t)
            @__type = t

        when "When"
            assign_types @what
            for clause in *@
                for case in *clause.cases
                    assign_types case
                assign_types clause.body
            if @elseBody
                assign_types @elseBody
            t = nil
            for clause in *@
                continue if clause.body.__type == Types.Abort
                return unless clause.body.__type
                t = node_assert clause.body.__type\orelse(t), item, "Type mismatch with #{t}"
            if @elseBody and @elseBody.__type != Types.Abort
                t = node_assert @elseBody.__type\orelse(t), item, "Type mismatch with #{t}"
            elseif not @elseBody
                t = Types.NilType\orelse(t)
            @__type = t

        when "For"
            assign_types @iterable
            iter_type = @iterable.__type
            if iter_type
                if iter_type\works_like_a(Types.TableType)
                    if @index and @val
                        @index.__type = iter_type.key_type
                        @val.__type = iter_type.value_type
                    else
                        @val.__type = iter_type.key_type
                elseif iter_type\works_like_a(Types.ListType)
                    @index.__type = Types.Int if @index
                    @val.__type = iter_type.item_type
                elseif iter_type == Types.Range
                    @index.__type = Types.Int if @index
                    @val.__type = Types.Int
            assign_types @body
            assign_types @between if @between
            assign_types @filter if @filter

        when "Block"
            for stmt in *@
                assign_types stmt
            @__type = @[#@].__type

        when "Do"
            for block in *@
                assign_types block

            t = nil
            for block in *@
                return unless block.__type
                t = block.__type\orelse(t)

            -- TODO: some `do` blocks have no `stop` and thus
            -- may not be optional
            @__type = t == Types.NilType and t or Types.OptionalType(t)

        when "Negative"
            assign_types @value
            return unless @value.__type
            if @value.__type\works_like_a(Types.Range)
                @__type = @value.__type
            else
                node_assert @value.__type\is_numeric!, @value, "Not a valid type to negate: #{@value.__type}"
                @__type = @value.__type

        when "Not"
            assign_types @value
            return unless @value.__type
            node_assert @value.__type == Types.Bool or @value.__type == Types.OptionalType(Types.Bool), @value, "Invalid type for `not`: #{@value.__type}"
            @__type = Types.Bool

        when "Len"
            assign_types @value
            @__type = Types.Int

        when "Mod","AddSub","MulDiv","Pow"
            assign_types @lhs or @base
            assign_types @rhs or @exponent
            lhs_t, rhs_t = (@lhs or @base).__type, (@rhs or @exponent).__type
            return unless lhs_t and rhs_t

            if (lhs_t\is_numeric! and rhs_t\works_like_a(Types.MeasureType)) or (rhs_t\is_numeric! and lhs_t\works_like_a(Types.MeasureType))
                switch @op[0]
                    when "+","-"
                        node_assert lhs_t == rhs_t, @, "Operands have different units: #{lhs_t} vs. #{rhs_t}"
                        @__type = lhs_t
                    when "*"
                        if lhs_t\is_numeric! and not lhs_t\works_like_a(Types.MeasureType)
                            @__type = rhs_t
                        elseif rhs_t\is_numeric! and not rhs_t\works_like_a(Types.MeasureType)
                            @__type = lhs_t
                        else
                            @__type = Types.MeasureType(Measure(1,lhs_t.units)*Measure(1,rhs_t.units))\normalized!
                    when "/"
                        if lhs_t\is_numeric! and not lhs_t\works_like_a(Types.MeasureType)
                            @__type = rhs_t
                        elseif rhs_t\is_numeric! and not rhs_t\works_like_a(Types.MeasureType)
                            @__type = lhs_t
                        else
                            @__type = Types.MeasureType(Measure(1,lhs_t.units)/Measure(1,rhs_t.units))\normalized!
                    else
                        node_error @, "Operation is not permitted for unit types"
                return

            if lhs_t == rhs_t and lhs_t\is_numeric!
                @__type = lhs_t
                return

            if @__tag == "AddSub" and @op[0] == "+"
                if lhs_t\works_like_a(Types.String) and rhs_t == lhs_t
                    @__type = lhs_t
                    return
                elseif lhs_t\works_like_a(Types.ListType)
                    if rhs_t == lhs_t
                        @__type = lhs_t
                        return
                    elseif rhs_t\is_a(lhs_t.item_type)
                        @__type = lhs_t
                        return
                elseif rhs_t\works_like_a(Types.ListType) and lhs_t\is_a(rhs_t.item_type)
                    @__type = rhs_t
                    return

            node_error @, "Operands are not compatible: `#{lhs_t}` #{@op[0]} `#{rhs_t}`"

        when "ButWith","ButWithUpdate"
            assign_types @base
            for override in *@
                assign_types override
            @__type = @base.__type

        else
            for k,child in pairs @
                continue if type(child) != "table" or (type(k) == "string" and k\match("^__"))
                assign_types child
            @__type = Types.NilType

assign_all_types = (ast)->
    get_all = (field)->
        vals = {}
        recurse = =>
            vals[@] = @[field]
            for k,child in pairs @
                continue if type(child) != "table" or (type(k) == "string" and k\match("^__"))
                recurse child
        recurse ast
        return vals

    for decl in coroutine.wrap(-> each_tag(ast, "UnitDeclaration"))
        amount = assert tonumber((decl.measure.amount[0]\gsub("_",""))), "#{viz decl.measure}"
        units = decl.measure.units[0]\gsub("[<>]","")
        register_unit_alias decl.name[0], Measure(amount, units)

    for extern in coroutine.wrap(-> each_tag(ast, "Extern"))
        bind_var extern.__parent, extern.name
        extern.name.__register = "$"..extern.name[0]

    while true
        progress = false

        pre_types = get_all("__type")
        pre_decls = get_all("__declaration")

        bind_all_types ast
        bind_variables ast
        assign_types ast

        post_decls = get_all("__declaration")
        for k,v in pairs post_decls
            if pre_decls[k] != post_decls[k]
                progress = true
                break

        post_types = get_all("__type")
        for k,v in pairs post_types
            if pre_types[k] != post_types[k]
                progress = true
                break

        break unless progress

    -- for var in coroutine.wrap(-> each_tag(ast, "Var","TypeVar"))
    --     node_assert var.__declaration, var, "Couldn't determine what this variable refers to"

get_type = (ast, force)->
    if force
        assert ast.__type, "Couldn't get type"
        -- node_assert ast.__type, ast, "Couldn't get type"
    else
        ast.__type

return {
    :assign_all_types,
    :get_type,
    :parse_type,
    :bind_var,
    :bind_type,
}