use proc_macro2::{Ident, Span};
use syn::{spanned::Spanned, Attribute, Block, Expr, NestedMeta, Pat, Type, Visibility};
use crate::{parse::ModuleKind, util::*};
#[derive(Debug)]
pub(crate) struct StarModule {
pub module_kind: ModuleKind,
pub visibility: Visibility,
pub globals_builder: Type,
pub name: Ident,
pub docstring: Option<String>,
pub stmts: Vec<StarStmt>,
}
impl StarModule {
pub(crate) fn span(&self) -> Span {
let mut span = self.name.span();
for stmt in &self.stmts {
if let Some(new_span) = span.join(stmt.span()) {
span = new_span;
}
}
span
}
}
#[derive(Debug)]
pub(crate) enum StarStmt {
Const(StarConst),
Fun(StarFun),
Attr(StarAttr),
}
impl StarStmt {
pub(crate) fn span(&self) -> Span {
match self {
StarStmt::Const(c) => c.span(),
StarStmt::Fun(c) => c.span(),
StarStmt::Attr(c) => c.span(),
}
}
}
#[derive(Debug)]
pub(crate) struct StarConst {
pub name: Ident,
pub ty: Type,
pub value: Expr,
}
impl StarConst {
pub(crate) fn span(&self) -> Span {
self.name
.span()
.join(self.value.span())
.unwrap_or_else(|| self.name.span())
}
}
#[derive(Debug)]
pub(crate) struct StarFun {
pub name: Ident,
pub type_attribute: Option<NestedMeta>,
pub attrs: Vec<Attribute>,
pub args: Vec<StarArg>,
pub return_type: Type,
pub return_type_arg: Type,
pub speculative_exec_safe: bool,
pub body: Block,
pub source: StarFunSource,
pub docstring: Option<String>,
}
impl StarFun {
pub(crate) fn is_method(&self) -> bool {
match self.args.first() {
Some(first) => {
assert!(first.source != StarArgSource::Unknown, "not yet resolved");
first.source == StarArgSource::This
}
None => false,
}
}
pub(crate) fn span(&self) -> Span {
self.name
.span()
.join(self.body.span())
.unwrap_or_else(|| self.name.span())
}
pub(crate) fn args_span(&self) -> Span {
self.args
.iter()
.map(|a| a.span)
.reduce(|a, b| a.join(b).unwrap_or(a))
.unwrap_or_else(|| self.name.span())
}
}
#[derive(Debug)]
pub(crate) struct StarAttr {
pub name: Ident,
pub arg: Type,
pub attrs: Vec<Attribute>,
pub return_type: Type,
pub return_type_arg: Type,
pub speculative_exec_safe: bool,
pub body: Block,
pub docstring: Option<String>,
}
impl StarAttr {
pub(crate) fn span(&self) -> Span {
self.name
.span()
.join(self.body.span())
.unwrap_or_else(|| self.name.span())
}
}
#[derive(Debug)]
pub(crate) struct StarArg {
pub span: Span,
pub attrs: Vec<Attribute>,
pub mutable: bool,
pub by_ref: bool,
pub name: Ident,
pub ty: Type,
pub default: Option<Pat>,
pub source: StarArgSource,
}
#[derive(Debug, PartialEq)]
pub(crate) enum StarArgSource {
Unknown,
This,
Parameters,
Argument(usize),
Required(usize),
Optional(usize),
}
#[derive(Debug)]
pub(crate) enum StarFunSource {
Unknown,
Parameters,
ThisParameters,
Argument(usize),
Positional(usize, usize),
}
impl StarModule {
pub fn resolve(&mut self) -> syn::Result<()> {
for x in &mut self.stmts {
if let StarStmt::Fun(x) = x {
x.resolve(self.module_kind)?;
}
}
Ok(())
}
}
impl StarFun {
#[allow(clippy::branches_sharing_code)] pub fn resolve(&mut self, module_kind: ModuleKind) -> syn::Result<()> {
fn requires_signature(x: &StarArg) -> bool {
!x.by_ref || x.is_args() || x.is_kwargs() || (x.is_value() && x.default.is_some())
}
if self.args.len() == 1 && self.args[0].is_arguments() {
self.args[0].source = StarArgSource::Parameters;
self.source = StarFunSource::Parameters;
} else if self.args.len() == 2 && self.args[0].is_this() && self.args[1].is_arguments() {
self.args[0].source = StarArgSource::This;
self.args[1].source = StarArgSource::Parameters;
self.source = StarFunSource::ThisParameters;
} else {
let use_arguments = self
.args
.iter()
.filter(|x| !x.is_this())
.any(requires_signature);
if use_arguments {
let mut argument = 0;
for x in &mut self.args {
if x.is_this() {
x.source = StarArgSource::This;
} else {
x.source = StarArgSource::Argument(argument);
argument += 1;
}
}
self.source = StarFunSource::Argument(argument);
} else {
let mut required = 0;
let mut optional = 0;
for x in &mut self.args {
if x.is_this() {
x.source = StarArgSource::This;
continue;
}
if optional == 0 && x.default.is_none() && !x.is_option() {
x.source = StarArgSource::Required(required);
required += 1;
} else {
x.source = StarArgSource::Optional(optional);
optional += 1;
}
}
self.source = StarFunSource::Positional(required, optional);
}
}
if self.is_method() && module_kind != ModuleKind::Methods {
return Err(syn::Error::new(
self.span(),
"Methods can only be defined in methods module",
));
}
Ok(())
}
}
impl StarArg {
pub fn is_arguments(&self) -> bool {
if let Type::Reference(ty) = &self.ty {
is_type_name(&ty.elem, "Arguments")
} else {
false
}
}
pub fn is_option(&self) -> bool {
is_type_name(&self.ty, "Option")
}
pub fn is_value(&self) -> bool {
is_type_name(&self.ty, "Value")
}
pub fn is_this(&self) -> bool {
self.name == "this" || self.name == "_this"
}
pub fn is_args(&self) -> bool {
self.name == "args"
}
pub fn is_kwargs(&self) -> bool {
self.name == "kwargs"
}
}