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

@@ -36,22 +36,24 @@ This language is under active development, progress will be marked here as the l
- [x] Basic
- [ ] Default Functions
- [ ] Generics
- [ ] Basic
- [x] Basic
- [ ] Inferred
- [ ] Higher kinded types
- [ ] Variadic generic types
- [ ] Control Flow
- [x] If
- [ ] While
- [ ] For
- [ ] Async-Await / Futures
- [ ] Enums
- [ ] Lambdas
- [ ] Async-Await
- [ ] Imports
- [ ] Visibility
- [ ] Const / Mut
- [ ] Macros
- [ ] Standard Library
This project is actively looking for contributors, so if you're interested in programming language design or have experience working with LLVM, don't hesitate to contact.
This project is actively looking for contributors, so if you're interested in programming language design or have experience working with LLVM, don't hesitate to contact. The current plan is to have this language transpile to a C-like subset of Rust, which can then be compiled into executable code.
## Philosophy
@@ -116,7 +118,7 @@ async fn handle(req: http.Request, resp: mut http.Response): {
await resp.write(json.encode[ExampleResponse](response_data));
}
async fn main(args: Vec[Str], os: OS): i32 {
async fn main(args: Vec[String], os: OS): i32 {
let log = logging.new_logger(os.console.stdout());
let router = http.Router("").add_route("/myroute", handle);
let http_server = http.Server(os.net(), "localhost", 8080, router);
@@ -131,7 +133,7 @@ async fn main(args: Vec[Str], os: OS): i32 {
All variables are immutable by default, to make them mutable use the `mut` keyword. Once a variable becomes immutable it cannot become mutable again. If you need it to become mutable, either implement the `clone` trait, or simply create a new one with the same data.
```rust
let mut foo = Dict[String, Int32].new(); // constructor returns a mutable reference
let mut foo = Dict[String, i32].new(); // constructor returns a mutable reference
foo.insert("eggs", 12);
foo.insert("bananas", 2);
foo.insert("grapes", 2);
@@ -168,7 +170,7 @@ Context is an exceptionally useful feature in golang, but a common complaint is
The boring standard library solves this by using parametric polymorphism. Context is by default an empty object passed through the chain, and each function/set of context parameters is an additional trait condition applied at compile time.
```rust
type HTTPRequest[Ctx: Context] = async fn(Ctx, http.Request, mut http.Response);
type HTTPRequest = async fn[Ctx: Context](Ctx, http.Request, mut http.Response);
pub fn tracing_middleware[Ctx: Tracing](handler: HTTPRequest[Ctx]): HTTPRequest {
return async fn(ctx: Ctx, req: http.Request, resp: mut http.Response) {
@@ -209,7 +211,7 @@ Similar to python, folders/files represent the `.` seperated import path, but re
import package.path as local_name;
pub type MyStruct struct {
pub id: I32,
pub id: i32,
}
```

View File

@@ -19,9 +19,9 @@ impl [K, V: MyTrait] Pair[K, V] {
}
fn main(): i64 {
let a = Pair{
let a = Pair[i64, Value]{
k: 4,
v: Value{value: 6},
};
return a.get_value(999);
return a.get_value[i64](999).value;
}

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,16 +183,44 @@ impl EnvType {
}
fn type_construct(&self, ctx: &Context, usage: &ast::GenericUsage) -> Result<EnvType> {
// 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{});
}
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);
}
}
// 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 self.fields.iter() {
let type_usage = TypeConstructor{generic: self.generic.clone(), type_usage: v.clone()}.construct(ctx, usage)?;
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 self.impls.iter() {
for impl_ in result.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)?;
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{
@@ -189,12 +228,14 @@ impl EnvType {
functions: functions,
});
}
return Ok(EnvType{
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)? {