got generics working

This commit is contained in:
2022-10-10 17:13:17 -06:00
parent 4c1c13149d
commit c4be846c1d
6 changed files with 166 additions and 55 deletions

View File

@@ -53,6 +53,10 @@ pub enum TypingError {
},
#[error("invalid use of alias")]
InvalidUseofAlias,
#[error("alias cannot have type parameters")]
InvalidTypeParameterOnAlias {
alias: ast::Identifier,
},
#[error("type cannot be used for generic")]
InvalidTypeForGeneric,
#[error("multiple errors")]

View File

@@ -90,10 +90,10 @@ pub LiteralStruct: ast::LiteralStruct = {
},
None => {
ast::LiteralStruct{
type_parameters: ast::GenericUsage::Unknown,
type_parameters: ast::GenericUsage::new(&[]),
name: i.clone(),
fields: field_list,
type_: ast::TypeUsage::new_named(&i, &ast::GenericUsage::Unknown),
type_: ast::TypeUsage::new_named(&i, &ast::GenericUsage::new(&[])),
}
}
}
@@ -207,13 +207,26 @@ pub Block: ast::Block = {
}
};
pub NamedTypeUsage: ast::NamedTypeUsage = {
pub PartialNamedTypeUsage: ast::NamedTypeUsage = {
<n:SpannedIdentifier> <gu:GenericUsage?> => match gu {
Some(tp) => ast::NamedTypeUsage{type_parameters: tp, name: n},
None => ast::NamedTypeUsage{type_parameters: ast::GenericUsage::Unknown, name: n},
},
};
pub NamedTypeUsage: ast::NamedTypeUsage = {
<n:SpannedIdentifier> <gu:GenericUsage?> => match gu {
Some(tp) => ast::NamedTypeUsage{type_parameters: tp, name: n},
None => ast::NamedTypeUsage{type_parameters: ast::GenericUsage::new(&[]), name: n},
},
};
pub PartialTypeUsage: ast::TypeUsage = {
<n:PartialNamedTypeUsage> => ast::TypeUsage::Named(n),
"fn" "(" <args:Comma<PartialTypeUsage>> ")" => ast::TypeUsage::Function(ast::FunctionTypeUsage{arguments: args, return_type: Box::new(ast::new_unit())}),
"fn" "(" <args:Comma<PartialTypeUsage>> ")" ":" <rt:PartialTypeUsage> => ast::TypeUsage::Function(ast::FunctionTypeUsage{arguments: args, return_type: Box::new(rt)}),
};
pub TypeUsage: ast::TypeUsage = {
<n:NamedTypeUsage> => ast::TypeUsage::Named(n),
"fn" "(" <args:Comma<TypeUsage>> ")" => ast::TypeUsage::Function(ast::FunctionTypeUsage{arguments: args, return_type: Box::new(ast::new_unit())}),

View File

@@ -8,7 +8,7 @@ struct Context {
pub type_aliases: Vec<ast::AliasTypeDeclaration>,
}
fn resolve_type(ctx: &Context, type_: &ast::NamedTypeUsage) -> ast::TypeUsage {
fn resolve_type(ctx: &Context, type_: &ast::NamedTypeUsage) -> Result<ast::TypeUsage> {
let mut changed = true;
let mut result = ast::TypeUsage::Named(type_.clone());
while changed {
@@ -17,7 +17,7 @@ fn resolve_type(ctx: &Context, type_: &ast::NamedTypeUsage) -> ast::TypeUsage {
match current {
ast::TypeUsage::Named(named) => {
for alias in ctx.type_aliases.iter() {
if named.name.name.value == alias.name.name.value {
if named.name.name.value == alias.name.name.value { // is alias, replace
changed = true;
result = alias.replaces.clone();
}
@@ -26,13 +26,42 @@ fn resolve_type(ctx: &Context, type_: &ast::NamedTypeUsage) -> ast::TypeUsage {
_ => break,
}
}
return result;
match &result {
ast::TypeUsage::Named(named) => {
match &named.type_parameters {
ast::GenericUsage::Known(known) => {
let mut result_params = vec!();
for param in known.parameters.iter() {
result_params.push(process_type(ctx, param)?);
}
let mut new_named = named.clone();
new_named.type_parameters = ast::GenericUsage::new(&result_params);
result = ast::TypeUsage::Named(new_named);
},
_ => {}
}
},
ast::TypeUsage::Function(func) => {
match &type_.type_parameters {
ast::GenericUsage::Known(known) => {
if known.parameters.len() > 0 {
return Err(errors::TypingError::InvalidTypeParameterOnAlias{alias: type_.name.clone()});
}
},
_ => {} //skip
}
},
_ => {
panic!("alias of a non-type, not possible");
}
}
return Ok(result);
}
fn process_type(ctx: &Context, type_: &ast::TypeUsage) -> Result<ast::TypeUsage> {
match type_ {
ast::TypeUsage::Named(named) => {
return Ok(resolve_type(ctx, named));
return Ok(resolve_type(ctx, named)?);
}
ast::TypeUsage::Function(function) => {
let mut arguments = vec!();
@@ -50,7 +79,7 @@ fn process_type(ctx: &Context, type_: &ast::TypeUsage) -> Result<ast::TypeUsage>
ast::TypeUsage::Namespace(namespace) => {
match namespace {
ast::NamespaceTypeUsage::Type(named_type)=> {
let result = resolve_type(ctx, named_type);
let result = resolve_type(ctx, named_type)?;
match result {
ast::TypeUsage::Named(named) => {
return Ok(ast::TypeUsage::Namespace(ast::NamespaceTypeUsage::Type(named)));
@@ -286,7 +315,7 @@ impl TypeAliasResolver {
type_parameters: literal_struct.type_parameters.clone(),
name: literal_struct.name.clone(),
},
);
)?;
let new_name = match &result {
ast::TypeUsage::Named(named) => named.name.clone(),
_ => panic!("LiteralStruct resolved to non-named-type"),

View File

@@ -29,12 +29,8 @@ fn type_meets_trait_bounds(ctx: &Context, type_: &ast::TypeUsage, bounds: &Vec<a
};
match &ctx.environment[&named.name.name.value] {
NamedEntity::NamedType(named_type) => {
println!("env value: {:?}", &ctx.environment[&named.name.name.value]);
let mut found = false;
for impl_ in named_type.impls.iter() {
println!("for: {:?}", &named.name.name.value);
println!("bounds: {:?}", &bound.name.value);
println!("trait: {:?}", &impl_.trait_);
if impl_.trait_ == Some(bound.name.value.to_string()) {
found = true;
break;
@@ -55,20 +51,37 @@ fn type_meets_trait_bounds(ctx: &Context, type_: &ast::TypeUsage, bounds: &Vec<a
fn replace_generic_with_concrete(replace: &ast::Identifier, with_type: &ast::TypeUsage, in_type: &ast::TypeUsage) -> ast::TypeUsage {
match in_type {
ast::TypeUsage::Named(named) => {
let mut result = named.clone();
if named.name.name.value == replace.name.value {
return with_type.clone();
}
return in_type.clone();
result.type_parameters = match &named.type_parameters {
ast::GenericUsage::Known(known) => {
let mut param_result = vec!();
for param in known.parameters.iter() {
param_result.push(replace_generic_with_concrete(replace, with_type, param));
}
ast::GenericUsage::new(&param_result)
},
ast::GenericUsage::Unknown => {
ast::GenericUsage::Unknown
},
};
return ast::TypeUsage::Named(result);
},
ast::TypeUsage::Function(func) => {
return ast::TypeUsage::Function(ast::FunctionTypeUsage{
let result = ast::TypeUsage::Function(ast::FunctionTypeUsage{
arguments: func.arguments.iter().map(|arg| {
replace_generic_with_concrete(replace, with_type, arg)
}).collect(),
return_type: Box::new(replace_generic_with_concrete(replace, with_type, &func.return_type)),
});
return result;
},
_ => {
// in_type is unknown, skip
return in_type.clone();
},
_ => panic!("unknown in a generic, this should not happen")
};
}
@@ -89,9 +102,7 @@ impl TypeConstructor {
// 1. for arg in args, assert arg matches self.generic traits via impl name
// 2. replace type usage names with arg types
for i in 0..known_usage.parameters.len() {
println!("test: {:?}\n{:?}", &known_usage.parameters[i], &self.generic.parameters[i].bounds);
if !type_meets_trait_bounds(ctx, &known_usage.parameters[i], &self.generic.parameters[i].bounds) {
panic!("InvalidTypeForGeneric");
return Err(errors::TypingError::InvalidTypeForGeneric);
}
result = replace_generic_with_concrete(&self.generic.parameters[i].name, &known_usage.parameters[i], &self.type_usage);
@@ -172,29 +183,59 @@ impl EnvType {
}
fn type_construct(&self, ctx: &Context, usage: &ast::GenericUsage) -> Result<EnvType> {
let mut fields = HashMap::new();
for (k, v) in self.fields.iter() {
let type_usage = TypeConstructor{generic: self.generic.clone(), type_usage: v.clone()}.construct(ctx, usage)?;
fields.insert(k.clone(), type_usage);
// steps
// 1. Check if matches bounds, create unknowns if necessary.
// 2. Replace all (Named+generics or function args/return) recursively.
// 3. Return updated, plus known generic usage to replace any unknown usage.
let known_usage = match usage {
ast::GenericUsage::Known(known) => known.clone(),
ast::GenericUsage::Unknown => {
let mut new_unknowns = vec!();
for _ in 0..self.generic.parameters.len() {
new_unknowns.push(ast::TypeUsage::new_unknown(&ctx.id_generator));
}
ast::GenericInstantiation {
parameters: new_unknowns.iter().map(|tp| tp.clone()).collect(),
}
},
};
if known_usage.parameters.len() != self.generic.parameters.len() {
return Err(errors::TypingError::WrongNumberOfTypeParameters{});
}
let mut impls = vec!();
for impl_ in self.impls.iter() {
let mut functions = HashMap::new();
for (name, func) in impl_.functions.iter() {
let type_usage = TypeConstructor{generic: self.generic.clone(), type_usage: func.type_usage.clone()}.construct(ctx, usage)?;
functions.insert(name.clone(), TypeConstructor{generic: func.generic.clone(), type_usage: type_usage});
for i in 0..known_usage.parameters.len() {
if !type_meets_trait_bounds(ctx, &known_usage.parameters[i], &self.generic.parameters[i].bounds) {
return Err(errors::TypingError::InvalidTypeForGeneric);
}
impls.push(EnvImpl{
trait_: impl_.trait_.clone(),
functions: functions,
});
}
return Ok(EnvType{
generic: ast::Generic{parameters: vec!()},
is_a: self.is_a.clone(),
fields: fields,
impls: impls,
});
// Generic type matches, time to replace
let mut result = self.clone();
for i in 0..known_usage.parameters.len() {
let mut fields = HashMap::new();
for (k, v) in result.fields.iter() {
let type_usage = replace_generic_with_concrete(&self.generic.parameters[i].name, &known_usage.parameters[i], &v);
fields.insert(k.clone(), type_usage);
}
let mut impls = vec!();
for impl_ in result.impls.iter() {
let mut functions = HashMap::new();
for (name, func) in impl_.functions.iter() {
let type_usage = replace_generic_with_concrete(&self.generic.parameters[i].name, &known_usage.parameters[i], &func.type_usage);
functions.insert(name.clone(), TypeConstructor{generic: func.generic.clone(), type_usage: type_usage});
}
impls.push(EnvImpl{
trait_: impl_.trait_.clone(),
functions: functions,
});
}
result = EnvType{
generic: ast::Generic{parameters: vec!()},
is_a: self.is_a.clone(),
fields: fields,
impls: impls,
};
}
return Ok(result);
}
}
@@ -410,6 +451,7 @@ fn get_attr(ctx: &Context, get_from: &NamedEntity, attribute: &ast::Identifier)
_ => panic!("variable has non-type as type"),
};
let type_ = env_type.type_construct(ctx, &named.type_parameters)?;
let attr = get_attr(ctx, &NamedEntity::NamedType(type_), attribute)?;
let method = match attr {
StructAttr::Field(field) => return Ok(StructAttr::Field(field)),
@@ -463,8 +505,6 @@ impl Context {
impls: vec!(),
};
for bound in parameter.bounds.iter() {
println!("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
println!("found bound: {:?}", bound);
if !self.environment.contains_key(&bound.name.value) {
return Err(errors::TypingError::TypeDoesNotExist {
identifier: bound.clone(),
@@ -545,7 +585,6 @@ fn type_exists(ctx: &Context, type_: &ast::TypeUsage) -> Result<()> {
let result = match type_ {
ast::TypeUsage::Named(named) => {
if !ctx.environment.contains_key(&named.name.name.value) {
panic!("foo");
return Err(errors::TypingError::TypeDoesNotExist {
identifier: named.name.clone(),
});
@@ -623,7 +662,32 @@ fn unify(ctx: &Context, t1: &ast::TypeUsage, t2: &ast::TypeUsage) -> Result<Subs
match (t1, t2) {
(ast::TypeUsage::Named(named1), ast::TypeUsage::Named(named2)) => {
if named1.name.name.value == named2.name.name.value {
return Ok(SubstitutionMap::new());
let mut result = SubstitutionMap::new();
match (&named1.type_parameters, &named2.type_parameters) {
(ast::GenericUsage::Known(known1), ast::GenericUsage::Known(known2)) => {
if known1.parameters.len() != known2.parameters.len() {
return Err(errors::TypingError::TypeMismatch {
type_one: t1.clone(),
type_two: t2.clone(),
});
}
for (i, _) in known1.parameters.iter().enumerate() {
result = compose_substitutions(
ctx,
&result,
&unify(
ctx,
&apply_substitution(ctx, &result, &known1.parameters[i])?,
&apply_substitution(ctx, &result, &known2.parameters[i])?,
)?,
)?;
}
},
_ => {
panic!("should never be unknown")
},
}
return Ok(result);
}
}
_ => {}
@@ -777,7 +841,8 @@ impl TypeChecker {
}
ast::ModuleItem::Impl(impl_) => {
let (impl_result, impl_subst) = self.with_impl(&ctx, &subst, impl_)?;
subst = compose_substitutions(&ctx, &subst, &impl_subst)?;
// TODO: errors on generics not exist at global scope
// subst = compose_substitutions(&ctx, &subst, &impl_subst)?;
ast::ModuleItem::Impl(impl_result)
}
});
@@ -929,7 +994,6 @@ impl TypeChecker {
&impl_ctx,
&ast::TypeUsage::new_named(&impl_.struct_.name.clone(), &ast::GenericUsage::Unknown),
)?;
println!("env {:?}", impl_ctx);
let mut functions = vec![];
for function in impl_.functions.iter() {
let (result, function_subs) = self.with_function(&impl_ctx, &substitutions, function)?;
@@ -1270,6 +1334,7 @@ impl TypeChecker {
});
}
};
if struct_type.fields.len() != literal_struct.fields.len() {
return Err(errors::TypingError::StructLiteralFieldsMismatch {
struct_name: literal_struct.name.clone(),
@@ -1321,7 +1386,6 @@ impl TypeChecker {
ast::TypeUsage::Function(fn_type) => {
substitution = compose_substitutions(ctx, &substitution, &unify(ctx, &function_call.type_, &*fn_type.return_type)?)?;
if function_call.arguments.len() != fn_type.arguments.len() {
println!("{:?}\n{:?}", &function_call, &fn_type);
return Err(errors::TypingError::ArgumentLengthMismatch {});
}
}
@@ -1479,7 +1543,6 @@ impl TypeChecker {
) -> Result<(ast::StructGetter, SubstitutionMap)> {
let mut substitution = substitution.clone();
let (source, subst) = self.with_expression(ctx, &substitution, &struct_getter.source)?;
println!("source: {:?}", &source);
substitution = compose_substitutions(ctx, &substitution, &subst)?;
let field_type = match get_attr(ctx, &NamedEntity::Variable(source.type_.clone()), &struct_getter.attribute)? {