Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/generics.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@ builtin spelling of `assert_eq`; the emitter has no type inference of its own.
A request that arrives with no call node (a json or config decode target) is
still keyed by emission kinds, and a body requested both ways is emitted once.

A specialization is emitted in the module whose code instantiates it, which
is often not the module that declares it. Its body still resolves names as
its declaring module does: the emitter swaps in that module's imports while
it emits the body, so a call to a third module's generic (`gather(xs)` or
`b.gather(xs)`) lowers to that module's symbol. For this to work, the driver
gives each module's emission the imports of every generic module it depends
on. The checker also types the signature in the declaring module, so
`iter.map_iter` returns std.iter's `MapIter` even when the caller declares its
own `MapIter`.

For a generic method of a generic struct the record is the whole key: the
receiver instance's type arguments first, then the method's own. The symbol
keeps the two lists apart by putting them where each already lives — the
Expand Down
19 changes: 4 additions & 15 deletions docs/limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,10 @@ stale and a fix to it is welcome.
binding a local of the same name in one function is E266. `pith lint` reports
the shadowing as E308.
- **same-named types in different modules**. two modules can each declare a
struct, plain or generic, with the same name, and each value dispatches to its
own module's methods. a second impl block that declares a method the struct
already has is E263 (an interface impl may repeat an inherent method of the
same name, as std/io does). three shapes still resolve by the bare name:
- a generic struct and a plain struct with the same name in two modules: a
method call on either runs the body from whichever module is imported last,
with no diagnostic. give them distinct names.
- a generic function whose signature names a generic its own module declares
(`iter.map_iter` returning `MapIter`) resolves that name in the caller, so a
caller that declares its own `MapIter` gets a compile error.
- two modules that each declare the same generic enum fail to compile
(`unknown load source` from the backend).
struct or enum, plain or generic, with the same name, and each value
dispatches to its own module's methods. a second impl block that declares a
method the struct already has is E263 (an interface impl may repeat an
inherent method of the same name, as std/io does).
- **method syntax never reaches a free function**. there is no ufcs. a
primitive receiver resolves builtins only, so `(1.5).to_int()` is the
`Float` builtin even when a free `fn to_int` exists.
Expand All @@ -84,9 +76,6 @@ stale and a fix to it is welcome.
specialization is type checked against its concrete types, so a body that is
wrong for some type its bound admits is reported only when a caller reaches
that type. `pith check` names the specialization (`... in bad[String]`).
- **a generic function in another module, called from inside a generic body,
can fail to resolve at startup** (#1095). simple shapes work. a non-generic
wrapper in the calling module is the workaround.
- **`spawn` with a builtin call in an argument fails to compile** (#1082).
`spawn f(bit_and(x, y))` fails with `unknown load source`. bind the argument
to a local first.
Expand Down
94,403 changes: 48,404 additions & 45,999 deletions self-host/bootstrap/ir_driver.ir

Large diffs are not rendered by default.

108 changes: 102 additions & 6 deletions self-host/checker.pith
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,13 @@ mut ambiguous_generic_bases: Map[String, Bool] := {}
# the module each generic declaration was registered from, keyed by its AST
# node index, so an instantiation can name the declaration it came from.
mut generic_declaration_modules: Map[Int, String] := {}
# the plain (non-generic) struct and enum names each module declares, keyed
# "module<TAB>name", and the first module to declare each name. a module's
# own plain type shadows a generic of the same name from another module
# (resolve_generic_declaration_key), and a generic whose name another module
# gives a plain type is ambiguous (note_plain_type_declaration).
mut module_plain_type_names: Map[String, Bool] := {}
mut plain_type_first_module: Map[String, String] := {}
mut checking_test_decl := false
# impl set: "Type\tInterface" -> 1
mut implementation_set: Map[String, Int] := {}
Expand Down Expand Up @@ -231,6 +238,8 @@ pub fn initialize_checker(file_path: String):
generic_base_first_module = {}
ambiguous_generic_bases = {}
generic_declaration_modules = {}
module_plain_type_names = {}
plain_type_first_module = {}
checker_interfaces_reset()
implementation_set = {}
interface_decl_index = {}
Expand Down Expand Up @@ -603,7 +612,14 @@ fn resolve_named_type(name: String) -> Int:
return tid

fn resolve_generic_type_from_node(node: Node) -> Int:
base := resolve_imported_type_name(node.value)
mut base := resolve_imported_type_name(node.value)
# `alias.Box[T]` names the aliased module's generic, which a plain type
# this module declares under the bare name must not shadow
parts := split_qualified_name(node.value)
if parts.1 != "" and module_aliases.contains_key(parts.0):
qualified := module_generic_target_name(module_aliases[parts.0], parts.1)
if generic_declaration_map.contains_key(qualified):
base = qualified
mut type_args: List[Int] := []
for child in node.children:
type_args.push(resolve_type_implementation(child))
Expand Down Expand Up @@ -961,6 +977,7 @@ fn register_struct_declaration(node_idx: Int, node: Node):
mut empty_bools: List[Bool] := []
tid := add_type_info(ti_struct(base_name, empty_names, empty_ints, empty_bools, empty_bools))
register_module_type_name(base_name, tid)
note_plain_type_declaration(base_name)
define_binding_at(current_scope, base_name, tid, false, node_idx)
# non-generic: resolve field types immediately
mut field_names: List[String] := []
Expand Down Expand Up @@ -1030,6 +1047,7 @@ fn register_enum_declaration(node_idx: Int, node: Node):
variant_types.push(vtypes)
tid := add_type_info(ti_enum(base_name, variant_names, variant_types))
register_module_type_name(base_name, tid)
note_plain_type_declaration(base_name)
define_binding_at(current_scope, base_name, tid, false, node_idx)

# ---------------------------------------------------------------
Expand Down Expand Up @@ -1417,9 +1435,25 @@ fn register_generic_declaration(name: String, node_idx: Int):
ambiguous_generic_bases.insert(name, true)
else:
generic_base_first_module.insert(name, current_checker_module_path)
if plain_type_first_module.contains_key(name):
if plain_type_first_module[name] != current_checker_module_path:
ambiguous_generic_bases.insert(name, true)
if current_checker_module_path.len() > 0 and generic_declaration_map.contains_key(name) == false:
generic_declaration_map.insert(name, node_idx)

# record a plain struct or enum this module declares. a generic of the same
# name in another module makes that generic's bare name ambiguous, exactly as
# a second generic would: its instances, methods and impls are then keyed by
# its module's name, so the plain type's bare-name entries cannot answer for
# them.
fn note_plain_type_declaration(name: String):
module_plain_type_names.insert(current_checker_module_path + chr(9) + name, true)
if plain_type_first_module.contains_key(name) == false:
plain_type_first_module.insert(name, current_checker_module_path)
if generic_base_first_module.contains_key(name):
if generic_base_first_module[name] != current_checker_module_path:
ambiguous_generic_bases.insert(name, true)

# the name every table keys a generic base by. a generic declaration has no
# type id to key on — only its instances get one — so a struct's methods, its
# interface impls, its associated-type bindings and its instance names are all
Expand All @@ -1437,6 +1471,17 @@ pub fn generic_base_identity(module_path: String, name: String) -> String:
return key
return name

## true when the module at `module_path` ("" for the root file) itself
## declares a generic struct, enum or function called `name`.
pub fn module_declares_generic(module_path: String, name: String) -> Bool:
key := module_generic_target_name(module_path, name)
if generic_declaration_map.contains_key(key) == false:
return false
decl_idx := generic_declaration_map[key]
if generic_declaration_modules.contains_key(decl_idx) == false:
return false
return generic_declaration_modules[decl_idx] == module_path

# the identity of the generic declaration at this AST node index, resolved
# from the module it was registered in. instantiation names an instance from
# this, so two modules' `Box[Int]` become two differently named types with
Expand Down Expand Up @@ -1516,6 +1561,13 @@ fn resolve_generic_declaration_key(name: String) -> String:
current_key := module_generic_target_name(current_checker_module_path, name)
if generic_declaration_map.contains_key(current_key):
return current_key
# a plain struct or enum this module declares is what its bare name means
# here, even when another module declares a generic of the same name.
# without this, the module's impl on its own plain type registered as an
# impl on the foreign generic, and a method call on either value ran
# whichever body was registered last.
if module_plain_type_names.contains_key(current_checker_module_path + chr(9) + name):
return ""
if generic_declaration_map.contains_key(name):
return name
if current_checker_module_path.len() == 0:
Expand Down Expand Up @@ -1892,7 +1944,15 @@ fn check_implementation_declaration(node: Node):
if method_start == 2 and iface_type_base_name(first_type_node) == "Drop":
check_drop_implementation(node, concrete_name, method_start)

concrete_tid := lookup_type_id(concrete_name)
# the module's own declaration first: the global table holds whichever
# module registered the name last, which can be another module's plain
# struct while this impl is on this module's generic of the same name. a
# generic's bodies are checked per specialization, not here.
mut concrete_tid := resolve_type_name_module_scoped(concrete_name)
if concrete_tid < 0:
if has_generic_declaration(concrete_name):
return
concrete_tid = lookup_type_id(concrete_name)
if concrete_tid < 0:
return

Expand Down Expand Up @@ -4286,18 +4346,26 @@ fn check_generic_function_call_explicit(name: String, index_node: Node, arg_indi
report_error("E207", "expected " + param_type_nodes.len().to_string() + " arguments, got " + arg_types.len().to_string())
return TID_ERR

signature_scope := enter_signature_scope(decl_idx)
mut ret_type := TID_VOID
for child in decl_node.children:
cn := get_node(child)
if cn.kind == "returns":
ret_type = resolve_type_with_substitution_map(cn.children[0], generic_params, subst_args)
mut concrete_params: List[Int] := []
for ptn in param_type_nodes:
if ptn >= 0:
concrete_params.push(resolve_type_with_substitution_map(ptn, generic_params, subst_args))
else:
concrete_params.push(TID_ERR)
leave_signature_scope(signature_scope)

# same widening rule as the inferred path: a plain value satisfies an
# optional parameter once the explicit type args make it concrete.
mut vidx := 0
for ptn in param_type_nodes:
if ptn >= 0 and vidx < arg_types.len():
concrete := resolve_type_with_substitution_map(ptn, generic_params, subst_args)
concrete := concrete_params[vidx]
if is_error_type(concrete) == false and is_error_type(arg_types[vidx]) == false:
if arg_types[vidx] != concrete:
if argument_widens_to_optional(argument_expression_index(arg_indices[vidx]), arg_types[vidx], concrete) == false:
Expand Down Expand Up @@ -5323,12 +5391,22 @@ fn check_generic_function_call(name: String, arg_indices: List[Int], scope_id: I
return TID_ERR
record_generic_call_subst(subst_args)
queue_function_specialization(decl_idx, 0 - 1, subst_args)
# resolve return type with substitution
# resolve return type with substitution. the types come from the
# signature's own module, not the caller's: `MapIter[I, U]` in std.iter
# names std.iter's MapIter even when the caller declares one too.
signature_scope := enter_signature_scope(decl_idx)
mut ret_type := TID_VOID
for child in decl_node.children:
cn := get_node(child)
if cn.kind == "returns":
ret_type = resolve_type_with_substitution_map(cn.children[0], generic_params, subst_args)
mut concrete_params: List[Int] := []
for ptn in param_type_nodes:
if ptn >= 0:
concrete_params.push(resolve_type_with_substitution_map(ptn, generic_params, subst_args))
else:
concrete_params.push(TID_ERR)
leave_signature_scope(signature_scope)
# validate arg types against concrete param types. a plain value widens
# into an optional parameter here the same way it does on the non-generic
# paths — safe now that #148 made the specialization read the parameter's
Expand All @@ -5337,7 +5415,7 @@ fn check_generic_function_call(name: String, arg_indices: List[Int], scope_id: I
for ptn in param_type_nodes:
if ptn >= 0:
if vidx < arg_types.len():
concrete := resolve_type_with_substitution_map(ptn, generic_params, subst_args)
concrete := concrete_params[vidx]
if is_error_type(concrete) == false:
if is_error_type(arg_types[vidx]) == false:
if arg_types[vidx] != concrete:
Expand Down Expand Up @@ -7283,6 +7361,18 @@ fn check_or_pattern_match(pat: Node, expected_type: Int, scope_id: Int):
else:
check_pattern_match(alt, expected_type, scope_id)

# whether a pattern's written enum name means the generic base an instance
# was named from. the base is the bare name unless two modules declare it,
# and then it is the declaring module's key, which the written name reaches
# through the module's own generic declaration.
fn generic_pattern_names_base(type_name: String, base: String) -> Bool:
if base == type_name:
return true
decl_idx := get_generic_declaration_index(type_name)
if decl_idx < 0:
return false
return generic_declaration_identity(decl_idx) == base

fn check_variant_pattern_match(pat: Node, expected_type: Int, scope_id: Int):
# value: "EnumName.VariantName"
# children: sub-patterns for variant fields
Expand All @@ -7301,7 +7391,7 @@ fn check_variant_pattern_match(pat: Node, expected_type: Int, scope_id: Int):
# check below.
if expected_type >= 0:
einfo := get_type_info(expected_type)
if einfo.kind == "enum" and extract_base_type_name(einfo.name) == type_name:
if einfo.kind == "enum" and generic_pattern_names_base(type_name, extract_base_type_name(einfo.name)):
enum_tid = expected_type
if enum_tid < 0:
report_error("E202", "unknown type: " + type_name)
Expand Down Expand Up @@ -8464,6 +8554,8 @@ pub struct CheckerSnapshot:
pub generic_base_first_module: Map[String, String]
pub ambiguous_generic_bases: Map[String, Bool]
pub generic_declaration_modules: Map[Int, String]
pub module_plain_type_names: Map[String, Bool]
pub plain_type_first_module: Map[String, String]
pub checking_test_decl: Bool
pub implementation_set: Map[String, Int]
pub interface_decl_index: Map[String, Int]
Expand Down Expand Up @@ -8529,6 +8621,8 @@ pub fn take_checker_snapshot() -> CheckerSnapshot:
generic_base_first_module: copy_map(generic_base_first_module),
ambiguous_generic_bases: copy_map(ambiguous_generic_bases),
generic_declaration_modules: copy_map(generic_declaration_modules),
module_plain_type_names: copy_map(module_plain_type_names),
plain_type_first_module: copy_map(plain_type_first_module),
checking_test_decl: checking_test_decl,
implementation_set: copy_map(implementation_set),
interface_decl_index: copy_map(interface_decl_index),
Expand Down Expand Up @@ -8594,6 +8688,8 @@ pub fn restore_checker_snapshot(s: CheckerSnapshot):
generic_base_first_module = copy_map(s.generic_base_first_module)
ambiguous_generic_bases = copy_map(s.ambiguous_generic_bases)
generic_declaration_modules = copy_map(s.generic_declaration_modules)
module_plain_type_names = copy_map(s.module_plain_type_names)
plain_type_first_module = copy_map(s.plain_type_first_module)
checking_test_decl = s.checking_test_decl
implementation_set = copy_map(s.implementation_set)
interface_decl_index = copy_map(s.interface_decl_index)
Expand Down
Loading
Loading