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

Christoph/feat/gen structs #906

Closed
Closed
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
35 changes: 32 additions & 3 deletions crates/analyzer/src/db/queries/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ use crate::namespace::items::{
StructId, TypeDef,
};
use crate::namespace::scopes::ItemScope;
use crate::namespace::types::{Type, TypeId};
use crate::traversal::types::type_desc;
use crate::namespace::types::{Generic, Type, TypeId};
use crate::traversal::types::{type_desc, type_desc_to_trait};
use crate::AnalyzerDb;
use fe_common::utils::humanize::pluralize_conditionally;
use fe_parser::node::Node;
use fe_parser::{ast, Label};
use indexmap::map::{Entry, IndexMap};
use smol_str::SmolStr;
Expand Down Expand Up @@ -128,7 +129,8 @@ pub fn struct_field_type(
if let Some(_node) = value {
scope.not_yet_implemented("struct field initial value assignment", field_data.ast.span);
}
let typ = match type_desc(&mut scope, typ, None) {

let typ = match resolve_struct_field_type(db, field, &mut scope, typ) {
Ok(typ) => match typ.typ(db) {
Type::Contract(_) => {
scope.not_yet_implemented(
Expand All @@ -150,6 +152,33 @@ pub fn struct_field_type(
Analysis::new(typ, scope.diagnostics.take().into())
}

pub fn resolve_struct_field_type(
db: &dyn AnalyzerDb,
field: StructFieldId,
context: &mut dyn AnalyzerContext,
desc: &Node<ast::TypeDesc>,
) -> Result<TypeId, TypeError> {
let parent = field.data(db).parent;
// First check if the param type is a local generic of the struct. If not, resolve as a regular type.
if let ast::TypeDesc::Base { base } = &desc.kind {
if let Some(val) = parent.generic_param(db, base) {
let bounds = match val {
ast::GenericParameter::Unbounded(_) => vec![].into(),
ast::GenericParameter::Bounded { bound, .. } => {
vec![type_desc_to_trait(context, &bound)?].into()
}
};

return Ok(db.intern_type(Type::Generic(Generic {
name: base.clone(),
bounds,
})));
}
}

type_desc(context, desc, None)
}

pub fn struct_all_functions(db: &dyn AnalyzerDb, struct_: StructId) -> Rc<[FunctionId]> {
let struct_data = struct_.data(db);
struct_data
Expand Down
16 changes: 16 additions & 0 deletions crates/analyzer/src/namespace/items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1444,6 +1444,22 @@ impl StructId {
pub fn functions(&self, db: &dyn AnalyzerDb) -> Rc<IndexMap<SmolStr, FunctionId>> {
db.struct_function_map(*self).value
}

pub fn generic_params(&self, db: &dyn AnalyzerDb) -> Vec<GenericParameter> {
self.data(db).ast.kind.generic_params.kind.clone()
}

pub fn generic_param(&self, db: &dyn AnalyzerDb, param_name: &str) -> Option<GenericParameter> {
self.generic_params(db)
.iter()
.find(|param| match param {
GenericParameter::Unbounded(name) if name.kind == param_name => true,
GenericParameter::Bounded { name, .. } if name.kind == param_name => true,
_ => false,
})
.cloned()
}

pub fn function(&self, db: &dyn AnalyzerDb, name: &str) -> Option<FunctionId> {
self.functions(db).get(name).copied()
}
Expand Down
24 changes: 14 additions & 10 deletions crates/analyzer/src/traversal/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::context::{
};
use crate::display::Displayable;
use crate::errors::{TypeCoercionError, TypeError};
use crate::namespace::items::{Item, TraitId};
use crate::namespace::items::{Item, TraitId, TypeDef};
use crate::namespace::types::{
Base, FeString, GenericArg, GenericParamKind, GenericType, Integer, TraitOrType, Tuple, Type,
TypeId,
Expand Down Expand Up @@ -483,15 +483,19 @@ pub fn resolve_concrete_type_named_thing<T: std::fmt::Display>(
) -> Result<TypeId, TypeError> {
match named_thing {
Some(NamedThing::Item(Item::Type(id))) => {
if let Some(args) = generic_args {
context.fancy_error(
&format!("`{}` type is not generic", base_desc.kind),
vec![Label::primary(
args.span,
"unexpected generic argument list",
)],
vec![],
);
// TODO: We should get rid of Item::GenericType. It's not a helpful seperation since most types will
// be allowed to become generic over time (structs, enums etc)
if !matches!(id, TypeDef::Struct(_)) {
if let Some(args) = generic_args {
context.fancy_error(
&format!("`{}` type is not generic", base_desc.kind),
vec![Label::primary(
args.span,
"unexpected generic argument list",
)],
vec![],
);
}
}
id.type_id(context.db())
}
Expand Down
4 changes: 4 additions & 0 deletions crates/mir/src/lower/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1069,6 +1069,10 @@ impl<'db, 'a> BodyLowerHelper<'db, 'a> {
self.builder.primitive_cast(arg, ty, source)
}
} else if ty.is_aggregate(self.db) {
// This seems to be the place were we could capture the mapping of generic types to concrete types.
// But how exactly is still unclear because we can not simply map TypeID to a fixed set of concrete types
// because the TypeID of `Foo<T>` is the same no matter if we deal with `Foo<u256>` or `Foo<bool>`.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's basically what I'm currently stuck at. I'm not yet sure how to properly resolve generic structs in MIR.

self.builder.aggregate_construct(ty, args, source)
} else {
unreachable!()
Expand Down
4 changes: 4 additions & 0 deletions crates/mir/src/lower/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,10 @@ fn lower_contract(db: &dyn MirDb, contract: analyzer_items::ContractId) -> TypeK
fn lower_struct(db: &dyn MirDb, id: analyzer_items::StructId) -> TypeKind {
let name = id.name(db.upcast());

// If the field of the struct is generic, then we need to know the concrete types that were used to
// initialize it. Something something, `resolved_generics`
// I think `aggregate_construct` is the place were we can pass on the concrete args.

// Lower struct fields.
let fields = id
.fields(db.upcast())
Expand Down
3 changes: 3 additions & 0 deletions crates/parser/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ pub struct Struct {
pub fields: Vec<Node<Field>>,
pub functions: Vec<Node<Function>>,
pub pub_qual: Option<Span>,
pub generic_params: Node<Vec<GenericParameter>>,
}

#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone)]
Expand Down Expand Up @@ -762,6 +763,8 @@ impl fmt::Display for Struct {
fields,
functions,
pub_qual,
// TODO: display
generic_params: _,
} = self;

if pub_qual.is_some() {
Expand Down
1 change: 1 addition & 0 deletions crates/parser/src/grammar.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod contracts;
pub mod expressions;
pub mod functions;
pub mod generics;
pub mod module;
pub mod types;
89 changes: 3 additions & 86 deletions crates/parser/src/grammar/functions.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use super::expressions::parse_expr;
use super::generics::parse_generic_params;
use super::types::parse_type_desc;

use crate::ast::{
BinOperator, Expr, FuncStmt, Function, FunctionArg, FunctionSignature, GenericParameter,
LiteralPattern, MatchArm, Path, Pattern, TypeDesc, VarDeclTarget,
BinOperator, Expr, FuncStmt, Function, FunctionArg, FunctionSignature, LiteralPattern,
MatchArm, Path, Pattern, VarDeclTarget,
};
use crate::node::{Node, Span};
use crate::{Label, ParseFailed, ParseResult, Parser, TokenKind};
Expand Down Expand Up @@ -120,90 +121,6 @@ pub fn parse_fn_def(par: &mut Parser, pub_qual: Option<Span>) -> ParseResult<Nod
))
}

/// Parse a single generic function parameter (eg. `T:SomeTrait` in `fn foo<T:
/// SomeTrait>(some_arg: u256) -> bool`). # Panics
/// Panics if the first token isn't `Name`.
pub fn parse_generic_param(par: &mut Parser) -> ParseResult<GenericParameter> {
use TokenKind::*;

let name = par.assert(Name);
match par.optional(Colon) {
Some(_) => {
let bound = par.expect(TokenKind::Name, "failed to parse generic bound")?;
Ok(GenericParameter::Bounded {
name: Node::new(name.text.into(), name.span),
bound: Node::new(
TypeDesc::Base {
base: bound.text.into(),
},
bound.span,
),
})
}
None => Ok(GenericParameter::Unbounded(Node::new(
name.text.into(),
name.span,
))),
}
}

/// Parse an angle-bracket-wrapped list of generic arguments (eg. `<T, R:
/// SomeTrait>` in `fn foo<T, R: SomeTrait>(some_arg: u256) -> bool`). # Panics
/// Panics if the first token isn't `<`.
pub fn parse_generic_params(par: &mut Parser) -> ParseResult<Node<Vec<GenericParameter>>> {
use TokenKind::*;
let mut span = par.assert(Lt).span;

let mut args = vec![];

let expect_end = |par: &mut Parser| {
// If there's no comma, the next token must be `>`
match par.peek_or_err()? {
Gt => Ok(par.next()?.span),
_ => {
let tok = par.next()?;
par.unexpected_token_error(
&tok,
"Unexpected token while parsing generic arg list",
vec!["Expected a `>` here".to_string()],
);
Err(ParseFailed)
}
}
};

loop {
match par.peek_or_err()? {
Gt => {
span += par.next()?.span;
break;
}
Name => {
let typ = parse_generic_param(par)?;
args.push(typ);
if par.peek() == Some(Comma) {
par.next()?;
} else {
span += expect_end(par)?;
break;
}
}

// Invalid generic argument.
_ => {
let tok = par.next()?;
par.unexpected_token_error(
&tok,
"failed to parse list of generic function parameters",
vec!["Expected a generic parameter name such as `T` here".to_string()],
);
return Err(ParseFailed);
}
}
}
Ok(Node::new(args, span))
}

fn parse_fn_param_list(par: &mut Parser) -> ParseResult<Node<Vec<Node<FunctionArg>>>> {
let mut span = par.assert(TokenKind::ParenOpen).span;
let mut params = vec![];
Expand Down
87 changes: 87 additions & 0 deletions crates/parser/src/grammar/generics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
use crate::ast::{GenericParameter, TypeDesc};
use crate::node::Node;
use crate::{ParseFailed, ParseResult, Parser, TokenKind};

/// Parse a single generic parameter (eg. `T:SomeTrait` in `fn foo<T: SomeTrait>(some_arg: u256) -> bool`).
/// # Panics
/// Panics if the first token isn't `Name`.
pub fn parse_generic_param(par: &mut Parser) -> ParseResult<GenericParameter> {
use TokenKind::*;

let name = par.assert(Name);
match par.optional(Colon) {
Some(_) => {
let bound = par.expect(TokenKind::Name, "failed to parse generic bound")?;
Ok(GenericParameter::Bounded {
name: Node::new(name.text.into(), name.span),
bound: Node::new(
TypeDesc::Base {
base: bound.text.into(),
},
bound.span,
),
})
}
None => Ok(GenericParameter::Unbounded(Node::new(
name.text.into(),
name.span,
))),
}
}

/// Parse an angle-bracket-wrapped list of generic arguments (eg. `<T, R: SomeTrait>` in `fn foo<T, R: SomeTrait>(some_arg: u256) -> bool`).
/// # Panics
/// Panics if the first token isn't `<`.
pub fn parse_generic_params(par: &mut Parser) -> ParseResult<Node<Vec<GenericParameter>>> {
use TokenKind::*;
let mut span = par.assert(Lt).span;

let mut args = vec![];

let expect_end = |par: &mut Parser| {
// If there's no comma, the next token must be `>`
match par.peek_or_err()? {
Gt => Ok(par.next()?.span),
_ => {
let tok = par.next()?;
par.unexpected_token_error(
&tok,
"Unexpected token while parsing generic arg list",
vec!["Expected a `>` here".to_string()],
);
Err(ParseFailed)
}
}
};

loop {
match par.peek_or_err()? {
Gt => {
span += par.next()?.span;
break;
}
Name => {
let typ = parse_generic_param(par)?;
args.push(typ);
if par.peek() == Some(Comma) {
par.next()?;
} else {
span += expect_end(par)?;
break;
}
}

// Invalid generic argument.
_ => {
let tok = par.next()?;
par.unexpected_token_error(
&tok,
"failed to parse list of generic parameters",
vec!["Expected a generic parameter name such as `T` here".to_string()],
);
return Err(ParseFailed);
}
}
}
Ok(Node::new(args, span))
}
11 changes: 10 additions & 1 deletion crates/parser/src/grammar/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use if_chain::if_chain;
use smol_str::SmolStr;
use vec1::Vec1;

use super::generics::parse_generic_params;

/// Parse a [`ModuleStmt::Struct`].
/// # Panics
/// Panics if the next token isn't `struct`.
Expand All @@ -26,11 +28,17 @@ pub fn parse_struct_def(
let mut span = struct_tok.span + name.span;
let mut fields = vec![];
let mut functions = vec![];

let generic_params = if par.peek() == Some(TokenKind::Lt) {
parse_generic_params(par)?
} else {
Node::new(vec![], name.span)
};

par.enter_block(span, "struct body must start with `{`")?;

loop {
par.eat_newlines();

let attributes = if let Some(attr) = par.optional(TokenKind::Hash) {
let attr_name = par.expect_with_notes(TokenKind::Name, "failed to parse attribute definition", |_|
vec!["Note: an attribute name must start with a letter or underscore, and contain letters, numbers, or underscores".into()])?;
Expand Down Expand Up @@ -74,6 +82,7 @@ pub fn parse_struct_def(
fields,
functions,
pub_qual,
generic_params,
},
span,
))
Expand Down
Loading