use std::collections::{HashMap, HashSet};
use crate::declaration::{Declaration, Module};
use crate::expression::{
Binding, Block, Expression, ExpressionKind, Literal, MatchPattern, Span, Statement,
};
use crate::operator::{BinaryOperator, ComparisonOperator};
use crate::types::{IntWidth, Mutability, Signedness, Type};
fn spanned_error(span: Span, message: impl std::fmt::Display) -> String {
if span.line > 0 {
format!("{span}: {message}")
} else {
message.to_string()
}
}
pub(crate) struct VariableInfo {
variable_type: Type,
auto_ref: bool,
}
pub(crate) struct ResolutionContext {
newtypes: HashMap<String, Type>,
return_types: HashMap<String, Type>,
function_parameters: HashMap<String, Vec<Type>>,
constant_types: HashMap<String, Type>,
scopes: Vec<HashMap<String, VariableInfo>>,
pointer_params: HashSet<String>,
variadic_functions: HashSet<String>,
current_return_type: Option<Type>,
}
impl ResolutionContext {
fn new() -> Self {
Self {
newtypes: HashMap::new(),
return_types: HashMap::new(),
function_parameters: HashMap::new(),
constant_types: HashMap::new(),
scopes: Vec::new(),
pointer_params: HashSet::new(),
variadic_functions: HashSet::new(),
current_return_type: None,
}
}
fn push_scope(&mut self) {
self.scopes.push(HashMap::new());
}
fn pop_scope(&mut self) {
self.scopes.pop();
}
fn define_variable(&mut self, name: String, variable_type: Type, auto_ref: bool) {
if let Some(scope) = self.scopes.last_mut() {
scope.insert(
name,
VariableInfo {
variable_type,
auto_ref,
},
);
}
}
fn lookup_variable_type(&self, name: &str) -> Option<Type> {
for scope in self.scopes.iter().rev() {
if let Some(info) = scope.get(name) {
return Some(info.variable_type.clone());
}
}
self.constant_types.get(name).cloned()
}
fn is_auto_ref(&self, name: &str) -> bool {
self.scopes
.iter()
.rev()
.any(|scope| scope.get(name).is_some_and(|info| info.auto_ref))
}
fn is_pointer_param(&self, name: &str) -> bool {
self.pointer_params.contains(name)
}
fn is_known_name(&self, name: &str) -> bool {
self.scopes.iter().any(|scope| scope.contains_key(name))
|| self.constant_types.contains_key(name)
|| self.return_types.contains_key(name)
|| self.newtypes.contains_key(name)
}
fn lookup_function_return(&self, name: &str) -> Option<Type> {
self.return_types.get(name).cloned()
}
fn lookup_function_params(&self, name: &str) -> Option<&Vec<Type>> {
self.function_parameters.get(name)
}
fn lookup_field(&self, type_name: &str, field_name: &str) -> Option<Type> {
let inner = self.newtypes.get(type_name)?;
if let Type::Tuple(fields) = inner {
fields.iter().find_map(|f| {
if let Type::Named(name) = f
&& name == field_name
{
Some(f.clone())
} else {
None
}
})
} else if inner.matches_name(field_name) {
Some(inner.clone())
} else {
None
}
}
fn resolve_named(&self, ty: &Type) -> Type {
self.resolve_named_depth(ty, 0)
}
fn resolve_named_depth(&self, ty: &Type, depth: usize) -> Type {
if depth > 100 {
return ty.clone();
}
if let Type::Named(name) = ty
&& let Some(inner) = self.newtypes.get(name)
{
return self.resolve_named_depth(inner, depth + 1);
}
ty.clone()
}
fn strip_wrappers(ty: &Type) -> &Type {
match ty {
Type::Pointer(_, inner) | Type::Array(inner, _) | Type::Slice(_, inner) => {
Self::strip_wrappers(inner)
}
Type::Vector(inner, _) => Self::strip_wrappers(inner),
other => other,
}
}
fn is_in_type_chain(&self, needle: &Type, haystack: &Type) -> bool {
let mut current = haystack.clone();
loop {
if let Type::Named(name) = ¤t
&& let Some(inner) = self.newtypes.get(name)
{
if inner == needle {
return true;
}
current = inner.clone();
continue;
}
return false;
}
}
fn types_conflict(&self, type_a: &Type, type_b: &Type) -> bool {
let core_a = Self::strip_wrappers(type_a);
let core_b = Self::strip_wrappers(type_b);
if core_a == core_b {
return true;
}
self.is_in_type_chain(core_a, core_b) || self.is_in_type_chain(core_b, core_a)
}
fn resolve_underlying(&self, resolved_type: &Type) -> Type {
match resolved_type {
Type::Named(name) => self.newtypes.get(name).map_or_else(
|| resolved_type.clone(),
|inner| self.resolve_underlying(inner),
),
Type::Pointer(mutability, inner) => {
Type::Pointer(*mutability, Box::new(self.resolve_underlying(inner)))
}
other => other.clone(),
}
}
}
pub fn resolve_module(module: &mut Module, check_duplicate_types: bool) -> Vec<String> {
let mut context = ResolutionContext::new();
let mut errors = Vec::new();
for declaration in module.iter() {
match declaration {
Declaration::Type(newtype) => {
if context.newtypes.contains_key(&newtype.name) {
errors.push(format!("duplicate type definition: '{}'", newtype.name));
}
context
.newtypes
.insert(newtype.name.clone(), newtype.inner_type.clone());
}
Declaration::Function(function) => {
context
.return_types
.insert(function.name.clone(), function.return_type.clone());
context.function_parameters.insert(
function.name.clone(),
function
.parameters
.iter()
.filter_map(|p| p.parameter_type.clone())
.collect(),
);
}
Declaration::Extern(ext) => {
context
.return_types
.insert(ext.name.clone(), ext.return_type.clone());
context.function_parameters.insert(
ext.name.clone(),
ext.parameters
.parameters()
.iter()
.filter_map(|p| p.parameter_type.clone())
.collect(),
);
if matches!(
ext.parameters,
crate::declaration::ParameterList::Variadic(_)
) {
context.variadic_functions.insert(ext.name.clone());
}
}
Declaration::Constant(constant) => {
context
.constant_types
.insert(constant.name.clone(), constant.constant_type.clone());
}
Declaration::Import(_) => {}
}
}
for declaration in module.iter_mut() {
match declaration {
Declaration::Function(function) => {
context.push_scope();
context.pointer_params.clear();
context.current_return_type = Some(function.return_type.clone());
for param in &function.parameters {
if let Some(ref parameter_type) = param.parameter_type {
let resolved = context.resolve_underlying(parameter_type);
let auto_ref = matches!(resolved, Type::Pointer(..));
if auto_ref {
context.pointer_params.insert(param.name.clone());
}
context.define_variable(
param.name.clone(),
parameter_type.clone(),
auto_ref,
);
}
}
if check_duplicate_types {
for param in &function.parameters {
if let Some(param_type) = ¶m.parameter_type {
let core = ResolutionContext::strip_wrappers(param_type);
if let Type::Named(type_name) = core {
if !context.newtypes.contains_key(type_name.as_str())
&& !context.return_types.contains_key(type_name.as_str())
{
errors.push(spanned_error(
function.span,
format!(
"in '{}': unknown type '{type_name}'",
function.name
),
));
continue;
}
if param.name != *type_name {
errors.push(spanned_error(
function.span,
format!(
"in '{}': parameter '{}' has named type {type_name} — use '{param_type}' shorthand instead",
function.name, param.name
),
));
}
}
}
}
for (i, param_a) in function.parameters.iter().enumerate() {
for param_b in function.parameters.iter().skip(i + 1) {
if let (Some(type_a), Some(type_b)) =
(¶m_a.parameter_type, ¶m_b.parameter_type)
&& context.types_conflict(type_a, type_b)
{
errors.push(spanned_error(
function.span,
format!(
"in '{}': parameters '{}' ({type_a}) and '{}' ({type_b}) have conflicting types — use distinct newtypes",
function.name, param_a.name, param_b.name
),
));
}
}
}
}
if let Err(error) = resolve_block(&mut function.body, &mut context) {
errors.push(error);
}
context.pop_scope();
context.pointer_params.clear();
context.current_return_type = None;
}
Declaration::Constant(constant) => {
if let Err(error) = resolve_expression(&mut constant.value, &mut context) {
errors.push(error);
}
}
_ => {}
}
}
errors
}
fn resolve_block(block: &mut Block, context: &mut ResolutionContext) -> Result<(), String> {
for statement in &mut block.statements {
resolve_statement(statement, context)?;
}
if let Some(result) = &mut block.result {
resolve_expression(result, context)?;
let return_is_pointer = context
.current_return_type
.as_ref()
.is_some_and(|t| matches!(context.resolve_underlying(t), Type::Pointer(..)));
if !return_is_pointer {
try_auto_deref_in_place(result, context);
}
}
Ok(())
}
fn resolve_statement(
statement: &mut Statement,
context: &mut ResolutionContext,
) -> Result<(), String> {
match statement {
Statement::Expression(expression) => {
resolve_expression(expression, context)?;
}
Statement::Let {
name,
binding,
declared_type,
value,
} => {
resolve_expression(value, context)?;
if matches!(value.kind, ExpressionKind::Literal(Literal::Null))
&& let Some(declared) = declared_type.as_ref()
{
value.resolved_type = Some(declared.clone());
}
try_auto_deref_in_place(value, context);
if let (Some(declared), Some(actual)) =
(declared_type.as_ref(), value.resolved_type.as_ref())
{
let declared_resolved = context.resolve_underlying(declared);
let actual_resolved = context.resolve_underlying(actual);
if !types_compatible(&declared_resolved, &actual_resolved) {
return Err(format!(
"type mismatch in let '{name}': expected {declared}, got {actual}",
));
}
}
let Some(inner_type) = declared_type
.clone()
.or_else(|| value.resolved_type.clone())
else {
return Err("unresolved type in let binding".into());
};
let bound_type = wrap_binding_type(*binding, inner_type);
let auto_ref = matches!(binding, Binding::Variable | Binding::Reference);
context.define_variable(name.clone(), bound_type, auto_ref);
}
Statement::Assign(target, value) => {
resolve_expression(target, context)?;
resolve_expression(value, context)?;
auto_deref_replace_target(target, context)?;
try_auto_deref_in_place(target, context);
try_auto_deref_in_place(value, context);
check_replace_types(target, value)?;
}
Statement::Return(Some(expression)) => {
resolve_expression(expression, context)?;
try_auto_deref_for_context(expression, context.current_return_type.as_ref(), context);
if let (Some(expected), Some(actual)) =
(&context.current_return_type, &expression.resolved_type)
{
let expected_resolved = context.resolve_underlying(expected);
let actual_resolved = context.resolve_underlying(actual);
if !types_compatible(&expected_resolved, &actual_resolved) {
return Err(format!(
"return type mismatch: expected {expected}, got {actual}",
));
}
}
}
Statement::Return(None) => {}
Statement::Label {
parameters,
initial_arguments,
..
} => {
for (i, argument) in initial_arguments.iter_mut().enumerate() {
resolve_expression(argument, context)?;
let param_type = parameters.get(i).and_then(|p| p.parameter_type.as_ref());
try_auto_deref_for_context(argument, param_type, context);
}
for (i, param) in parameters.iter_mut().enumerate() {
if param.parameter_type.is_none()
&& let Some(argument) = initial_arguments.get(i)
{
param.parameter_type.clone_from(&argument.resolved_type);
}
}
for param in parameters.iter() {
if let Some(ref parameter_type) = param.parameter_type {
context.define_variable(param.name.clone(), parameter_type.clone(), false);
}
}
}
Statement::Jump { arguments, .. } => {
for argument in arguments.iter_mut() {
resolve_expression(argument, context)?;
try_auto_deref_in_place(argument, context);
}
}
Statement::MultiReplace {
bindings,
targets,
values,
} => {
for value in values.iter_mut() {
resolve_expression(value, context)?;
try_auto_deref_in_place(value, context);
}
for target in targets.iter_mut() {
resolve_expression(target, context)?;
auto_deref_replace_target(target, context)?;
try_auto_deref_in_place(target, context);
}
for (index, binding) in bindings.iter().enumerate() {
if let Some((name, bind_mode)) = binding
&& let Some(target_type) = targets[index].resolved_type.clone()
{
let resolved = context.resolve_named(&target_type);
let derefed = match resolved {
Type::Pointer(_, inner) => *inner,
other => other,
};
let auto_ref = matches!(bind_mode, Binding::Variable | Binding::Reference);
context.define_variable(
name.clone(),
wrap_binding_type(*bind_mode, derefed),
auto_ref,
);
}
}
}
Statement::Defer(inner) => {
resolve_statement(inner, context)?;
}
}
Ok(())
}
fn resolve_expression(
expression: &mut Expression,
context: &mut ResolutionContext,
) -> Result<(), String> {
match &mut expression.kind {
ExpressionKind::Literal(literal) => {
expression.resolved_type = Some(literal_type(literal));
}
ExpressionKind::Variable(name) => {
if !context.is_known_name(name) {
return Err(format!("undefined variable '{name}'"));
}
expression.resolved_type = context.lookup_variable_type(name);
}
ExpressionKind::BinaryOperation(operator, left, right) => {
resolve_expression(left, context)?;
resolve_expression(right, context)?;
if matches!(left.kind, ExpressionKind::Literal(Literal::Null))
&& let Some(ref right_type) = right.resolved_type
{
left.resolved_type = Some(right_type.clone());
}
if matches!(right.kind, ExpressionKind::Literal(Literal::Null))
&& let Some(ref left_type) = left.resolved_type
{
right.resolved_type = Some(left_type.clone());
}
try_auto_deref_in_place(left, context);
try_auto_deref_in_place(right, context);
if matches!(
operator,
BinaryOperator::Comparison(
ComparisonOperator::Equal | ComparisonOperator::NotEqual
)
) {
let left_is_pointer = left
.resolved_type
.as_ref()
.is_some_and(|t| matches!(context.resolve_underlying(t), Type::Pointer(..)));
let right_is_pointer = right
.resolved_type
.as_ref()
.is_some_and(|t| matches!(context.resolve_underlying(t), Type::Pointer(..)));
let left_is_integer = left
.resolved_type
.as_ref()
.is_some_and(|t| matches!(context.resolve_underlying(t), Type::Int(..)));
let right_is_integer = right
.resolved_type
.as_ref()
.is_some_and(|t| matches!(context.resolve_underlying(t), Type::Int(..)));
if (left_is_pointer && right_is_integer) || (left_is_integer && right_is_pointer) {
return Err(spanned_error(
expression.span,
"cannot compare pointer with integer — use 'pointer == null' instead",
));
}
}
coerce_numeric_literals(left, right);
coerce_mixed_newtypes(left, right, context);
check_binary_operands(context, operator, left, right)?;
expression.resolved_type = if let (Some(left_type), Some(right_type)) =
(&left.resolved_type, &right.resolved_type)
&& is_pointer_arithmetic(context, operator, left_type, right_type)
{
if matches!(context.resolve_underlying(left_type), Type::Pointer(..)) {
Some(left_type.clone())
} else {
Some(right_type.clone())
}
} else {
left.resolved_type
.as_ref()
.map(|t| operator_result_type(operator, t))
};
}
ExpressionKind::UnaryOperation(operator, operand) => {
resolve_expression(operand, context)?;
try_auto_deref_in_place(operand, context);
expression.resolved_type = operand
.resolved_type
.as_ref()
.map(|t| unary_result_type(operator, t));
}
ExpressionKind::Call(callee, arguments) => {
resolve_expression(callee, context)?;
if let ExpressionKind::Variable(ref name) = callee.kind
&& let Some(inner_type) = context.newtypes.get(name).cloned()
{
for arg in arguments.iter_mut() {
resolve_expression(arg, context)?;
try_auto_deref_in_place(arg, context);
}
let type_name = name.clone();
if !matches!(inner_type, Type::Tuple(_)) {
if arguments.len() != 1 {
return Err(format!(
"'{type_name}': expected 1 value, got {}",
arguments.len()
));
}
let value = arguments.pop().unwrap();
expression.kind =
ExpressionKind::Convert(Box::new(value), Type::Named(type_name.clone()));
expression.resolved_type = Some(Type::Named(type_name));
return Ok(());
}
let fields = match_fields_by_type_or_position(
&inner_type,
&type_name,
std::mem::take(arguments),
)?;
expression.kind = ExpressionKind::TypeConstruction(type_name.clone(), fields);
expression.resolved_type = Some(Type::Named(type_name));
if let ExpressionKind::TypeConstruction(ref name, ref fields) = expression.kind {
check_construction_fields(context, name, fields)?;
}
return Ok(());
}
let param_types = if let ExpressionKind::Variable(ref name) = callee.kind {
context.lookup_function_params(name).cloned()
} else {
None
};
for arg in arguments.iter_mut() {
resolve_expression(arg, context)?;
}
if let Some(ref params) = param_types {
reorder_arguments_by_type(params, arguments, context);
}
for (i, arg) in arguments.iter_mut().enumerate() {
let expects_pointer = param_types.as_ref().is_some_and(|params| {
i < params.len()
&& matches!(context.resolve_named(¶ms[i]), Type::Pointer(..))
});
let arg_is_pointer = arg
.resolved_type
.as_ref()
.is_some_and(|t| matches!(context.resolve_underlying(t), Type::Pointer(..)));
if !expects_pointer && !arg_is_pointer {
try_auto_deref_in_place(arg, context);
}
}
if let Some(ref params) = param_types
&& let ExpressionKind::Variable(ref name) = callee.kind
&& arguments.len() != params.len()
&& !context.variadic_functions.contains(name.as_str())
{
return Err(format!(
"'{name}' expects {} argument{}, got {}",
params.len(),
if params.len() == 1 { "" } else { "s" },
arguments.len(),
));
}
if let Some(ref params) = param_types
&& let ExpressionKind::Variable(ref name) = callee.kind
&& !context.variadic_functions.contains(name.as_str())
{
for (index, (argument, expected)) in arguments.iter().zip(params.iter()).enumerate()
{
if let Some(actual) = &argument.resolved_type {
let expected_resolved = context.resolve_underlying(expected);
let actual_resolved = context.resolve_underlying(actual);
if !types_compatible(&expected_resolved, &actual_resolved) {
return Err(format!(
"'{name}' argument {}: expected {expected}, got {actual}",
index + 1
));
}
}
}
}
expression.resolved_type = if let ExpressionKind::Variable(ref name) = callee.kind {
context.lookup_function_return(name)
} else if let Some(callee_type) = &callee.resolved_type
&& let Type::Function(signature) = context.resolve_named(callee_type)
{
Some(*signature.return_type)
} else {
None
};
}
ExpressionKind::Field(object, field_name) => {
resolve_expression(object, context)?;
let Some(object_type) = object.resolved_type.as_ref() else {
return Err(spanned_error(
expression.span,
format!("cannot access field '{field_name}' on expression with unknown type"),
));
};
expression.resolved_type = Some(
resolve_field_type(object_type, field_name, context)
.map_err(|error| spanned_error(expression.span, error))?,
);
}
ExpressionKind::Index(array, index) => {
resolve_expression(array, context)?;
resolve_expression(index, context)?;
try_auto_deref_in_place(index, context);
expression.resolved_type = array
.resolved_type
.as_ref()
.map(|t| resolve_index_type(t, context));
}
ExpressionKind::Dereference(operand) => {
resolve_expression(operand, context)?;
let is_pointer = operand
.resolved_type
.as_ref()
.is_some_and(|t| matches!(context.resolve_named(t), Type::Pointer(..)));
if is_pointer {
expression.resolved_type = operand.resolved_type.as_ref().map(|t| {
if let Type::Pointer(_, inner) = context.resolve_named(t) {
*inner
} else {
unreachable!()
}
});
} else {
let placeholder = Expression::new(
ExpressionKind::Literal(Literal::Bool(false)),
Some(Type::Bool),
);
let old = std::mem::replace(expression, placeholder);
if let ExpressionKind::Dereference(operand) = old.kind {
*expression = *operand;
}
}
}
ExpressionKind::Convert(operand, target_type) => {
resolve_expression(operand, context)?;
if let Some(ref source_type) = operand.resolved_type
&& source_type == target_type
{
return Err(spanned_error(
expression.span,
format!("unnecessary cast: {source_type} is already {target_type}"),
));
}
let target_is_pointer =
matches!(context.resolve_underlying(target_type), Type::Pointer(..));
if !target_is_pointer && !is_pointer_param_chain(operand, context) {
try_auto_deref_in_place(operand, context);
}
expression.resolved_type = Some(target_type.clone());
}
ExpressionKind::Transmute(operand, target_type) => {
let span = expression.span;
resolve_expression(operand, context)?;
if let Some(ref source_type) = operand.resolved_type {
let source_resolved = context.resolve_underlying(source_type);
let target_resolved = context.resolve_underlying(target_type);
let source_is_pointer = matches!(source_resolved, Type::Pointer(..));
let target_is_pointer = matches!(target_resolved, Type::Pointer(..));
let source_is_integer = matches!(source_resolved, Type::Int(..));
let target_is_integer = matches!(target_resolved, Type::Int(..));
if !source_is_pointer && !target_is_pointer {
return Err(spanned_error(
span,
format!(
"'as' is only for pointer casts — use '{target_type}: value' for conversions",
),
));
}
if source_is_pointer && !target_is_pointer && !target_is_integer {
return Err(spanned_error(
span,
format!(
"pointer can only be cast to integer or other pointer, not {target_type}",
),
));
}
if !source_is_pointer && !source_is_integer && target_is_pointer {
return Err(spanned_error(
span,
format!(
"only integers or pointers can be cast to pointer, not {source_type}",
),
));
}
if matches!(source_resolved, Type::Pointer(Mutability::Shared, _))
&& matches!(target_resolved, Type::Pointer(Mutability::Mutable, _))
{
return Err(spanned_error(
span,
format!("cannot cast &T to |T: {source_type} as {target_type}"),
));
}
}
expression.resolved_type = Some(target_type.clone());
}
ExpressionKind::SizeOf(_) => {
expression.resolved_type = Some(Type::Int(IntWidth::WSize, Signedness::Unsigned));
}
ExpressionKind::TypeConstruction(name, fields) => {
for (field_name, value) in fields.iter_mut() {
resolve_expression(value, context)?;
let field_type = context
.newtypes
.get(field_name)
.map(|_| Type::Named(field_name.clone()));
try_auto_deref_for_context(value, field_type.as_ref(), context);
}
check_construction_fields(context, name, fields)?;
expression.resolved_type = Some(Type::Named(name.clone()));
}
ExpressionKind::ArrayLiteral(elements) => {
for element in elements.iter_mut() {
resolve_expression(element, context)?;
try_auto_deref_in_place(element, context);
}
let element_type = elements.first().and_then(|e| e.resolved_type.clone());
let length = elements.len();
if let Some(element_type) = element_type {
let new_type = match &expression.resolved_type {
Some(Type::Vector(_, _)) => Type::Vector(Box::new(element_type), length),
_ => Type::Array(Box::new(element_type), length),
};
expression.resolved_type = Some(new_type);
}
}
ExpressionKind::TupleLiteral(elements) => {
for element in elements.iter_mut() {
resolve_expression(element, context)?;
try_auto_deref_in_place(element, context);
}
let types: Option<Vec<Type>> =
elements.iter().map(|e| e.resolved_type.clone()).collect();
expression.resolved_type = types.map(Type::Tuple);
}
ExpressionKind::Block(block) => {
resolve_block(block, context)?;
expression.resolved_type = Some(block_result_type(block));
}
ExpressionKind::If {
condition,
then_branch,
else_branch,
} => {
resolve_expression(condition, context)?;
try_auto_deref_in_place(condition, context);
if let Some(ref condition_type) = condition.resolved_type {
let resolved = context.resolve_underlying(condition_type);
if matches!(resolved, Type::Pointer(..)) {
return Err(spanned_error(
condition.span,
"if-condition must be bool, not a pointer — use 'pointer != null' instead",
));
}
}
resolve_block(then_branch, context)?;
if let Some(else_branch) = else_branch {
resolve_block(else_branch, context)?;
}
expression.resolved_type = Some(block_result_type(then_branch));
}
ExpressionKind::Match { value, arms } => {
resolve_expression(value, context)?;
try_auto_deref_in_place(value, context);
for arm in arms.iter_mut() {
context.push_scope();
if let MatchPattern::Variant(type_name, binding_name) = &arm.pattern {
if let Some(inner) = context.newtypes.get(type_name).cloned() {
let binding_type = context.resolve_named(&inner);
context.define_variable(binding_name.clone(), binding_type, false);
}
} else if let MatchPattern::Variable(binding_name) = &arm.pattern
&& let Some(ref value_type) = value.resolved_type
{
context.define_variable(binding_name.clone(), value_type.clone(), false);
}
resolve_block(&mut arm.body, context)?;
context.pop_scope();
}
expression.resolved_type = Some(
arms.first()
.map_or_else(Type::unit, |arm| block_result_type(&arm.body)),
);
}
ExpressionKind::Replace(target, value) => {
resolve_expression(target, context)?;
resolve_expression(value, context)?;
auto_deref_replace_target(target, context)?;
try_auto_deref_in_place(target, context);
try_auto_deref_in_place(value, context);
expression.resolved_type.clone_from(&target.resolved_type);
check_replace_types(target, value)?;
}
ExpressionKind::OpAssign(_, target, value) => {
resolve_expression(target, context)?;
resolve_expression(value, context)?;
auto_deref_replace_target(target, context)?;
try_auto_deref_in_place(target, context);
try_auto_deref_in_place(value, context);
expression.resolved_type.clone_from(&target.resolved_type);
}
ExpressionKind::Slice(array, start, end) => {
resolve_expression(array, context)?;
if let Some(start) = start {
resolve_expression(start, context)?;
try_auto_deref_in_place(start, context);
}
if let Some(end) = end {
resolve_expression(end, context)?;
try_auto_deref_in_place(end, context);
}
expression.resolved_type = array.resolved_type.as_ref().map(|t| {
let resolved = context.resolve_named(t);
let element_type = match &resolved {
Type::Array(inner, _) => (**inner).clone(),
_ => resolved,
};
Type::Slice(Mutability::Shared, Box::new(element_type))
});
}
ExpressionKind::Print(args) => {
for arg in args.iter_mut() {
resolve_expression(arg, context)?;
try_auto_deref_in_place(arg, context);
}
expression.resolved_type = Some(Type::unit());
}
}
Ok(())
}
mod helpers;
use helpers::*;