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

Generic structs #514

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
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
8 changes: 7 additions & 1 deletion numbat/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ pub enum ProcedureKind {
Type,
}

#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TypeParameterBound {
Dim,
}
Expand Down Expand Up @@ -430,6 +430,7 @@ pub enum Statement {
DefineStruct {
struct_name_span: Span,
struct_name: String,
type_parameters: Vec<(Span, String, Option<TypeParameterBound>)>,
fields: Vec<(Span, String, TypeAnnotation)>,
},
}
Expand Down Expand Up @@ -656,11 +657,16 @@ impl ReplaceSpans for Statement {
}
Statement::DefineStruct {
struct_name,
type_parameters,
fields,
..
} => Statement::DefineStruct {
struct_name_span: Span::dummy(),
struct_name: struct_name.clone(),
type_parameters: type_parameters
.iter()
.map(|(_, name, bound)| (Span::dummy(), name.clone(), bound.clone()))
.collect(),
fields: fields
.iter()
.map(|(_span, name, type_)| {
Expand Down
1 change: 1 addition & 0 deletions numbat/src/ffi/lookup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ pub fn _get_chemical_element_data_raw(mut args: Args) -> Result<Value> {

let info = StructInfo {
name: "_ChemicalElementRaw".to_string(),
type_parameters: vec![],
definition_span: unknown_span,
fields,
};
Expand Down
123 changes: 71 additions & 52 deletions numbat/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,55 @@ impl<'a> Parser<'a> {
Ok(identifiers)
}

fn type_parameters(&mut self) -> Result<Vec<(Span, String, Option<TypeParameterBound>)>> {
let mut type_parameters = vec![];
// Parsing the generic parameters if there are any
if self.match_exact(TokenKind::LessThan).is_some() {
while self.match_exact(TokenKind::GreaterThan).is_none() {
if let Some(type_parameter_name) = self.match_exact(TokenKind::Identifier) {
let bound = if self.match_exact(TokenKind::Colon).is_some() {
match self.match_exact(TokenKind::Identifier) {
Some(token) if token.lexeme == "Dim" => Some(TypeParameterBound::Dim),
Some(token) => {
return Err(ParseError {
kind: ParseErrorKind::UnknownBound(token.lexeme.clone()),
span: token.span,
});
}
None => {
return Err(ParseError {
kind: ParseErrorKind::ExpectedBoundInTypeParameterDefinition,
span: self.peek().span,
});
}
}
} else {
None
};

let span = self.last().unwrap().span;
type_parameters.push((span, type_parameter_name.lexeme.to_string(), bound));

if self.match_exact(TokenKind::Comma).is_none()
&& self.peek().kind != TokenKind::GreaterThan
{
return Err(ParseError {
kind: ParseErrorKind::ExpectedCommaOrRightAngleBracket,
span: self.peek().span,
});
}
} else {
return Err(ParseError {
kind: ParseErrorKind::ExpectedTypeParameterName,
span: self.peek().span,
});
}
}
}

Ok(type_parameters)
}

fn statement(&mut self) -> Result<Statement> {
if !(self.peek().kind == TokenKind::At
|| self.peek().kind == TokenKind::Unit
Expand Down Expand Up @@ -436,58 +485,7 @@ impl<'a> Parser<'a> {
} else if self.match_exact(TokenKind::Fn).is_some() {
if let Some(fn_name) = self.match_exact(TokenKind::Identifier) {
let function_name_span = self.last().unwrap().span;
let mut type_parameters = vec![];
// Parsing the generic parameters if there are any
if self.match_exact(TokenKind::LessThan).is_some() {
while self.match_exact(TokenKind::GreaterThan).is_none() {
if let Some(type_parameter_name) = self.match_exact(TokenKind::Identifier) {
let bound = if self.match_exact(TokenKind::Colon).is_some() {
match self.match_exact(TokenKind::Identifier) {
Some(token) if token.lexeme == "Dim" => {
Some(TypeParameterBound::Dim)
}
Some(token) => {
return Err(ParseError {
kind: ParseErrorKind::UnknownBound(
token.lexeme.clone(),
),
span: token.span,
});
}
None => {
return Err(ParseError {
kind: ParseErrorKind::ExpectedBoundInTypeParameterDefinition,
span: self.peek().span,
});
}
}
} else {
None
};

let span = self.last().unwrap().span;
type_parameters.push((
span,
type_parameter_name.lexeme.to_string(),
bound,
));

if self.match_exact(TokenKind::Comma).is_none()
&& self.peek().kind != TokenKind::GreaterThan
{
return Err(ParseError {
kind: ParseErrorKind::ExpectedCommaOrRightAngleBracket,
span: self.peek().span,
});
}
} else {
return Err(ParseError {
kind: ParseErrorKind::ExpectedTypeParameterName,
span: self.peek().span,
});
}
}
}
let type_parameters = self.type_parameters()?;

if self.match_exact(TokenKind::LeftParen).is_none() {
return Err(ParseError {
Expand Down Expand Up @@ -756,6 +754,8 @@ impl<'a> Parser<'a> {
let name = self.identifier()?;
let name_span = self.last().unwrap().span;

let type_parameters = self.type_parameters()?;

if self.match_exact(TokenKind::LeftCurly).is_none() {
return Err(ParseError {
kind: ParseErrorKind::ExpectedLeftCurlyAfterStructName,
Expand Down Expand Up @@ -807,6 +807,7 @@ impl<'a> Parser<'a> {
Ok(Statement::DefineStruct {
struct_name_span: name_span,
struct_name: name,
type_parameters,
fields,
})
} else if self.match_any(PROCEDURES).is_some() {
Expand Down Expand Up @@ -3051,6 +3052,7 @@ mod tests {
Statement::DefineStruct {
struct_name_span: Span::dummy(),
struct_name: "Foo".to_owned(),
type_parameters: vec![],
fields: vec![
(
Span::dummy(),
Expand All @@ -3072,6 +3074,23 @@ mod tests {
},
);

parse_as(
&["struct Foo<D: Dim> { foo: D }"],
Statement::DefineStruct {
struct_name_span: Span::dummy(),
struct_name: "Foo".to_owned(),
type_parameters: vec![(Span::dummy(), "D".into(), Some(TypeParameterBound::Dim))],
fields: vec![(
Span::dummy(),
"foo".to_owned(),
TypeAnnotation::TypeExpression(TypeExpression::TypeIdentifier(
Span::dummy(),
"D".to_owned(),
)),
)],
},
);

parse_as_expression(
&["Foo {foo: 1, bar: 2}"],
struct_! {
Expand Down
2 changes: 2 additions & 0 deletions numbat/src/prefix_transformer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,10 +245,12 @@ impl Transformer {
Statement::DefineStruct {
struct_name_span,
struct_name,
type_parameters,
fields,
} => Statement::DefineStruct {
struct_name_span,
struct_name,
type_parameters,
fields,
},
Statement::DefineDimension(name_span, name, dexprs) => {
Expand Down
40 changes: 39 additions & 1 deletion numbat/src/typechecker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1655,6 +1655,7 @@ impl TypeChecker {
ast::Statement::DefineStruct {
struct_name_span,
struct_name,
type_parameters,
fields,
} => {
self.type_namespace.add_identifier(
Expand All @@ -1665,6 +1666,39 @@ impl TypeChecker {

let mut seen_fields = HashMap::new();

let mut typechecker_struct = self.clone();

for (span, type_parameter, bound) in type_parameters {
if typechecker_struct
.type_namespace
.has_identifier(type_parameter)
{
return Err(TypeCheckError::TypeParameterNameClash(
*span,
type_parameter.clone(),
));
}

typechecker_struct
.type_namespace
.add_identifier(type_parameter.clone(), *span, "type parameter".to_owned())
.ok(); // TODO: is this call even correct?

typechecker_struct
.registry
.introduced_type_parameters
.push((*span, type_parameter.clone(), bound.clone()));

match bound {
Some(TypeParameterBound::Dim) => {
typechecker_struct
.add_dtype_constraint(&Type::TPar(type_parameter.clone()))
.ok();
}
None => {}
}
}

for (span, field, _) in fields {
if let Some(other_span) = seen_fields.get(field) {
return Err(TypeCheckError::DuplicateFieldInStructDefinition(
Expand All @@ -1680,10 +1714,14 @@ impl TypeChecker {
let struct_info = StructInfo {
definition_span: *struct_name_span,
name: struct_name.clone(),
type_parameters: type_parameters.clone(),
fields: fields
.iter()
.map(|(span, name, type_)| {
Ok((name.clone(), (*span, self.type_from_annotation(type_)?)))
Ok((
name.clone(),
(*span, typechecker_struct.type_from_annotation(type_)?),
))
})
.collect::<Result<_>>()?,
};
Expand Down
1 change: 1 addition & 0 deletions numbat/src/typed_ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ impl From<BaseRepresentation> for DType {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StructInfo {
pub definition_span: Span,
pub type_parameters: Vec<(Span, String, Option<TypeParameterBound>)>,
pub name: String,
pub fields: IndexMap<String, (Span, Type)>,
}
Expand Down
Loading