Skip to content
Open
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
3 changes: 3 additions & 0 deletions sway-core/src/language/ty/expression/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,9 @@ impl TyExpression {
pub(crate) fn gather_mutability(&self) -> VariableMutability {
match &self.expression {
TyExpressionVariant::VariableExpression { mutability, .. } => *mutability,
TyExpressionVariant::ConstantExpression { .. } => VariableMutability::Immutable,
TyExpressionVariant::StructFieldAccess { prefix, .. }
| TyExpressionVariant::TupleElemAccess { prefix, .. } => prefix.gather_mutability(),
_ => VariableMutability::Immutable,
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,13 +216,7 @@ fn unify_arguments_and_parameters(
}

// check for matching mutability
let param_mutability =
ty::VariableMutability::new_from_ref_mut(param.is_reference, param.is_mutable);
if arg.gather_mutability().is_immutable() && param_mutability.is_mutable() {
handler.emit_err(CompileError::ImmutableArgumentToMutableParameter {
span: arg.span.clone(),
});
}
check_arg_mutability(handler, param, &arg);

typed_arguments_and_names.push((param.name.clone(), arg));
}
Expand All @@ -231,6 +225,21 @@ fn unify_arguments_and_parameters(
})
}

/// Emits an error if an immutable `arg` is passed to a `ref mut` `param`.
pub(crate) fn check_arg_mutability(
handler: &Handler,
param: &ty::TyFunctionParameter,
arg: &ty::TyExpression,
) {
let param_mutability =
ty::VariableMutability::new_from_ref_mut(param.is_reference, param.is_mutable);
if param_mutability.is_mutable() && arg.gather_mutability().is_immutable() {
handler.emit_err(CompileError::ImmutableArgumentToMutableParameter {
span: arg.span.clone(),
});
}
}

pub(crate) fn check_function_arguments_arity(
handler: &Handler,
arguments_len: usize,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use ast_elements::{
type_argument::GenericTypeArgument,
type_parameter::{ConstGenericExpr, GenericTypeParameter},
};
use ast_node::typed_expression::check_function_arguments_arity;
use ast_node::typed_expression::{check_arg_mutability, check_function_arguments_arity};
use indexmap::IndexMap;
use itertools::izip;
use std::collections::{BTreeMap, HashMap, VecDeque};
Expand Down Expand Up @@ -162,37 +162,53 @@ pub(crate) fn type_check_method_application(
index
};

if let (Some(arg), _, false) = arg_opt {
if let Some(param) = method.parameters.get(param_index) {
if coercion_check.check(arg.return_type, param.type_argument.type_id) {
// If argument type coerces to resolved method parameter type skip second type_check.
args_buf.push_back(arg);
continue;
}
} else {
args_buf.push_back(arg);
continue;
}
}
let param = method.parameters.get(param_index);

// Reuse the first-pass typed argument when it already coerces to the
// resolved parameter type (or there is no matching parameter), otherwise
// type check the argument expression again, this time with the type
// annotation and throwing out the error.
let (arg_typed, _, needs_recheck) = arg_opt;
let reuse = !needs_recheck
&& arg_typed.is_some()
&& param
.map(|param| {
coercion_check.check(
arg_typed.as_ref().unwrap().return_type,
param.type_argument.type_id,
)
})
.unwrap_or(true);

// We type check the argument expression again this time throwing out the error.
let ctx = if let Some(param) = method.parameters.get(param_index) {
// We now try to type check it again, this time with the type annotation.
ctx.by_ref()
.with_help_text(
"Function application argument type must match function parameter type.",
)
.with_type_annotation(param.type_argument.type_id)
let typed_arg = if reuse {
arg_typed.unwrap()
} else {
ctx.by_ref()
.with_help_text("")
.with_type_annotation(type_engine.new_unknown())
let ctx = if let Some(param) = param {
ctx.by_ref()
.with_help_text(
"Function application argument type must match function parameter type.",
)
.with_type_annotation(param.type_argument.type_id)
} else {
ctx.by_ref()
.with_help_text("")
.with_type_annotation(type_engine.new_unknown())
};
ty::TyExpression::type_check(handler, ctx, arg)
.unwrap_or_else(|err| ty::TyExpression::error(err, span.clone(), engines))
};

args_buf.push_back(
ty::TyExpression::type_check(handler, ctx, arg)
.unwrap_or_else(|err| ty::TyExpression::error(err, span.clone(), engines)),
);
// Check that an immutable argument is not passed to a `ref mut` parameter.
// This keys off the same source-argument-to-parameter mapping used above,
// so it stays correct even when the contract-call receiver is not pushed
// into `args_buf`.
if let Some(param) = param {
if !param.is_self() {
check_arg_mutability(handler, param, &typed_arg);
}
}

args_buf.push_back(typed_arg);
}

// check the method visibility
Expand Down
3 changes: 2 additions & 1 deletion sway-lib-std/src/hash.sw
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ impl Hasher {
/// Note that the length of `bytes` is not appended to the
/// `Hasher`, just the content.
pub fn write(ref mut self, bytes: Bytes) {
self.bytes.append(bytes);
let mut b = bytes;
self.bytes.append(b);
}

/// Appends bytes from the `slice` to this `Hasher`.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[[package]]
name = "ref_mut_method_mismatch"
source = "member"
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[project]
authors = ["Fuel Labs <contact@fuel.sh>"]
entry = "main.sw"
license = "Apache-2.0"
name = "ref_mut_method_mismatch"
implicit-std = false
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
script;

struct Point {
x: u64,
y: u64,
}

impl Point {
fn mutate(self, ref mut _v: u64) { }
}

const MY_CONST: u64 = 10;

fn main() {
// immutable variable
let x = 42u64;
Point { x: 1, y: 2 }.mutate(x);

// struct field access on immutable binding
let p = Point { x: 1, y: 2 };
Point { x: 1, y: 2 }.mutate(p.x);

// tuple element access on immutable binding
let t = (1u64, 2u64);
Point { x: 1, y: 2 }.mutate(t.0);

// constant
Point { x: 1, y: 2 }.mutate(MY_CONST);

// mutable variable — should NOT error
let mut m = 99u64;
Point { x: 1, y: 2 }.mutate(m);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
category = "fail"

# immutable variable
# check: $()Point { x: 1, y: 2 }.mutate(x);
# nextln: $()Cannot pass immutable argument to mutable parameter.

# struct field access
# check: $()Point { x: 1, y: 2 }.mutate(p.x);
# nextln: $()Cannot pass immutable argument to mutable parameter.

# tuple element access
# check: $()Point { x: 1, y: 2 }.mutate(t.0);
# nextln: $()Cannot pass immutable argument to mutable parameter.

# constant
# check: $()Point { x: 1, y: 2 }.mutate(MY_CONST);
# nextln: $()Cannot pass immutable argument to mutable parameter.
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ pub fn run_all_tests() -> u64 {
assert(items_1_trait_teststruct_1_res);


let hasher = mk_hasher();
let mut hasher = mk_hasher();
teststruct_1.hash(hasher);


Expand Down
Loading