got generics working
This commit is contained in:
16
README.md
16
README.md
@@ -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,
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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())}),
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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(¶m_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)? {
|
||||
|
||||
Reference in New Issue
Block a user