Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand return type properly in generic functions #451

Merged
merged 6 commits into from
Dec 28, 2021
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
95 changes: 57 additions & 38 deletions src/generics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,63 @@ use crate::instruction::TypeId;
use crate::{log, ErrKind, Error};
use crate::{Context, TypeCtx};

pub type GenericMap = HashMap<TypeId, TypeId>;
#[derive(Default)]
pub struct GenericMap {
map: HashMap<TypeId, TypeId>,
}

impl GenericMap {
/// Create a generic map from two sets of [`TypeId`]s: The set of generics to resolve,
/// and the set of resolved generics.
pub fn create(
generics: &[TypeId],
resolved: &[TypeId],
ctx: &mut TypeCtx,
) -> Result<GenericMap, Error> {
if generics.len() != resolved.len() {
// FIXME: Add better error message here printing both sets of generics
let err_msg = String::from("missing types in generic expansion");
return Err(Error::new(ErrKind::Generics).with_msg(err_msg));
}

let mut map = GenericMap::default();

let mut is_err = false;

generics.iter().zip(resolved).for_each(|(l_ty, r_ty)| {
log!("mapping generic: {} <- {}", l_ty, r_ty);
if let Err(e) = map.declare(l_ty.clone(), r_ty.clone()) {
ctx.error(e);
is_err = true;
}
});

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

/// Declare a new type "match" in the map. This function associates a generic type
/// with its resolved counterpart, and errors out otherwise
pub fn declare(&mut self, lty: TypeId, rty: TypeId) -> Result<(), Error> {
match self.map.insert(lty.clone(), rty.clone()) {
None => Ok(()),
Some(existing_ty) => Err(Error::new(ErrKind::Generics).with_msg(format!(
"mapping type to already mapped generic type: {} <- {} with {}",
lty, existing_ty, rty
))),
}
}

/// Get the type associated with a generic type in a previous call to `declare()`
pub fn get_match(&self, to_resolve: &TypeId) -> Result<TypeId, Error> {
self.map.get(to_resolve).cloned().ok_or_else(|| {
Error::new(ErrKind::Generics)
.with_msg(format!("undeclared generic type: {}", to_resolve))
})
}
}

/// Mangle a name to resolve it to its proper expanded name.
/// The format used is the following:
Expand All @@ -25,43 +81,6 @@ pub fn mangle(name: &str, types: &[TypeId]) -> String {
mangled
}

/// Create a generic map from two sets of [`TypeId`]s: The set of generics to resolve,
/// and the set of resolved generics.
pub fn create_map(
generics: &[TypeId],
resolved: &[TypeId],
ctx: &mut TypeCtx,
) -> Result<GenericMap, Error> {
if generics.len() != resolved.len() {
// FIXME: Add better error message here printing both sets of generics
let err_msg = String::from("missing types in generic expansion");
return Err(Error::new(ErrKind::Generics).with_msg(err_msg));
}

let mut map = GenericMap::new();

let mut is_err = false;

generics.iter().zip(resolved).for_each(|(l_ty, r_ty)| {
log!("mapping generic: {} <- {}", l_ty, r_ty);
match map.insert(l_ty.clone(), r_ty.clone()) {
None => {}
Some(existing_ty) => {
ctx.error(Error::new(ErrKind::Generics).with_msg(format!(
"mapping type to already mapped generic type: {} <- {} with {}",
l_ty, existing_ty, r_ty
)));
is_err = true;
}
}
});

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

/// 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
23 changes: 18 additions & 5 deletions src/instruction/function_call.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! FunctionCalls are used when calling a function. The argument list is given to the
//! function on execution.

use crate::generics::GenericMap;
use crate::instruction::{FunctionDec, FunctionKind, TypeId, Var};
use crate::typechecker::TypeCtx;
use crate::{
Expand Down Expand Up @@ -282,19 +283,31 @@ impl Generic for FunctionCall {
// caught already in an earlier pass
let dec = ctx.typechecker.get_function(&self.fn_name).unwrap().clone(); // FIXME: No clone
let type_map =
match generics::create_map(dec.generics(), &self.generics, &mut ctx.typechecker) {
match GenericMap::create(dec.generics(), &self.generics, &mut ctx.typechecker) {
Err(e) => {
ctx.error(e);
return;
}
Ok(m) => m,
};

let new_fn =
dec.from_type_map(generics::mangle(dec.name(), &self.generics), ctx, &type_map);
let mut new_fn =
match dec.from_type_map(generics::mangle(dec.name(), &self.generics), &type_map, ctx) {
Ok(f) => f,
Err(e) => {
ctx.error(e);
return;
}
};

// FIXME: No unwrap
ctx.add_function(new_fn).unwrap();
if let Err(e) = ctx.type_check(&mut new_fn) {
// FIXME: This should probably be a generic error instead
// FIXME: The name is also mangled and shouldn't be
ctx.error(e);
} else {
// FIXME: No unwrap
ctx.add_function(new_fn).unwrap();
}
}

fn resolve_self(&mut self, ctx: &mut TypeCtx) {
Expand Down
30 changes: 23 additions & 7 deletions src/instruction/function_declaration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,31 +51,47 @@ impl FunctionDec {
/// Generate a new instance of [`FunctionDec`] from a given generic type map
pub fn from_type_map(
&self,
name: String,
_ctx: &mut Context,
mangled_name: String,
type_map: &GenericMap,
) -> FunctionDec {
ctx: &mut Context,
) -> Result<FunctionDec, Error> {
let mut new_fn = self.clone();
new_fn.name = name;
new_fn.name = mangled_name;
new_fn.generics = vec![];

// FIXME: This does not change the return type?
let mut is_err = false;

new_fn
.args
.iter_mut()
.zip(self.args().iter())
.for_each(|(new_arg, old_generic)| {
new_arg.set_type(type_map.get(old_generic.get_type()).unwrap().clone())
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);
});

if let Some(ret_ty) = new_fn.ty {
let new_ret_ty = type_map.get_match(&ret_ty)?;
new_fn.ty = Some(new_ret_ty);
}

// FIXME: We also need to generate a new version of each instruction in the
// block
// if let Some(b) = &mut new_fn.block {
// b.resolve_self(ctx);
// }

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

pub fn generics(&self) -> &Vec<TypeId> {
Expand Down
4 changes: 4 additions & 0 deletions src/instruction/type_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ impl TypeId {
TypeId { id }
}

pub fn void() -> TypeId {
TypeId::new(String::from("void"))
}

pub fn id(&self) -> &str {
&self.id
}
Expand Down
10 changes: 10 additions & 0 deletions tests/ft/generics/generics.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,13 @@ tests:
args:
- "tests/ft/generics/valid_id.jk"
exit_code: 0
- name: "Undeclared return type"
binary: "target/debug/jinko"
args:
- "tests/ft/generics/undeclared_return_generic.jk"
exit_code: 1
- name: "Undeclared type in arguments"
binary: "target/debug/jinko"
args:
- "tests/ft/generics/undeclared_arg_generic.jk"
exit_code: 1