Fix tests; Implement type generics
This commit is contained in:
parent
d53ddd07eb
commit
cddf199156
@ -44,6 +44,9 @@ impl AbstractNode for Expression {
|
|||||||
fn validate(&self, context: &mut Context, manage_memory: bool) -> Result<(), ValidationError> {
|
fn validate(&self, context: &mut Context, manage_memory: bool) -> Result<(), ValidationError> {
|
||||||
match self {
|
match self {
|
||||||
Expression::As(r#as) => r#as.node.validate(context, manage_memory),
|
Expression::As(r#as) => r#as.node.validate(context, manage_memory),
|
||||||
|
Expression::BuiltInFunctionCall(built_in_function_call) => {
|
||||||
|
built_in_function_call.node.validate(context, manage_memory)
|
||||||
|
}
|
||||||
Expression::FunctionCall(function_call) => {
|
Expression::FunctionCall(function_call) => {
|
||||||
function_call.node.validate(context, manage_memory)
|
function_call.node.validate(context, manage_memory)
|
||||||
}
|
}
|
||||||
@ -68,9 +71,6 @@ impl AbstractNode for Expression {
|
|||||||
Expression::Logic(logic) => logic.node.validate(context, manage_memory),
|
Expression::Logic(logic) => logic.node.validate(context, manage_memory),
|
||||||
Expression::Math(math) => math.node.validate(context, manage_memory),
|
Expression::Math(math) => math.node.validate(context, manage_memory),
|
||||||
Expression::Value(value_node) => value_node.node.validate(context, manage_memory),
|
Expression::Value(value_node) => value_node.node.validate(context, manage_memory),
|
||||||
Expression::BuiltInFunctionCall(built_in_function_call) => {
|
|
||||||
built_in_function_call.node.validate(context, manage_memory)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ pub mod assignment;
|
|||||||
pub mod async_block;
|
pub mod async_block;
|
||||||
pub mod block;
|
pub mod block;
|
||||||
pub mod built_in_function_call;
|
pub mod built_in_function_call;
|
||||||
|
pub mod expression;
|
||||||
pub mod function_call;
|
pub mod function_call;
|
||||||
pub mod if_else;
|
pub mod if_else;
|
||||||
pub mod list_index;
|
pub mod list_index;
|
||||||
@ -15,7 +16,6 @@ pub mod structure_definition;
|
|||||||
pub mod r#type;
|
pub mod r#type;
|
||||||
pub mod type_alias;
|
pub mod type_alias;
|
||||||
pub mod type_constructor;
|
pub mod type_constructor;
|
||||||
pub mod value_expression;
|
|
||||||
pub mod value_node;
|
pub mod value_node;
|
||||||
pub mod r#while;
|
pub mod r#while;
|
||||||
|
|
||||||
@ -29,6 +29,7 @@ pub use self::{
|
|||||||
async_block::AsyncBlock,
|
async_block::AsyncBlock,
|
||||||
block::Block,
|
block::Block,
|
||||||
built_in_function_call::BuiltInFunctionCall,
|
built_in_function_call::BuiltInFunctionCall,
|
||||||
|
expression::Expression,
|
||||||
function_call::FunctionCall,
|
function_call::FunctionCall,
|
||||||
if_else::IfElse,
|
if_else::IfElse,
|
||||||
list_index::ListIndex,
|
list_index::ListIndex,
|
||||||
@ -43,7 +44,6 @@ pub use self::{
|
|||||||
structure_definition::StructureDefinition,
|
structure_definition::StructureDefinition,
|
||||||
type_alias::TypeAssignment,
|
type_alias::TypeAssignment,
|
||||||
type_constructor::{FunctionTypeConstructor, ListTypeConstructor, TypeConstructor},
|
type_constructor::{FunctionTypeConstructor, ListTypeConstructor, TypeConstructor},
|
||||||
value_expression::Expression,
|
|
||||||
value_node::ValueNode,
|
value_node::ValueNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ pub enum Type {
|
|||||||
value_parameters: Vec<(Identifier, Type)>,
|
value_parameters: Vec<(Identifier, Type)>,
|
||||||
return_type: Box<Type>,
|
return_type: Box<Type>,
|
||||||
},
|
},
|
||||||
|
Generic(Option<Box<Type>>),
|
||||||
Integer,
|
Integer,
|
||||||
List {
|
List {
|
||||||
length: usize,
|
length: usize,
|
||||||
@ -49,8 +50,19 @@ impl Type {
|
|||||||
| (Type::None, Type::None)
|
| (Type::None, Type::None)
|
||||||
| (Type::Range, Type::Range)
|
| (Type::Range, Type::Range)
|
||||||
| (Type::String, Type::String) => return Ok(()),
|
| (Type::String, Type::String) => return Ok(()),
|
||||||
|
(Type::Generic(left), Type::Generic(right)) => match (left, right) {
|
||||||
|
(Some(left), Some(right)) => {
|
||||||
|
if left.check(&right).is_ok() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(None, None) => {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
(Type::ListOf(left), Type::ListOf(right)) => {
|
(Type::ListOf(left), Type::ListOf(right)) => {
|
||||||
if let Ok(()) = left.check(&right) {
|
if left.check(&right).is_ok() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -195,6 +207,13 @@ impl Display for Type {
|
|||||||
Type::Any => write!(f, "any"),
|
Type::Any => write!(f, "any"),
|
||||||
Type::Boolean => write!(f, "bool"),
|
Type::Boolean => write!(f, "bool"),
|
||||||
Type::Float => write!(f, "float"),
|
Type::Float => write!(f, "float"),
|
||||||
|
Type::Generic(type_option) => {
|
||||||
|
if let Some(concrete_type) = type_option {
|
||||||
|
write!(f, "implied to be {concrete_type}")
|
||||||
|
} else {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
Type::Integer => write!(f, "int"),
|
Type::Integer => write!(f, "int"),
|
||||||
Type::List { length, item_type } => write!(f, "[{length}; {}]", item_type),
|
Type::List { length, item_type } => write!(f, "[{length}; {}]", item_type),
|
||||||
Type::ListOf(item_type) => write!(f, "list({})", item_type),
|
Type::ListOf(item_type) => write!(f, "list({})", item_type),
|
||||||
|
@ -26,7 +26,7 @@ pub enum ValueNode {
|
|||||||
name: WithPosition<Identifier>,
|
name: WithPosition<Identifier>,
|
||||||
fields: Vec<(WithPosition<Identifier>, Expression)>,
|
fields: Vec<(WithPosition<Identifier>, Expression)>,
|
||||||
},
|
},
|
||||||
Parsed {
|
Function {
|
||||||
type_parameters: Option<Vec<Identifier>>,
|
type_parameters: Option<Vec<Identifier>>,
|
||||||
value_parameters: Vec<(Identifier, TypeConstructor)>,
|
value_parameters: Vec<(Identifier, TypeConstructor)>,
|
||||||
return_type: TypeConstructor,
|
return_type: TypeConstructor,
|
||||||
@ -57,8 +57,8 @@ impl AbstractNode for ValueNode {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let ValueNode::Parsed {
|
if let ValueNode::Function {
|
||||||
type_parameters: _,
|
type_parameters,
|
||||||
value_parameters,
|
value_parameters,
|
||||||
return_type,
|
return_type,
|
||||||
body,
|
body,
|
||||||
@ -66,6 +66,12 @@ impl AbstractNode for ValueNode {
|
|||||||
{
|
{
|
||||||
let mut function_context = Context::new(Some(&context));
|
let mut function_context = Context::new(Some(&context));
|
||||||
|
|
||||||
|
if let Some(type_parameters) = type_parameters {
|
||||||
|
for identifier in type_parameters {
|
||||||
|
function_context.set_type(identifier.clone(), Type::Generic(None))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (identifier, type_constructor) in value_parameters {
|
for (identifier, type_constructor) in value_parameters {
|
||||||
let r#type = type_constructor.clone().construct(&function_context)?;
|
let r#type = type_constructor.clone().construct(&function_context)?;
|
||||||
|
|
||||||
@ -175,18 +181,14 @@ impl AbstractNode for ValueNode {
|
|||||||
}
|
}
|
||||||
ValueNode::Range(range) => Value::range(range),
|
ValueNode::Range(range) => Value::range(range),
|
||||||
ValueNode::String(string) => Value::string(string),
|
ValueNode::String(string) => Value::string(string),
|
||||||
ValueNode::Parsed {
|
ValueNode::Function {
|
||||||
type_parameters,
|
type_parameters,
|
||||||
value_parameters: constructors,
|
value_parameters: constructors,
|
||||||
return_type,
|
return_type,
|
||||||
body,
|
body,
|
||||||
} => {
|
} => {
|
||||||
let type_parameters = type_parameters.map(|parameter_list| {
|
let type_parameters =
|
||||||
parameter_list
|
type_parameters.map(|parameter_list| parameter_list.into_iter().collect());
|
||||||
.into_iter()
|
|
||||||
.map(|parameter| parameter)
|
|
||||||
.collect()
|
|
||||||
});
|
|
||||||
let mut value_parameters = Vec::with_capacity(constructors.len());
|
let mut value_parameters = Vec::with_capacity(constructors.len());
|
||||||
|
|
||||||
for (identifier, constructor) in constructors {
|
for (identifier, constructor) in constructors {
|
||||||
@ -263,13 +265,13 @@ impl Ord for ValueNode {
|
|||||||
(String(left), String(right)) => left.cmp(right),
|
(String(left), String(right)) => left.cmp(right),
|
||||||
(String(_), _) => Ordering::Greater,
|
(String(_), _) => Ordering::Greater,
|
||||||
(
|
(
|
||||||
Parsed {
|
Function {
|
||||||
type_parameters: left_type_arguments,
|
type_parameters: left_type_arguments,
|
||||||
value_parameters: left_parameters,
|
value_parameters: left_parameters,
|
||||||
return_type: left_return,
|
return_type: left_return,
|
||||||
body: left_body,
|
body: left_body,
|
||||||
},
|
},
|
||||||
Parsed {
|
Function {
|
||||||
type_parameters: right_type_arguments,
|
type_parameters: right_type_arguments,
|
||||||
value_parameters: right_parameters,
|
value_parameters: right_parameters,
|
||||||
return_type: right_return,
|
return_type: right_return,
|
||||||
@ -296,7 +298,7 @@ impl Ord for ValueNode {
|
|||||||
parameter_cmp
|
parameter_cmp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(Parsed { .. }, _) => Ordering::Greater,
|
(Function { .. }, _) => Ordering::Greater,
|
||||||
(
|
(
|
||||||
Structure {
|
Structure {
|
||||||
name: left_name,
|
name: left_name,
|
||||||
@ -337,7 +339,7 @@ impl ExpectedType for ValueNode {
|
|||||||
ValueNode::Map(_) => Type::Map,
|
ValueNode::Map(_) => Type::Map,
|
||||||
ValueNode::Range(_) => Type::Range,
|
ValueNode::Range(_) => Type::Range,
|
||||||
ValueNode::String(_) => Type::String,
|
ValueNode::String(_) => Type::String,
|
||||||
ValueNode::Parsed {
|
ValueNode::Function {
|
||||||
type_parameters,
|
type_parameters,
|
||||||
value_parameters,
|
value_parameters,
|
||||||
return_type,
|
return_type,
|
||||||
|
@ -26,6 +26,10 @@ impl<'a> Context<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_child<'b>(&'b self) -> Context<'b> {
|
||||||
|
Context::new(Some(self))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn inner(
|
pub fn inner(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<RwLockReadGuard<BTreeMap<Identifier, (VariableData, UsageData)>>, RwLockPoisonError>
|
) -> Result<RwLockReadGuard<BTreeMap<Identifier, (VariableData, UsageData)>>, RwLockPoisonError>
|
||||||
|
@ -10,16 +10,16 @@ use crate::{
|
|||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
Parse {
|
|
||||||
expected: String,
|
|
||||||
span: (usize, usize),
|
|
||||||
found: Option<String>,
|
|
||||||
},
|
|
||||||
Lex {
|
Lex {
|
||||||
expected: String,
|
expected: String,
|
||||||
span: (usize, usize),
|
span: (usize, usize),
|
||||||
reason: String,
|
reason: String,
|
||||||
},
|
},
|
||||||
|
Parse {
|
||||||
|
expected: String,
|
||||||
|
span: (usize, usize),
|
||||||
|
found: Option<String>,
|
||||||
|
},
|
||||||
Runtime {
|
Runtime {
|
||||||
error: RuntimeError,
|
error: RuntimeError,
|
||||||
position: SourcePosition,
|
position: SourcePosition,
|
||||||
|
@ -237,11 +237,7 @@ impl InterpreterError {
|
|||||||
} else {
|
} else {
|
||||||
format!("Expected {expected}.")
|
format!("Expected {expected}.")
|
||||||
};
|
};
|
||||||
|
let found = found.unwrap_or_else(|| "End of input".to_string());
|
||||||
let label_message = format!(
|
|
||||||
"{} is not valid in this position.",
|
|
||||||
found.unwrap_or_else(|| String::with_capacity(0))
|
|
||||||
);
|
|
||||||
|
|
||||||
(
|
(
|
||||||
Report::build(
|
Report::build(
|
||||||
@ -252,7 +248,7 @@ impl InterpreterError {
|
|||||||
.with_message(description)
|
.with_message(description)
|
||||||
.with_label(
|
.with_label(
|
||||||
Label::new((self.source_id.clone(), span.0..span.1))
|
Label::new((self.source_id.clone(), span.0..span.1))
|
||||||
.with_message(label_message)
|
.with_message(format!("{found} is not valid in this position."))
|
||||||
.with_color(Color::Red),
|
.with_color(Color::Red),
|
||||||
),
|
),
|
||||||
None,
|
None,
|
||||||
|
@ -246,7 +246,7 @@ pub fn parser<'src>(
|
|||||||
.map_with(
|
.map_with(
|
||||||
|(((type_parameters, value_parameters), return_type), body), state| {
|
|(((type_parameters, value_parameters), return_type), body), state| {
|
||||||
Expression::Value(
|
Expression::Value(
|
||||||
ValueNode::Parsed {
|
ValueNode::Function {
|
||||||
type_parameters,
|
type_parameters,
|
||||||
value_parameters,
|
value_parameters,
|
||||||
return_type,
|
return_type,
|
||||||
@ -640,6 +640,73 @@ mod tests {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
// Reuse these tests when structures are reimplemented
|
||||||
|
// #[test]
|
||||||
|
// fn structure_instance() {
|
||||||
|
// assert_eq!(
|
||||||
|
// parse(
|
||||||
|
// &lex("
|
||||||
|
// Foo {
|
||||||
|
// bar = 42,
|
||||||
|
// baz = 'hiya',
|
||||||
|
// }
|
||||||
|
// ")
|
||||||
|
// .unwrap()
|
||||||
|
// )
|
||||||
|
// .unwrap()[0],
|
||||||
|
// Statement::Expression(Expression::Value(
|
||||||
|
// ValueNode::Structure {
|
||||||
|
// name: Identifier::new("Foo").with_position((21, 24)),
|
||||||
|
// fields: vec![
|
||||||
|
// (
|
||||||
|
// Identifier::new("bar").with_position((0, 0)),
|
||||||
|
// Expression::Value(ValueNode::Integer(42).with_position((57, 59)))
|
||||||
|
// ),
|
||||||
|
// (
|
||||||
|
// Identifier::new("baz").with_position((0, 0)),
|
||||||
|
// Expression::Value(
|
||||||
|
// ValueNode::String("hiya".to_string()).with_position((91, 97))
|
||||||
|
// )
|
||||||
|
// ),
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// .with_position((21, 120))
|
||||||
|
// ))
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn structure_definition() {
|
||||||
|
// assert_eq!(
|
||||||
|
// parse(
|
||||||
|
// &lex("
|
||||||
|
// struct Foo {
|
||||||
|
// bar : int,
|
||||||
|
// baz : str,
|
||||||
|
// }
|
||||||
|
// ")
|
||||||
|
// .unwrap()
|
||||||
|
// )
|
||||||
|
// .unwrap()[0],
|
||||||
|
// Statement::StructureDefinition(
|
||||||
|
// StructureDefinition::new(
|
||||||
|
// Identifier::new("Foo"),
|
||||||
|
// vec![
|
||||||
|
// (
|
||||||
|
// Identifier::new("bar"),
|
||||||
|
// TypeConstructor::Type(Type::Integer.with_position((64, 67)))
|
||||||
|
// ),
|
||||||
|
// (
|
||||||
|
// Identifier::new("baz"),
|
||||||
|
// TypeConstructor::Type(Type::String.with_position((99, 102)))
|
||||||
|
// ),
|
||||||
|
// ]
|
||||||
|
// )
|
||||||
|
// .with_position((21, 125))
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn type_alias() {
|
fn type_alias() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -743,72 +810,6 @@ mod tests {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn structure_instance() {
|
|
||||||
assert_eq!(
|
|
||||||
parse(
|
|
||||||
&lex("
|
|
||||||
Foo {
|
|
||||||
bar = 42,
|
|
||||||
baz = 'hiya',
|
|
||||||
}
|
|
||||||
")
|
|
||||||
.unwrap()
|
|
||||||
)
|
|
||||||
.unwrap()[0],
|
|
||||||
Statement::Expression(Expression::Value(
|
|
||||||
ValueNode::Structure {
|
|
||||||
name: Identifier::new("Foo").with_position((21, 24)),
|
|
||||||
fields: vec![
|
|
||||||
(
|
|
||||||
Identifier::new("bar").with_position((0, 0)),
|
|
||||||
Expression::Value(ValueNode::Integer(42).with_position((57, 59)))
|
|
||||||
),
|
|
||||||
(
|
|
||||||
Identifier::new("baz").with_position((0, 0)),
|
|
||||||
Expression::Value(
|
|
||||||
ValueNode::String("hiya".to_string()).with_position((91, 97))
|
|
||||||
)
|
|
||||||
),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
.with_position((21, 120))
|
|
||||||
))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn structure_definition() {
|
|
||||||
assert_eq!(
|
|
||||||
parse(
|
|
||||||
&lex("
|
|
||||||
struct Foo {
|
|
||||||
bar : int,
|
|
||||||
baz : str,
|
|
||||||
}
|
|
||||||
")
|
|
||||||
.unwrap()
|
|
||||||
)
|
|
||||||
.unwrap()[0],
|
|
||||||
Statement::StructureDefinition(
|
|
||||||
StructureDefinition::new(
|
|
||||||
Identifier::new("Foo"),
|
|
||||||
vec![
|
|
||||||
(
|
|
||||||
Identifier::new("bar"),
|
|
||||||
TypeConstructor::Type(Type::Integer.with_position((64, 67)))
|
|
||||||
),
|
|
||||||
(
|
|
||||||
Identifier::new("baz"),
|
|
||||||
TypeConstructor::Type(Type::String.with_position((99, 102)))
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
.with_position((21, 125))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn map_index() {
|
fn map_index() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -912,23 +913,23 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn list_of_type() {
|
fn list_of_type() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse(&lex("foobar : list(bool) = [true]").unwrap()).unwrap()[0],
|
parse(&lex("foobar : [bool] = [true]").unwrap()).unwrap()[0],
|
||||||
Statement::Assignment(
|
Statement::Assignment(
|
||||||
Assignment::new(
|
Assignment::new(
|
||||||
Identifier::new("foobar").with_position((0, 6)),
|
Identifier::new("foobar").with_position((0, 6)),
|
||||||
Some(TypeConstructor::ListOf(
|
Some(TypeConstructor::ListOf(
|
||||||
Box::new(TypeConstructor::Type(Type::Boolean.with_position((9, 19))))
|
Box::new(TypeConstructor::Type(Type::Boolean.with_position((10, 14))))
|
||||||
.with_position((0, 0))
|
.with_position((9, 15))
|
||||||
)),
|
)),
|
||||||
AssignmentOperator::Assign,
|
AssignmentOperator::Assign,
|
||||||
Statement::Expression(Expression::Value(
|
Statement::Expression(Expression::Value(
|
||||||
ValueNode::List(vec![Expression::Value(
|
ValueNode::List(vec![Expression::Value(
|
||||||
ValueNode::Boolean(true).with_position((23, 27))
|
ValueNode::Boolean(true).with_position((19, 23))
|
||||||
)])
|
)])
|
||||||
.with_position((22, 28))
|
.with_position((18, 24))
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
.with_position((0, 28))
|
.with_position((0, 24))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1009,7 +1010,7 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse(&lex("fn () -> int { 0 }").unwrap()).unwrap()[0],
|
parse(&lex("fn () -> int { 0 }").unwrap()).unwrap()[0],
|
||||||
Statement::Expression(Expression::Value(
|
Statement::Expression(Expression::Value(
|
||||||
ValueNode::Parsed {
|
ValueNode::Function {
|
||||||
type_parameters: None,
|
type_parameters: None,
|
||||||
value_parameters: vec![],
|
value_parameters: vec![],
|
||||||
return_type: TypeConstructor::Type(Type::Integer.with_position((9, 12))),
|
return_type: TypeConstructor::Type(Type::Integer.with_position((9, 12))),
|
||||||
@ -1025,7 +1026,7 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse(&lex("fn (x: int) -> int { x }").unwrap()).unwrap()[0],
|
parse(&lex("fn (x: int) -> int { x }").unwrap()).unwrap()[0],
|
||||||
Statement::Expression(Expression::Value(
|
Statement::Expression(Expression::Value(
|
||||||
ValueNode::Parsed {
|
ValueNode::Function {
|
||||||
type_parameters: None,
|
type_parameters: None,
|
||||||
value_parameters: vec![(
|
value_parameters: vec![(
|
||||||
Identifier::new("x"),
|
Identifier::new("x"),
|
||||||
@ -1047,7 +1048,7 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse(&lex("fn T, U (x: T, y: U) -> T { x }").unwrap()).unwrap()[0],
|
parse(&lex("fn T, U (x: T, y: U) -> T { x }").unwrap()).unwrap()[0],
|
||||||
Statement::Expression(Expression::Value(
|
Statement::Expression(Expression::Value(
|
||||||
ValueNode::Parsed {
|
ValueNode::Function {
|
||||||
type_parameters: Some(vec![Identifier::new("T"), Identifier::new("U"),]),
|
type_parameters: Some(vec![Identifier::new("T"), Identifier::new("U"),]),
|
||||||
value_parameters: vec![
|
value_parameters: vec![
|
||||||
(
|
(
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
json = {
|
json = {
|
||||||
parse = fn (T)(input: str) -> T {
|
parse = fn T (input: str) -> T {
|
||||||
JSON_PARSE T input
|
JSON_PARSE T input
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user