Skip to content

Commit

Permalink
Merge pull request #540 from jinko-core/generic-type-dec
Browse files Browse the repository at this point in the history
Generic type declarations
  • Loading branch information
CohenArthur authored Mar 19, 2022
2 parents 2c2456f + aa183da commit 10149dc
Show file tree
Hide file tree
Showing 19 changed files with 319 additions and 84 deletions.
31 changes: 29 additions & 2 deletions src/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ use std::path::PathBuf;
#[cfg(feature = "ffi")]
use crate::ffi;
use crate::instance::{FromObjectInstance, ToObjectInstance};
use crate::{Context, Instruction, JkBool, JkChar, JkFloat, JkInt, JkString, ObjectInstance};
use crate::{
generics, log, Context, Instruction, JkBool, JkChar, JkFloat, JkInt, JkString, ObjectInstance,
};

type Args = Vec<Box<dyn Instruction>>;
type BuiltinFn = fn(&mut Context, Args) -> Option<ObjectInstance>;
Expand Down Expand Up @@ -141,6 +143,23 @@ fn fmt_float(ctx: &mut Context, args: Args) -> Option<ObjectInstance> {
Some(JkString::from(value.to_string()).to_instance())
}

fn size_of(ctx: &mut Context, args: Args) -> Option<ObjectInstance> {
let instance = args[0].execute(ctx).unwrap();

log!("called size_of");

Some(JkInt::from(instance.size() as i64).to_instance())
}

fn type_of(ctx: &mut Context, args: Args) -> Option<ObjectInstance> {
let instance = args[0].execute(ctx).unwrap();
let instance_ty = instance.ty().to_string();

log!("called type_of");

Some(JkString::from(instance_ty).to_instance())
}

impl Builtins {
fn add(&mut self, name: &'static str, builtin_fn: BuiltinFn) {
self.functions.insert(String::from(name), builtin_fn);
Expand All @@ -164,16 +183,24 @@ impl Builtins {
builtins.add("__builtin_arg_get", arg_get);
builtins.add("__builtin_arg_amount", arg_amount);
builtins.add("__builtin_exit", exit);
builtins.add("size_of", size_of);
builtins.add("type_of", type_of);

builtins
}

pub fn contains(&self, name: &str) -> bool {
// We can demangle builtins to dispatch to our single, non generic
// implementation.
let name = generics::original_name(name);

log!("checking if builtin is present: {}", name);

self.functions.contains_key(name)
}

pub fn get(&self, builtin: &str) -> Option<&BuiltinFn> {
self.functions.get(builtin)
self.functions.get(generics::original_name(builtin))
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ use std::rc::Rc;

use crate::error::{ErrKind, Error, ErrorHandler};
use crate::instruction::{Block, FunctionDec, FunctionKind, Instruction, TypeDec, Var};
use crate::parser;
use crate::typechecker::{SpecializedNode, TypeCheck, TypeCtx, TypeId};
use crate::ObjectInstance;
use crate::{log, parser};
use crate::{Builtins, CheckedType};

/// Type the context uses for keys
Expand Down Expand Up @@ -298,6 +298,8 @@ impl Context {
fn inner_check(&mut self, ep: &mut Block) -> Result<(), Error> {
self.scope_enter();

log!("starting first pass of typechecking");

ep.type_of(&mut self.typechecker);

self.error_handler
Expand Down
33 changes: 33 additions & 0 deletions src/generics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,31 @@ pub fn mangle(name: &str, types: &[TypeId]) -> String {
mangled
}

/// Performs the opposite conversion, turning a mangled name into a valid
/// jinko function name with generics.
pub fn demangle(_mangled_name: &str) -> String {
todo!()
}

/// Fetch the original name contained in a mangled name. This is useful for
/// generic builtins, which only have one implementation despite being able
/// to handle multiple types.
///
/// ```rust
/// use jinko::generics::original_name;
///
/// let mangled = "type_of+int";
/// let original_builtin_name = original_name(mangled);
///
/// assert_eq!(original_builtin_name, "type_of");
/// ```
pub fn original_name(mangled_name: &str) -> &str {
match mangled_name.find('+') {
None => mangled_name,
Some(first_separator) => &mangled_name[..first_separator],
}
}

/// Since most of the instructions cannot do generic expansion, we can implement
/// default methods which do nothing. This avoid more boilerplate code for instructions
/// such as constants or variables which cannot be generic.
Expand Down Expand Up @@ -138,6 +163,14 @@ mod tests {
);
}

#[test]
fn get_original_name_back() {
assert_eq!(
original_name(&mangle("og_fn", &[ty!("float"), ty!("ComplexType")])),
"og_fn"
);
}

#[test]
fn create_map_different_size() {
let mut ctx = TypeCtx::new();
Expand Down
9 changes: 5 additions & 4 deletions src/instruction/field_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,13 +105,14 @@ impl TypeCheck for FieldAccess {

// We can unwrap here since the type that was resolved from the instance HAS
// to exist. If it does not, this is an interpreter error
let (_, fields_ty) = ctx.get_custom_type(instance_ty_name).unwrap();
let dec = ctx.get_custom_type(instance_ty_name).unwrap();

match fields_ty
match dec
.fields()
.iter()
.find(|(field_name, _)| *field_name == self.field_name)
.find(|dec_arg| dec_arg.name() == self.field_name)
{
Some((_, field_ty)) => field_ty.clone(),
Some(dec_arg) => CheckedType::Resolved(dec_arg.get_type().clone()),
None => {
ctx.error(
Error::new(ErrKind::TypeChecker)
Expand Down
7 changes: 7 additions & 0 deletions src/instruction/function_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,10 @@ impl Instruction for FunctionCall {
}

impl TypeCheck for FunctionCall {
fn type_log(&self) -> String {
self.fn_name.to_string()
}

fn resolve_type(&mut self, ctx: &mut TypeCtx) -> CheckedType {
log!("typechecking call to {}", self.fn_name);

Expand Down Expand Up @@ -455,6 +459,9 @@ impl Generic for FunctionCall {
})
.collect();

// FIXME: Do we need this?
// self.args.iter_mut().for_each(|arg| arg.resolve_self(ctx));

self.fn_name = generic_name;
self.generics = vec![];
match dec.ty() {
Expand Down
61 changes: 44 additions & 17 deletions src/instruction/type_declaration.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use super::{DecArg, InstrKind, Instruction};

use crate::{
generics::GenericMap,
log,
typechecker::{CheckedType, TypeCtx, TypeId},
Context, Generic, ObjectInstance, SpanTuple, TypeCheck,
Context, ErrKind, Error, Generic, ObjectInstance, SpanTuple, TypeCheck,
};

#[derive(Clone, Debug, PartialEq)]
Expand All @@ -27,6 +28,42 @@ impl TypeDec {
}
}

/// Generate a new instance of [`TypeDec`] from a given generic type map
pub fn from_type_map(
&self,
mangled_name: String,
type_map: &GenericMap,
ctx: &mut TypeCtx,
) -> Result<TypeDec, Error> {
let mut new_type = self.clone();
new_type.name = mangled_name;
new_type.generics = vec![];

let mut is_err = false;

new_type
.fields
.iter_mut()
.zip(self.fields().iter())
.filter(|(_, generic)| self.generics().contains(generic.get_type()))
.for_each(|(new_arg, old_generic)| {
let new_type = match type_map.get_match(old_generic.get_type()) {
Ok(t) => t,
Err(e) => {
ctx.error(e);
is_err = true;
TypeId::void()
}
};
new_arg.set_type(new_type);
});

match is_err {
true => Err(Error::new(ErrKind::Generics)),
false => Ok(new_type),
}
}

/// Get a reference to the name of the type
pub fn name(&self) -> &str {
&self.name
Expand All @@ -37,6 +74,11 @@ impl TypeDec {
&self.fields
}

/// Get a reference to the type's generics
pub fn generics(&self) -> &Vec<TypeId> {
&self.generics
}

pub fn set_location(&mut self, location: SpanTuple) {
self.location = Some(location)
}
Expand Down Expand Up @@ -101,22 +143,7 @@ impl Instruction for TypeDec {

impl TypeCheck for TypeDec {
fn resolve_type(&mut self, ctx: &mut TypeCtx) -> CheckedType {
// TODO: FunctionDecs and TypeDec are very similar. Should we factor them together?
let fields_ty = self
.fields
.iter()
.map(|dec_arg| {
(
dec_arg.name().to_string(),
CheckedType::Resolved(dec_arg.get_type().clone()),
)
})
.collect();
if let Err(e) = ctx.declare_custom_type(
self.name.clone(),
CheckedType::Resolved(TypeId::from(self.name())),
fields_ty,
) {
if let Err(e) = ctx.declare_custom_type(self.name.clone(), self.clone()) {
ctx.error(e);
}

Expand Down
66 changes: 57 additions & 9 deletions src/instruction/type_instantiation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
//! type on execution.
use super::{Context, ErrKind, Error, InstrKind, Instruction, ObjectInstance, TypeDec, VarAssign};
use crate::generics::{self, GenericMap};
use crate::instance::Name;
use crate::typechecker::TypeCtx;
use crate::symbol::Symbol;
use crate::typechecker::{SpecializedNode, TypeCtx};
use crate::{log, Generic, SpanTuple};
use crate::{typechecker::CheckedType, TypeCheck, TypeId};
use crate::{Generic, SpanTuple};

use std::rc::Rc;

Expand Down Expand Up @@ -84,6 +86,47 @@ impl TypeInstantiation {
pub fn set_location(&mut self, location: SpanTuple) {
self.location = Some(location)
}

pub fn resolve_generic_instantiation(
&mut self,
dec: TypeDec,
ctx: &mut TypeCtx,
) -> CheckedType {
log!(
"creating specialized type. type generics: {}, instantiation generics {}",
dec.generics().len(),
self.generics.len()
);
let type_map = match GenericMap::create(dec.generics(), &self.generics, ctx) {
Ok(map) => map,
Err(e) => {
ctx.error(e.with_loc(self.location.clone()));
return CheckedType::Error;
}
};

let specialized_name = generics::mangle(dec.name(), &self.generics);
log!("specialized name {}", specialized_name);
if ctx.get_custom_type(&specialized_name).is_none() {
// FIXME: Remove this clone once we have proper symbols
let specialized_ty = match dec.from_type_map(specialized_name.clone(), &type_map, ctx) {
Ok(f) => f,
Err(e) => {
ctx.error(e.with_loc(self.location.clone()));
return CheckedType::Error;
}
};

ctx.add_specialized_node(SpecializedNode::Type(specialized_ty));
}

self.type_name = TypeId::new(Symbol::from(specialized_name));
self.generics = vec![];

// Recursively resolve the type of self now that we changed the
// function to call
self.type_of(ctx)
}
}

impl Instruction for TypeInstantiation {
Expand Down Expand Up @@ -143,8 +186,8 @@ impl Instruction for TypeInstantiation {

impl TypeCheck for TypeInstantiation {
fn resolve_type(&mut self, ctx: &mut TypeCtx) -> CheckedType {
let (_, fields_ty) = match ctx.get_custom_type(self.type_name.id()) {
Some(ty) => ty,
let dec = match ctx.get_custom_type(self.type_name.id()) {
Some(ty) => ty.clone(),
None => {
ctx.error(
Error::new(ErrKind::TypeChecker)
Expand All @@ -159,17 +202,22 @@ impl TypeCheck for TypeInstantiation {
}
};

let fields_ty = fields_ty.clone();
if !dec.generics().is_empty() || !self.generics.is_empty() {
log!("resolving generic type instantiation");
return self.resolve_generic_instantiation(dec, ctx);
}

let mut errors = vec![];
for ((_, field_ty), var_assign) in fields_ty.iter().zip(self.fields.iter_mut()) {
for (field_dec, var_assign) in dec.fields().iter().zip(self.fields.iter_mut()) {
let expected_ty = CheckedType::Resolved(field_dec.get_type().clone());
let value_ty = var_assign.value_mut().type_of(ctx);
if field_ty != &value_ty {
if expected_ty != value_ty {
errors.push(
Error::new(ErrKind::TypeChecker)
.with_msg(format!(
"trying to assign value of type `{}` to field of type `{}`",
value_ty, field_ty
value_ty,
field_dec.get_type()
))
.with_loc(var_assign.location().cloned()),
);
Expand Down Expand Up @@ -197,7 +245,7 @@ impl Generic for TypeInstantiation {}
#[cfg(test)]
mod test {
use super::*;
use crate::{jinko_fail, span, symbol::Symbol};
use crate::{jinko_fail, symbol::Symbol};

#[test]
fn t_fields_number() {
Expand Down
4 changes: 4 additions & 0 deletions src/instruction/var.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ impl Instruction for Var {
}

impl TypeCheck for Var {
fn type_log(&self) -> String {
self.name.to_string()
}

fn resolve_type(&mut self, ctx: &mut TypeCtx) -> CheckedType {
match ctx.get_var(self.name()) {
Some(var_ty) => var_ty.clone(),
Expand Down
Loading

0 comments on commit 10149dc

Please sign in to comment.