Skip to content

Commit

Permalink
Start typechecking custom types and variants
Browse files Browse the repository at this point in the history
  • Loading branch information
dusty-phillips committed Sep 15, 2024
1 parent cefad6f commit a2a9914
Show file tree
Hide file tree
Showing 8 changed files with 322 additions and 17 deletions.
2 changes: 2 additions & 0 deletions src/glimpse/error.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,7 @@ pub type TypeCheckError {
right_got: String,
expected: String,
)
UnknownCustomType(name: String)
NoSuchModule(name: String)
DuplicateCustomType(name: String)
}
58 changes: 56 additions & 2 deletions src/glimpse/internal/typecheck.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import glimpse/internal/typecheck/environment.{
type Environment, type TypeCheckResult,
}
import glimpse/internal/typecheck/types.{type TypeResult}
import glimpse/internal/typecheck/variants.{type VariantField}
import pprint

pub fn fold_function_parameter(
Expand Down Expand Up @@ -36,6 +37,50 @@ pub fn fold_function_parameter(
}
}

pub fn fold_variant_constructors(
state: TypeCheckResult,
variant: glance.Variant,
) -> list.ContinueOrStop(TypeCheckResult) {
case state {
Error(error) -> list.Stop(Error(error))
Ok(environment.TypeState(environment, types.CustomType(custom_type))) ->
{
use variant_fields <- result.try(
variant.fields
|> list.map(variant_field(environment, _))
|> result.all,
)

Ok(environment.TypeState(
environment.add_variant_constructor(
environment,
variant.name,
variants.Variant(variant.name, custom_type, variant_fields),
),
types.CustomType(custom_type),
))
}
|> list.Continue
Ok(_) -> panic as "Variant custom type should only be custom type"
}
}

pub fn variant_field(
environment: Environment,
field: glance.Field(glance.Type),
) -> Result(VariantField, error.TypeCheckError) {
case field {
glance.Field(option.Some(field_name), glance_type) -> {
use field_type <- result.try(type_(environment, glance_type))
Ok(variants.NamedField(field_name, field_type))
}
glance.Field(option.None, glance_type) -> {
use field_type <- result.try(type_(environment, glance_type))
Ok(variants.PositionField(field_type))
}
}
}

pub fn block(
environment: Environment,
statements: List(glance.Statement),
Expand Down Expand Up @@ -108,7 +153,7 @@ pub fn expression(
glance.String(_) -> Ok(types.StringType)
glance.Variable("Nil") -> Ok(types.NilType)
glance.Variable("True") | glance.Variable("False") -> Ok(types.BoolType)
glance.Variable(name) -> environment.lookup_type(environment, name)
glance.Variable(name) -> environment.lookup_variable_type(environment, name)

glance.NegateInt(int_expr) -> {
case expression(environment, int_expr) {
Expand Down Expand Up @@ -248,7 +293,16 @@ pub fn type_(environment: Environment, glance_type: glance.Type) -> TypeResult {
glance.NamedType("Nil", option.None, []) -> Ok(types.NilType)
glance.NamedType("String", option.None, []) -> Ok(types.StringType)
glance.NamedType("Bool", option.None, []) -> Ok(types.BoolType)
glance.VariableType(name) -> environment.lookup_type(environment, name)

// 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, []) ->
environment.lookup_custom_type(environment, name)

glance.VariableType(name) ->
environment.lookup_variable_type(environment, name)
_ -> {
pprint.debug(glance_type)
todo
Expand Down
56 changes: 44 additions & 12 deletions src/glimpse/internal/typecheck/environment.gleam
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import gleam/dict
import gleam/result
import glimpse/error
import glimpse/internal/typecheck/types.{type Type}
import glimpse/internal/typecheck/types.{type Type, type TypeResult}
import glimpse/internal/typecheck/variants.{type Variant}

pub type Environment {
Environment(definitions: dict.Dict(String, Type))
Environment(
definitions: dict.Dict(String, Type),
custom_types: dict.Dict(String, Type),
constructors: dict.Dict(String, Variant),
)
}

pub type TypeState {
Expand All @@ -15,7 +20,11 @@ pub type TypeCheckResult =
Result(TypeState, error.TypeCheckError)

pub fn new() -> Environment {
Environment(definitions: dict.new())
Environment(
definitions: dict.new(),
custom_types: dict.new(),
constructors: dict.new(),
)
}

pub fn add_def(
Expand All @@ -24,23 +33,46 @@ pub fn add_def(
type_: Type,
) -> Environment {
Environment(
// ..environment,
..environment,
definitions: dict.insert(environment.definitions, name, type_),
)
}

pub fn lookup_type(
pub fn add_custom_type(environment: Environment, name: String) -> Environment {
Environment(
..environment,
custom_types: dict.insert(
environment.custom_types,
name,
types.CustomType(name),
),
)
}

pub fn add_variant_constructor(
environment: Environment,
name: String,
) -> Result(Type, error.TypeCheckError) {
dict.get(environment.definitions, name)
|> result.replace_error(error.InvalidName(name))
constructor: Variant,
) -> Environment {
Environment(
..environment,
constructors: dict.insert(environment.constructors, name, constructor),
)
}

pub fn lookup_type_out(
pub fn lookup_variable_type(
environment: Environment,
name: String,
) -> TypeCheckResult {
lookup_type(environment, name)
|> result.map(TypeState(environment, _))
) -> TypeResult {
dict.get(environment.definitions, name)
|> result.replace_error(error.InvalidName(name))
}

pub fn lookup_custom_type(environment: Environment, name: String) -> TypeResult {
dict.get(environment.custom_types, name)
|> result.replace_error(error.UnknownCustomType(name))
}

pub fn extract_env(state: TypeState) -> Environment {
state.environment
}
4 changes: 4 additions & 0 deletions src/glimpse/internal/typecheck/types.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub type Type {
FloatType
StringType
BoolType
CustomType(name: String)
}

pub type TypeResult =
Expand All @@ -20,6 +21,7 @@ pub fn to_string(type_: Type) -> String {
FloatType -> "Float"
StringType -> "String"
BoolType -> "Bool"
CustomType(name) -> name
}
}

Expand All @@ -30,6 +32,8 @@ pub fn to_glance(type_: Type) -> glance.Type {
FloatType -> glance.NamedType("Float", option.None, [])
StringType -> glance.NamedType("String", option.None, [])
BoolType -> glance.NamedType("Bool", option.None, [])
// TODO: CustomType will need a module field
CustomType(name) -> glance.NamedType(name, option.None, [])
}
}

Expand Down
10 changes: 10 additions & 0 deletions src/glimpse/internal/typecheck/variants.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import glimpse/internal/typecheck/types

pub type VariantField {
NamedField(name: String, type_: types.Type)
PositionField(type_: types.Type)
}

pub type Variant {
Variant(name: String, custom_type: String, fields: List(VariantField))
}
38 changes: 38 additions & 0 deletions src/glimpse/typecheck.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,44 @@ pub fn module(
let module_dict = dict.insert(package.modules, module_name, glimpse_module)
let package = glimpse.Package(..package, modules: module_dict)
Ok(package)
// TODO: Pretty sure this needs to also return an updated environment
// that contains the public types and functions of the module
}

/// Update the environment to include the custom type and all its constructors.
pub fn custom_type(
environment: Environment,
custom_type: glance.CustomType,
) -> Result(Environment, error.TypeCheckError) {
case environment.custom_types |> dict.get(custom_type.name) {
Ok(_) -> Error(error.DuplicateCustomType(custom_type.name))
Error(_) -> {
// add to env first so variants can parse recursive types
let environment =
environment |> environment.add_custom_type(custom_type.name)

list.fold_until(
custom_type.variants,
Ok(environment.TypeState(
environment,
types.CustomType(custom_type.name),
)),
intern.fold_variant_constructors,
)
|> result.map(environment.extract_env)
}
}
// Problem: where do we put the variant?
//
// probably a new constructors dict on the environment class
// that maps constructor name to CustomType(name)
// but it could also be in the definitions.
// We can't put both the type and the variant in the definitions, though
// because variants could lobber the type.
//
// Problem: do we need to do anything special with single variant custom
// types? If there is only one variant, we are allowed to look up record fields
// on the type.
}

/// Takes a glance function as input and returns the same function, but
Expand Down
Loading

0 comments on commit a2a9914

Please sign in to comment.