mod special_functions;
mod special_properties;
use crate::internal::as2::error::ParsingError;
use crate::internal::as2::global_types::GLOBAL_TYPES;
use crate::internal::as2::hir::EnumeratorTarget;
use crate::internal::as2::hir::scope::Scope;
use crate::internal::as2::lexer::Lexer;
use crate::internal::as2::resolver::special_functions::resolve_special_call;
use crate::internal::as2::{ast, type_path_to_file_path};
use crate::internal::as2::{hir, parser};
use crate::internal::span::{Span, Spanned};
use crate::provider::SourceProvider;
use crate::sources::SourceSet;
use indexmap::{IndexMap, IndexSet};
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
struct ModuleContext<'a> {
imports: HashMap<String, Vec<String>>,
errors: Vec<ParsingError>,
dependencies: IndexSet<String>,
provider: &'a dyn SourceProvider,
source_set: &'a mut SourceSet,
known_script_paths: &'a IndexSet<String>,
is_script: bool,
class_name: Option<Spanned<String>>,
super_name: Option<Spanned<String>>,
class_members: HashMap<String, bool>,
}
impl<'a> ModuleContext<'a> {
fn new(
provider: &'a dyn SourceProvider,
source_set: &'a mut SourceSet,
is_script: bool,
class_name: Option<Spanned<String>>,
known_script_paths: &'a IndexSet<String>,
) -> Self {
Self {
imports: HashMap::new(),
errors: vec![],
dependencies: IndexSet::new(),
provider,
source_set,
is_script,
class_name,
super_name: None,
class_members: HashMap::new(),
known_script_paths,
}
}
fn set_super_name(&mut self, name: Option<Spanned<String>>) {
self.super_name = name;
}
fn import(&mut self, span: Span, path: Vec<String>, name: String) {
if path.is_empty() {
self.add_dependency(span, &name, true);
} else {
self.add_dependency(span, &format!("{}.{}", path.join("."), name), true);
}
self.imports.insert(name, path);
}
fn error(&mut self, message: impl ToString, span: Span) {
self.errors
.push(ParsingError::new(message.to_string(), span));
}
fn expand_identifier(&self, name: String, span: Span) -> Option<hir::Expr> {
if let Some(is_static) = self.class_members.get(&name)
&& let Some(class_name) = &self.class_name
{
let parent = if *is_static {
Box::new(hir::Expr::new(
span,
hir::ExprKind::Constant(hir::ConstantKind::Identifier(
class_name.value.clone(),
)),
))
} else {
Box::new(hir::Expr::new(
span,
hir::ExprKind::Constant(hir::ConstantKind::Identifier("this".to_owned())),
))
};
return Some(hir::Expr::new(
span,
hir::ExprKind::Field(
parent,
Box::new(hir::Expr::new(
span,
hir::ExprKind::Constant(hir::ConstantKind::String(name)),
)),
),
));
}
if let Some(path) = self.imports.get(&name)
&& !path.is_empty()
{
let mut parent = Box::new(hir::Expr::new(
span,
hir::ExprKind::Constant(hir::ConstantKind::Identifier(
path.first().unwrap().to_owned(),
)),
));
let wrap_parent = |parent: Box<hir::Expr>, child: String| {
hir::Expr::new(
span,
hir::ExprKind::Field(
parent,
Box::new(hir::Expr::new(
span,
hir::ExprKind::Constant(hir::ConstantKind::String(child)),
)),
),
)
};
for segment in path.iter().skip(1) {
parent = Box::new(wrap_parent(parent, segment.to_owned()));
}
return Some(wrap_parent(parent, name));
}
None
}
fn expand_typename(&self, name: &str) -> String {
if let Some(path) = self.imports.get(name) {
if path.is_empty() {
name.to_string()
} else {
format!("{}.{name}", path.join("."))
}
} else {
name.to_owned()
}
}
fn add_dependency(&mut self, span: Span, name: &str, required: bool) -> bool {
if GLOBAL_TYPES.contains(&name) {
return true;
}
let path = type_path_to_file_path(name);
if self.known_script_paths.contains(&path) || !self.provider.is_file(&path) {
if required {
self.error(
format!("The class or interface `{}` could not be loaded.", name),
span,
);
}
return false;
}
self.dependencies.insert(name.to_owned());
true
}
fn add_dependency_by_path(&mut self, expr: &hir::Expr, required: bool) -> bool {
if let Ok(path) = identifiers_to_path(expr) {
self.add_dependency(expr.span, &path.join("."), required)
} else {
false
}
}
fn add_class_member(&mut self, name: &str, is_static: bool) {
self.class_members.insert(name.to_owned(), is_static);
}
}
pub fn identifiers_to_path(mut expr: &hir::ExprKind) -> Result<Vec<&str>, ()> {
let mut path = vec![];
loop {
match expr {
hir::ExprKind::Constant(hir::ConstantKind::Identifier(identifier)) => {
path.push(identifier.as_str());
break;
}
hir::ExprKind::Field(parent, child) => {
match &child.value {
hir::ExprKind::Constant(hir::ConstantKind::String(identifier)) => {
path.push(identifier.as_str());
}
_ => return Err(()),
}
expr = &parent.value;
}
_ => return Err(()),
}
}
path.reverse();
Ok(path)
}
pub fn resolve_hir<P: SourceProvider>(
provider: &P,
source_set: &mut SourceSet,
ast: ast::Document,
is_script: bool,
expected_name: &str,
known_script_paths: &IndexSet<String>,
) -> (hir::Document, Vec<ParsingError>, IndexSet<String>) {
let mut context = ModuleContext::new(
provider,
source_set,
is_script,
if expected_name.is_empty() {
None
} else {
Some(Spanned::new(Span::default(), expected_name.to_owned()))
},
known_script_paths,
);
let document = if is_script {
let mut statements = resolve_statement_vec(&mut context, &ast.statements);
let scope = Scope::for_root(&mut statements);
hir::Document::Script { statements, scope }
} else {
resolve_class_or_interface(&mut context, &ast.statements, expected_name)
};
(document, context.errors, context.dependencies)
}
fn resolve_statement_vec(
context: &mut ModuleContext,
input: &[ast::Statement],
) -> Vec<hir::StatementKind> {
input
.iter()
.map(|statement| resolve_statement(context, statement))
.collect()
}
fn resolve_class_or_interface(
context: &mut ModuleContext,
input: &[ast::Statement],
expected_name: &str,
) -> hir::Document {
let mut result = None;
for statement in input {
match &statement.value {
ast::StatementKind::Import(import) => {
context.import(
statement.span,
import.path.iter().map(|s| (*s).to_owned()).collect(),
(*import.name).to_owned(),
);
}
ast::StatementKind::Interface {
name,
extends,
body,
} => {
context.set_super_name(extends.clone());
if name.value == expected_name {
if result.is_some() {
context.error(
"Only one class or interface can be defined per ActionScript 2.0 .as file.",
statement.span,
);
continue;
}
result = Some(hir::Document::Interface(resolve_interface(
context,
name.value.to_owned(),
extends.clone(),
body,
)));
} else {
context.error(
format!("The class '{0}' needs to be defined in a file whose relative path is '{0}.as'.", name.value), name.span
);
}
}
ast::StatementKind::Class {
name,
extends,
implements,
members,
} => {
context.set_super_name(extends.clone());
if name.value == expected_name {
if result.is_some() {
context.error(
"Only one class or interface can be defined per ActionScript 2.0 .as file.",
statement.span,
);
continue;
}
result = Some(hir::Document::Class(Box::new(resolve_class(
context,
Spanned::new(name.span, name.value.to_owned()),
extends.clone(),
implements,
members,
))));
} else {
context.error(
format!("The class '{0}' needs to be defined in a file whose relative path is '{0}.as'.", name.value), name.span
);
}
}
_ => context.error(
"ActionScript 2.0 class scripts may only define class or interface constructs.",
statement.span,
),
}
}
result.unwrap_or(hir::Document::Invalid)
}
fn resolve_interface(
context: &mut ModuleContext,
name: String,
extends: Option<Spanned<String>>,
body: &[Spanned<ast::FunctionSignature>],
) -> hir::Interface {
let expanded_extends = if let Some(extends) = &extends {
let expanded = context.expand_typename(&extends.value);
context.add_dependency(extends.span, &expanded, true);
Some(expanded)
} else {
None
};
let mut functions = IndexMap::new();
for function in body {
let Some(function_name) = function.name else {
context.error("All member functions need to have names.", function.span);
continue;
};
if function.function_type != ast::FunctionType::Regular {
context.error(
"Getter/setter declarations are not permitted in interfaces.",
function.span,
);
continue;
}
if functions
.insert(
function_name.value.to_owned(),
hir::FunctionSignature {
name: Some(Spanned::new(
function_name.span,
function_name.value.to_owned(),
)),
args: function
.args
.iter()
.map(|arg| hir::FunctionArgument {
name: arg.name.to_owned(),
type_name: resolve_opt_type_name(context, &arg.type_name),
register: None,
})
.collect(),
return_type: resolve_opt_type_name(context, &function.return_type),
},
)
.is_some()
{
context.error(
"The same member name may not be repeated more than once.",
function_name.span,
);
}
}
hir::Interface {
name,
extends: expanded_extends,
functions,
}
}
fn resolve_class(
context: &mut ModuleContext,
class_name: Spanned<String>,
extends: Option<Spanned<String>>,
implements: &[Spanned<String>],
members: &[(
Spanned<ast::ClassMember>,
HashMap<ast::ClassMemberAttribute, Span>,
)],
) -> hir::Class {
let segments: Vec<&str> = class_name.split(".").collect();
if segments.len() > 1 {
context.import(
class_name.span,
segments[..segments.len() - 1]
.iter()
.map(|s| (*s).to_owned())
.collect(),
segments[segments.len() - 1].to_owned(),
);
}
let expanded_extends = if let Some(extends) = &extends {
let expanded = context.expand_typename(&extends.value);
context.add_dependency(extends.span, &expanded, true);
Some(expanded)
} else {
None
};
let mut expanded_implements = vec![];
for interface in implements {
let expanded = context.expand_typename(&interface.value);
context.add_dependency(interface.span, &expanded, true);
expanded_implements.push(expanded);
}
let mut seen_names = HashSet::new();
let mut fields = IndexMap::new();
let mut functions = IndexMap::new();
let mut virtual_properties = IndexMap::new();
let mut constructor = None;
for (member, attributes) in members {
match &member.value {
ast::ClassMember::Function(function) => {
let Some(function_name) = function.signature.name else {
context.error("All member functions need to have names.", member.span);
continue;
};
let internal_name = match function.signature.function_type {
ast::FunctionType::Getter => format!("__get__{}", function_name.value),
ast::FunctionType::Setter => format!("__set__{}", function_name.value),
ast::FunctionType::Regular => function_name.value.to_owned(),
};
if !seen_names.insert(internal_name.to_owned()) {
context.error(
"The same member name may not be repeated more than once.",
function_name.span,
);
continue;
}
if function.signature.function_type != ast::FunctionType::Regular {
let property = virtual_properties
.entry(function_name.value.to_owned())
.or_insert_with(|| hir::VirtualProperty {
name: function_name.value.to_owned(),
getter: None,
setter: None,
is_static: attributes.contains_key(&ast::ClassMemberAttribute::Static),
});
match function.signature.function_type {
ast::FunctionType::Regular => unreachable!(),
ast::FunctionType::Getter => {
property.getter = Some(internal_name.clone());
}
ast::FunctionType::Setter => {
property.setter = Some(internal_name.clone());
}
}
}
if function_name.value == class_name.value {
if let Some(attr_span) = attributes.get(&ast::ClassMemberAttribute::Static) {
context.error("The only attributes allowed for constructor functions are public and private.", *attr_span);
continue;
}
if function.signature.function_type != ast::FunctionType::Regular {
context.error("The only attributes allowed for constructor functions are public and private.", function_name.span);
continue;
}
constructor = Some(function);
} else {
context.add_class_member(
function_name.value,
attributes.contains_key(&ast::ClassMemberAttribute::Static),
);
functions.insert(internal_name, (function, attributes));
}
}
ast::ClassMember::Variable(declaration) => {
if !seen_names.insert(declaration.name.value.to_owned()) {
context.error(
"The same member name may not be repeated more than once.",
declaration.name.span,
);
continue;
}
context.add_class_member(
declaration.name.value,
attributes.contains_key(&ast::ClassMemberAttribute::Static),
);
if let Some(value) = &declaration.value {
match &value.value {
ast::ExprKind::Constant(
ast::ConstantKind::String(_)
| ast::ConstantKind::Float(_)
| ast::ConstantKind::Integer(_),
) => {}
_ => {
context.error(
"Variables may only be initialized with constants.",
value.span,
);
continue;
}
};
}
fields.insert(
declaration.name.value.to_owned(),
hir::Field {
type_name: resolve_opt_type_name(context, &declaration.type_name),
value: declaration
.value
.as_ref()
.map(|expr| resolve_expr(context, expr)),
is_static: attributes.contains_key(&ast::ClassMemberAttribute::Static),
},
);
}
}
}
let mut resolved_functions = IndexMap::new();
for (name, (function, attributes)) in functions {
resolved_functions.insert(
name,
hir::Method {
function: resolve_function(
context,
function,
attributes.contains_key(&ast::ClassMemberAttribute::Static),
),
is_static: attributes.contains_key(&ast::ClassMemberAttribute::Static),
},
);
}
let resolved_constructor = constructor
.map(|function| resolve_function(context, function, false))
.unwrap_or_else(|| hir::Function {
signature: hir::FunctionSignature {
name: Some(class_name.clone()),
..Default::default()
},
body: vec![], ..Default::default()
});
hir::Class {
name: class_name.value,
extends: expanded_extends,
implements: expanded_implements,
functions: resolved_functions,
fields,
constructor: resolved_constructor,
virtual_properties,
}
}
fn resolve_statement_box(
context: &mut ModuleContext,
input: &ast::Statement,
) -> Box<hir::StatementKind> {
Box::new(resolve_statement(context, input))
}
fn resolve_statement(context: &mut ModuleContext, input: &ast::Statement) -> hir::StatementKind {
match &input.value {
ast::StatementKind::Include(path) => {
let source_file = match context
.source_set
.get_or_load(path.to_string(), context.provider)
{
Ok(source_file) => source_file,
Err(e) => {
context.errors.push(ParsingError::new(
format!("Could not load file '{}': {}", path, e),
input.span,
));
return hir::StatementKind::Block(vec![]);
}
};
let tokens = Lexer::new(&source_file.source, source_file.file_id).into_vec();
let ast = match parser::parse_document(&tokens) {
Ok(ast) => ast,
Err(e) => {
context.errors.push(e.into());
return hir::StatementKind::Block(vec![]);
}
};
hir::StatementKind::Block(resolve_statement_vec(context, &ast.statements))
}
ast::StatementKind::Declare(declarations) => {
if let Some(declaration) = declarations.first()
&& declarations.len() == 1
{
hir::StatementKind::Declare(Box::new(resolve_declaration(context, declaration)))
} else {
hir::StatementKind::Block(
declarations
.iter()
.map(|d| {
hir::StatementKind::Declare(Box::new(resolve_declaration(context, d)))
})
.collect(),
)
}
}
ast::StatementKind::Return(values) => {
hir::StatementKind::Return(resolve_expr_vec(context, values))
}
ast::StatementKind::Throw(values) => {
hir::StatementKind::Throw(resolve_expr_vec(context, values))
}
ast::StatementKind::Expr(expr) => hir::StatementKind::Expr(resolve_expr(context, expr)),
ast::StatementKind::Block(statements) => {
hir::StatementKind::Block(resolve_statement_vec(context, statements))
}
ast::StatementKind::ForIn { condition, body } => hir::StatementKind::ForIn {
condition: resolve_for_condition(context, condition),
body: resolve_statement_box(context, body),
},
ast::StatementKind::While { condition, body } => hir::StatementKind::While {
condition: resolve_expr(context, condition),
body: resolve_statement_box(context, body),
},
ast::StatementKind::If { condition, yes, no } => hir::StatementKind::If {
condition: resolve_expr(context, condition),
yes: resolve_statement_box(context, yes),
no: no
.as_ref()
.map(|statement| resolve_statement_box(context, statement)),
},
ast::StatementKind::Break => hir::StatementKind::Break,
ast::StatementKind::Continue => hir::StatementKind::Continue,
ast::StatementKind::Try(try_catch) => hir::StatementKind::Try(hir::TryCatch {
try_body: resolve_statement_vec(context, &try_catch.try_body),
catch_all: try_catch
.catch_all
.as_ref()
.map(|catch| resolve_catch(context, catch)),
typed_catches: try_catch
.typed_catches
.iter()
.map(|(type_name, catch)| {
(
resolve_type_name(context, type_name),
resolve_catch(context, catch),
)
})
.collect(),
finally: resolve_statement_vec(context, &try_catch.finally),
}),
ast::StatementKind::WaitForFrame {
frame,
scene,
if_loaded,
} => hir::StatementKind::WaitForFrame {
frame: resolve_expr(context, frame),
scene: scene.as_ref().map(|expr| resolve_expr(context, expr)),
if_loaded: resolve_statement_box(context, if_loaded),
},
ast::StatementKind::TellTarget { target, body } => hir::StatementKind::TellTarget {
target: resolve_expr(context, target),
body: resolve_statement_box(context, body),
},
ast::StatementKind::InlinePCode(pcode) => {
hir::StatementKind::InlinePCode((*pcode).to_owned())
}
ast::StatementKind::With { target, body } => hir::StatementKind::With {
target: resolve_expr(context, target),
body: resolve_statement_box(context, body),
},
ast::StatementKind::Switch { target, elements } => hir::StatementKind::Switch {
target: resolve_expr(context, target),
elements: elements
.iter()
.map(|element| resolve_switch_element(context, element))
.collect(),
},
ast::StatementKind::Import(import) => {
context.import(
input.span,
import.path.iter().map(|s| (*s).to_owned()).collect(),
(*import.name).to_owned(),
);
hir::StatementKind::Block(vec![]) }
ast::StatementKind::Interface { .. } | ast::StatementKind::Class { .. } => {
if context.is_script {
context.error(
"Classes may only be defined in external ActionScript 2.0 class scripts.",
input.span,
);
hir::StatementKind::Block(vec![]) } else {
context.error(
"Class and interface definitions cannot be nested.",
input.span,
);
hir::StatementKind::Block(vec![]) }
}
}
}
fn resolve_for_condition(
context: &mut ModuleContext,
input: &ast::ForCondition,
) -> hir::ForCondition {
match input {
ast::ForCondition::Enumerate {
variable,
declare,
object,
} => hir::ForCondition::Enumerate {
target: EnumeratorTarget::Variable {
name: (*variable).to_owned(),
declare: *declare,
},
object: resolve_expr(context, object),
},
ast::ForCondition::Classic {
initialize,
condition,
update,
} => hir::ForCondition::Classic {
initialize: initialize
.as_ref()
.map(|statement| resolve_statement_box(context, statement)),
condition: resolve_expr_vec(context, condition),
update: resolve_expr_vec(context, update),
},
}
}
fn resolve_declaration(context: &mut ModuleContext, input: &ast::Declaration) -> hir::Declaration {
hir::Declaration {
name: Spanned::new(input.name.span, input.name.value.to_owned()),
type_name: resolve_opt_type_name(context, &input.type_name),
value: input.value.as_ref().map(|expr| resolve_expr(context, expr)),
}
}
fn resolve_catch(context: &mut ModuleContext, input: &ast::Catch) -> hir::Catch {
hir::Catch {
name: Spanned::new(input.name.span, input.name.value.to_owned()),
body: resolve_statement_vec(context, &input.body),
}
}
fn resolve_switch_element(
context: &mut ModuleContext,
input: &ast::SwitchElement,
) -> hir::SwitchElement {
match input {
ast::SwitchElement::Case(expr) => hir::SwitchElement::Case(resolve_expr(context, expr)),
ast::SwitchElement::Default => hir::SwitchElement::Default,
ast::SwitchElement::Statement(statement) => {
hir::SwitchElement::Statement(resolve_statement(context, statement))
}
}
}
fn resolve_expr_box(context: &mut ModuleContext, input: &ast::Expr) -> Box<hir::Expr> {
Box::new(resolve_expr(context, input))
}
fn resolve_expr_vec(context: &mut ModuleContext, input: &[ast::Expr]) -> Vec<hir::Expr> {
input
.iter()
.map(|expr| resolve_expr(context, expr))
.collect()
}
fn resolve_constant(constant: &ast::ConstantKind) -> hir::ConstantKind {
match constant {
ast::ConstantKind::String(v) => hir::ConstantKind::String(v.to_string()),
ast::ConstantKind::Identifier(v) => match *v {
"true" => hir::ConstantKind::Boolean(true),
"false" => hir::ConstantKind::Boolean(false),
other => hir::ConstantKind::Identifier(other.to_owned()),
},
ast::ConstantKind::Float(v) => hir::ConstantKind::Float(*v),
ast::ConstantKind::Integer(v) => hir::ConstantKind::Integer(*v),
}
}
fn resolve_expr(context: &mut ModuleContext, input: &ast::Expr) -> hir::Expr {
let span = input.span;
let result = match &input.value {
ast::ExprKind::Constant(ast::ConstantKind::Identifier(identifier)) => {
if let Some(path) = context.expand_identifier((*identifier).to_owned(), input.span) {
return path;
} else {
hir::ExprKind::Constant(resolve_constant(&ast::ConstantKind::Identifier(
identifier,
)))
}
}
ast::ExprKind::Constant(value) => hir::ExprKind::Constant(resolve_constant(value)),
ast::ExprKind::Call { name, args } => resolve_call(context, span, name, args),
ast::ExprKind::New { name, args } => {
if let ast::ExprKind::Constant(ast::ConstantKind::Identifier(identifier)) = &name.value
{
context.add_dependency(span, identifier, false);
}
hir::ExprKind::New {
name: resolve_expr_box(context, name),
args: resolve_expr_vec(context, args),
}
}
ast::ExprKind::BinaryOperator(op, left, right) => hir::ExprKind::BinaryOperator(
*op,
resolve_expr_box(context, left),
resolve_expr_box(context, right),
),
ast::ExprKind::UnaryOperator(op, value) => {
hir::ExprKind::UnaryOperator(*op, resolve_expr_box(context, value))
}
ast::ExprKind::Parenthesis(expr) => return resolve_expr(context, expr),
ast::ExprKind::Ternary { condition, yes, no } => hir::ExprKind::Ternary {
condition: resolve_expr_box(context, condition),
yes: resolve_expr_box(context, yes),
no: resolve_expr_box(context, no),
},
ast::ExprKind::InitObject(values) => hir::ExprKind::InitObject(
values
.iter()
.map(|(name, value)| ((*name).to_owned(), resolve_expr(context, value)))
.collect(),
),
ast::ExprKind::InitArray(values) => hir::ExprKind::InitArray(
values
.iter()
.map(|expr| resolve_expr(context, expr))
.collect(),
),
ast::ExprKind::Field(object, field) => {
if let Some(path) = try_field_to_path(&object.value, &field.value) {
context.add_dependency(span, &path, false);
}
hir::ExprKind::Field(
resolve_expr_box(context, object),
resolve_expr_box(context, field),
)
}
ast::ExprKind::TypeOf(value) => hir::ExprKind::TypeOf(resolve_expr_box(context, value)),
ast::ExprKind::Delete(value) => hir::ExprKind::Delete(resolve_expr_box(context, value)),
ast::ExprKind::Void(value) => hir::ExprKind::Void(resolve_expr_box(context, value)),
ast::ExprKind::Function(function) => {
if function.signature.function_type != ast::FunctionType::Regular {
context.error(
"Getter/setter declarations are not permitted here.",
input.span,
);
}
hir::ExprKind::Function(Box::new(resolve_function(context, function, false)))
}
ast::ExprKind::GetVariable(name) => {
hir::ExprKind::GetVariable(resolve_expr_box(context, name))
}
ast::ExprKind::SetVariable(name, value) => hir::ExprKind::SetVariable(
resolve_expr_box(context, name),
resolve_expr_box(context, value),
),
};
hir::Expr::new(input.span, result)
}
fn try_field_to_path(obj: &ast::ExprKind, key: &ast::ExprKind) -> Option<String> {
let mut path = vec![];
if let ast::ExprKind::Constant(ast::ConstantKind::String(identifier)) = key {
path.push(identifier.as_ref());
} else {
return None;
}
let mut parent = obj;
loop {
match parent {
ast::ExprKind::Constant(ast::ConstantKind::Identifier(identifier)) => {
path.push(identifier);
break;
}
ast::ExprKind::Field(obj, key) => {
parent = obj;
if let ast::ExprKind::Constant(ast::ConstantKind::String(key)) = &key.value {
path.push(key);
} else {
return None;
}
}
_ => return None,
}
}
Some(
path.iter()
.rev()
.map(|s| (*s).to_owned())
.collect::<Vec<_>>()
.join("."),
)
}
fn resolve_call(
context: &mut ModuleContext,
span: Span,
name: &ast::Expr,
args: &[ast::Expr],
) -> hir::ExprKind {
if let ast::Expr {
value: ast::ExprKind::Constant(ast::ConstantKind::Identifier(name)),
..
} = *name
&& let Some(special) = resolve_special_call(context, span, name, args)
{
return special;
}
let name = resolve_expr_box(context, name);
let mut args = resolve_expr_vec(context, args);
if context.add_dependency_by_path(&name, false)
&& args.len() == 1
&& let Some(object) = args.pop()
{
return hir::ExprKind::CastToObject {
class: name,
object: Box::new(object),
};
}
hir::ExprKind::Call { name, args }
}
fn resolve_function(
context: &mut ModuleContext,
input: &ast::Function,
is_static: bool,
) -> hir::Function {
let mut body = resolve_statement_vec(context, &input.body);
if let Some(name) = input.signature.name
&& input.signature.function_type == ast::FunctionType::Setter
{
let mut getter = Box::new(ast::Expr::new(
name.span,
ast::ExprKind::Constant(ast::ConstantKind::String(Cow::Owned(format!(
"__get__{}",
name.value
)))),
));
if !is_static {
getter = Box::new(ast::Expr::new(
name.span,
ast::ExprKind::Field(
Box::new(ast::Expr::new(
name.span,
ast::ExprKind::Constant(ast::ConstantKind::Identifier("this")),
)),
getter,
),
))
};
body.push(hir::StatementKind::Return(vec![resolve_expr(
context,
&ast::Expr::new(
name.span,
ast::ExprKind::Call {
name: getter,
args: vec![],
},
),
)]))
}
let args = input
.signature
.args
.iter()
.map(|arg| hir::FunctionArgument {
name: arg.name.to_owned(),
type_name: resolve_opt_type_name(context, &arg.type_name),
register: None,
})
.collect();
let scope = Scope::for_function(
&args,
&mut body,
context.class_name.clone(),
context.super_name.clone(),
);
hir::Function {
signature: hir::FunctionSignature {
name: input
.signature
.name
.map(|name| Spanned::new(name.span, name.value.to_owned())),
args,
return_type: resolve_opt_type_name(context, &input.signature.return_type),
},
body,
scope,
..Default::default()
}
}
fn resolve_type_name(context: &mut ModuleContext, type_name: &Spanned<&str>) -> Spanned<String> {
let expanded = context.expand_typename(type_name.value);
context.add_dependency(type_name.span, &expanded, true);
Spanned::new(type_name.span, expanded)
}
fn resolve_opt_type_name(
context: &mut ModuleContext,
type_name: &Option<Spanned<&str>>,
) -> Option<Spanned<String>> {
type_name
.as_ref()
.map(|type_name| resolve_type_name(context, type_name))
}