Skip to content

Commit

Permalink
Support importing and returning custom types from modules
Browse files Browse the repository at this point in the history
Note: If you are reading this because you ran a git blame and this
came up, please know that I had a bad day today and I'm not
thinking clearly.

I relied exclusively on gleam's compiler and unit tests to tell
me I'd done a good job instead of actually reasoning about it.

If you aren't reading this, it means that the compiler is awesome
and the code turned out ok anyway.
  • Loading branch information
dusty-phillips committed Sep 18, 2024
1 parent 15142a6 commit 5a8bef5
Show file tree
Hide file tree
Showing 5 changed files with 90 additions and 56 deletions.
4 changes: 2 additions & 2 deletions src/glimpse/internal/typecheck.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ pub fn expression(
container,
))
case container_expression_type {
types.NamespaceType(nested_env) ->
nested_env
types.NamespaceType(nested_defs, _nested_types) ->
nested_defs
|> dict.get(label)
|> result.replace_error(error.InvalidName(label))
type_ ->
Expand Down
10 changes: 7 additions & 3 deletions src/glimpse/internal/typecheck/imports.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,13 @@ pub fn fold_import_from_env(
namespace,
types.NamespaceType(
module_env.definitions
|> dict.filter(fn(key, _) {
set.contains(module_env.public_definitions, key)
}),
|> dict.filter(fn(key, _) {
set.contains(module_env.public_definitions, key)
}),
module_env.custom_types
|> dict.filter(fn(key, _) {
set.contains(module_env.public_custom_types, key)
}),
),
)
|> types.add_import_mapping_to_env(module, namespace)
Expand Down
44 changes: 38 additions & 6 deletions src/glimpse/internal/typecheck/types.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ pub type Type {
return: Type,
)
/// Used for field access on imports; no direct glance analog
NamespaceType(Dict(String, Type))
NamespaceType(
definitions: Dict(String, Type),
custom_types: Dict(String, Type),
)
}

pub type TypeResult =
Expand All @@ -37,6 +40,7 @@ pub type Environment {
definitions: dict.Dict(String, Type),
public_definitions: set.Set(String),
custom_types: dict.Dict(String, Type),
public_custom_types: set.Set(String),
// absolute path to whatever the relative name is in this env
import_names: dict.Dict(String, String),
// other environments that could be imported from this one
Expand Down Expand Up @@ -76,6 +80,7 @@ pub fn new_env(current_module: String) -> Environment {
definitions: dict.new(),
public_definitions: set.new(),
custom_types: dict.new(),
public_custom_types: set.new(),
import_names: dict.new(),
module_environments: dict.new(),
)
Expand All @@ -101,6 +106,18 @@ pub fn publish_def_in_env(environment: Environment, name: String) -> Environment
)
}

/// publish a custom_type that is already in the custom_types so that it is
/// visible to importing modules
pub fn publish_custom_type_in_env(
environment: Environment,
name: String,
) -> Environment {
Environment(
..environment,
public_custom_types: set.insert(environment.public_custom_types, name),
)
}

/// Adds the custom_type to the environment, assuming that the current module
//

Expand Down Expand Up @@ -161,9 +178,17 @@ pub fn type_(environment: Environment, glance_type: glance.Type) -> TypeResult {
// TODO: custom types with parameters need to be supported
// TODO: not 100% certain all named types that are not covered
// above are actually custom types
// TODO: support named types in other modules
glance.NamedType(name, option.None, []) ->
lookup_custom_type(environment, name)
glance.NamedType(name, option.Some(module), []) -> {
let namespace = environment.definitions |> dict.get(module)
case namespace {
Ok(NamespaceType(_, custom_types)) ->
dict.get(custom_types, name)
|> result.replace_error(error.InvalidFieldAccess(module, name))
_ -> Error(error.InvalidFieldAccess(module, name))
}
}

glance.VariableType(name) -> lookup_variable_type(environment, name)
_ -> {
Expand All @@ -180,14 +205,14 @@ pub fn to_string(environment: Environment, type_: Type) -> String {
FloatType -> "Float"
StringType -> "String"
BoolType -> "Bool"
CustomType(_module, name) -> name
CustomType(module, name) -> module <> "." <> name
CallableType(parameters, _labels, return) ->
string_builder.from_string("fn (")
|> string_builder.append(list_to_string(parameters, environment))
|> string_builder.append(") -> ")
|> string_builder.append(to_string(environment, return))
|> string_builder.to_string
NamespaceType(_) -> "<Namespace>"
NamespaceType(..) -> "<Namespace>"
}
}

Expand All @@ -208,13 +233,20 @@ pub fn to_glance(environment: Environment, type_: Type) -> glance.Type {
FloatType -> glance.NamedType("Float", option.None, [])
StringType -> glance.NamedType("String", option.None, [])
BoolType -> glance.NamedType("Bool", option.None, [])
CustomType(_module, name) -> glance.NamedType(name, option.None, [])
CustomType(module, name) -> {
case dict.get(environment.import_names, module) {
Ok(relative) if module == environment.current_module ->
glance.NamedType(name, option.None, [])
Ok(relative) -> glance.NamedType(name, option.Some(relative), [])
Error(_) -> panic as "Custom type should always have a valid module"
}
}
CallableType(parameters, _labels, return) ->
glance.FunctionType(
list.map(parameters, to_glance(environment, _)),
to_glance(environment, return),
)
NamespaceType(_) -> panic as "Cannot convert namespace to glance"
NamespaceType(..) -> panic as "Cannot convert namespace to glance"
}
}

Expand Down
9 changes: 8 additions & 1 deletion src/glimpse/typecheck.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,14 @@ pub fn custom_type(
Error(_) -> {
// add to env first so variants can parse recursive types
let environment =
environment |> types.add_custom_type_to_env(custom_type.name)
environment
|> types.add_custom_type_to_env(custom_type.name)

let environment = case custom_type.publicity {
glance.Public ->
types.publish_custom_type_in_env(environment, custom_type.name)
glance.Private -> environment
}

list.fold_until(
custom_type.variants,
Expand Down
79 changes: 35 additions & 44 deletions test/typecheck/imports_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,10 @@ pub fn import_adds_function_to_env_test() {
|> assertions.should_have_dict_size(1)
|> dict.get("foo")
|> should.be_ok
|> should.equal(
types.NamespaceType(
dict.from_list([
#("bar", types.CallableType([], dict.new(), types.NilType)),
]),
),
)
|> should.equal(types.NamespaceType(
dict.from_list([#("bar", types.CallableType([], dict.new(), types.NilType))]),
dict.new(),
))

main_env.import_names
|> assertions.should_have_dict_size(1)
Expand All @@ -53,7 +50,7 @@ pub fn import_no_add_private_function_to_env_test() {
|> assertions.should_have_dict_size(1)
|> dict.get("foo")
|> should.be_ok
|> should.equal(types.NamespaceType(dict.new()))
|> should.equal(types.NamespaceType(dict.new(), dict.new()))

main_env.import_names
|> assertions.should_have_dict_size(1)
Expand All @@ -77,20 +74,19 @@ pub fn import_adds_variant_to_env_test() {
|> assertions.should_have_dict_size(1)
|> dict.get("foo")
|> should.be_ok
|> should.equal(
types.NamespaceType(
dict.from_list([
#(
"Foo",
types.CallableType(
[],
dict.new(),
types.CustomType("main_module", "Foo"),
),
|> should.equal(types.NamespaceType(
dict.from_list([
#(
"Foo",
types.CallableType(
[],
dict.new(),
types.CustomType("main_module", "Foo"),
),
]),
),
)
),
]),
dict.from_list([#("Foo", types.CustomType("main_module", "Foo"))]),
))

main_env.import_names
|> assertions.should_have_dict_size(1)
Expand All @@ -114,7 +110,7 @@ pub fn import_no_add_private_variant_to_env_test() {
|> assertions.should_have_dict_size(1)
|> dict.get("foo")
|> should.be_ok
|> should.equal(types.NamespaceType(dict.from_list([])))
|> should.equal(types.NamespaceType(dict.from_list([]), dict.new()))

main_env.import_names
|> assertions.should_have_dict_size(1)
Expand Down Expand Up @@ -143,13 +139,10 @@ pub fn import_call_function_field_access_test() {
|> assertions.should_have_dict_size(2)
|> dict.get("foo")
|> should.be_ok
|> should.equal(
types.NamespaceType(
dict.from_list([
#("bar", types.CallableType([], dict.new(), types.NilType)),
]),
),
)
|> should.equal(types.NamespaceType(
dict.from_list([#("bar", types.CallableType([], dict.new(), types.NilType))]),
dict.new(),
))

main_env.import_names
|> assertions.should_have_dict_size(1)
Expand All @@ -165,9 +158,8 @@ pub fn variant_call_function_field_access_test() {
let #(_, main_env) =
glance.module(
"import foo
pub fn main() -> Nil {
pub fn main() -> foo.Foo {
foo.Foo()
Nil
}",
)
|> should.be_ok
Expand All @@ -179,20 +171,19 @@ pub fn variant_call_function_field_access_test() {
|> assertions.should_have_dict_size(2)
|> dict.get("foo")
|> should.be_ok
|> should.equal(
types.NamespaceType(
dict.from_list([
#(
"Foo",
types.CallableType(
[],
dict.new(),
types.CustomType("main_module", "Foo"),
),
|> should.equal(types.NamespaceType(
dict.from_list([
#(
"Foo",
types.CallableType(
[],
dict.new(),
types.CustomType("main_module", "Foo"),
),
]),
),
)
),
]),
dict.from_list([#("Foo", types.CustomType("main_module", "Foo"))]),
))

main_env.import_names
|> assertions.should_have_dict_size(1)
Expand Down

0 comments on commit 5a8bef5

Please sign in to comment.