diff --git a/compiler/tests/compile_errors.rs b/compiler/tests/compile_errors.rs index 33348c9eba..cb0170e9ea 100644 --- a/compiler/tests/compile_errors.rs +++ b/compiler/tests/compile_errors.rs @@ -27,6 +27,7 @@ use std::fs; case("unary_minus_on_bool.fe", "TypeError"), case("type_constructor_from_variable.fe", "NumericLiteralExpected"), case("needs_mem_copy.fe", "CannotMove"), + case("string_capacity_mismatch.fe", "StringCapacityMismatch"), case("numeric_capacity_mismatch/u8_neg.fe", "NumericCapacityMismatch"), case("numeric_capacity_mismatch/u8_pos.fe", "NumericCapacityMismatch"), case("numeric_capacity_mismatch/u16_neg.fe", "NumericCapacityMismatch"), diff --git a/compiler/tests/evm_contracts.rs b/compiler/tests/evm_contracts.rs index 00372c09ea..7e58c622d4 100644 --- a/compiler/tests/evm_contracts.rs +++ b/compiler/tests/evm_contracts.rs @@ -756,6 +756,13 @@ fn strings() { Some(string_token("The quick brown fox jumps over the lazy dog")), ); + harness.test_function( + &mut executor, + "return_casted_static_string", + vec![], + Some(string_token("foo")), + ); + harness.events_emitted( executor, vec![( @@ -767,6 +774,7 @@ fn strings() { string_token("string 3"), address_token("1000000000000000000000000000000000000001"), string_token("static string"), + string_token("foo"), ], )], ); diff --git a/compiler/tests/fixtures/compile_errors/string_capacity_mismatch.fe b/compiler/tests/fixtures/compile_errors/string_capacity_mismatch.fe new file mode 100644 index 0000000000..a2b4ce7cdb --- /dev/null +++ b/compiler/tests/fixtures/compile_errors/string_capacity_mismatch.fe @@ -0,0 +1,3 @@ +contract Foo: + pub def bar() -> string3: + return string3("too long") \ No newline at end of file diff --git a/compiler/tests/fixtures/strings.fe b/compiler/tests/fixtures/strings.fe index aefee30adf..1c90bc41a1 100644 --- a/compiler/tests/fixtures/strings.fe +++ b/compiler/tests/fixtures/strings.fe @@ -6,12 +6,16 @@ contract Foo: s3: string100 a: address s4: string13 + s5: string100 pub def __init__(s1: string42, a: address, s2: string26, u: u256, s3: string100): - emit MyEvent(s2, u, s1, s3, a, "static string") + emit MyEvent(s2, u, s1, s3, a, "static string", "foo") pub def bar(s1: string100, s2: string100) -> string100: return s2 - pub def return_static_string() -> string43: - return "The quick brown fox jumps over the lazy dog" + pub def return_static_string() -> string50: + return string50("The quick brown fox jumps over the lazy dog") + + pub def return_casted_static_string() -> string100: + return string100("foo") diff --git a/newsfragments/201.feature.md b/newsfragments/201.feature.md new file mode 100644 index 0000000000..4716b522c0 --- /dev/null +++ b/newsfragments/201.feature.md @@ -0,0 +1,7 @@ +Add support for string type casts + +Example: + +``` +val: string100 = string100("foo") +``` diff --git a/semantics/src/errors.rs b/semantics/src/errors.rs index d7fa2981fb..11acc0a696 100644 --- a/semantics/src/errors.rs +++ b/semantics/src/errors.rs @@ -11,6 +11,7 @@ pub enum ErrorKind { MissingReturn, NotSubscriptable, NumericCapacityMismatch, + StringCapacityMismatch, UndefinedValue, UnexpectedReturn, TypeError, @@ -70,6 +71,14 @@ impl SemanticError { } } + /// Create a new error with kind `NumericCapacityMismatch` + pub fn string_capacity_mismatch() -> Self { + SemanticError { + kind: ErrorKind::StringCapacityMismatch, + context: vec![], + } + } + /// Create a new error with kind `UndefinedValue` pub fn undefined_value() -> Self { SemanticError { diff --git a/semantics/src/namespace/types.rs b/semantics/src/namespace/types.rs index 6a62a6221a..34093f109d 100644 --- a/semantics/src/namespace/types.rs +++ b/semantics/src/namespace/types.rs @@ -157,6 +157,20 @@ pub struct FeString { pub max_size: usize, } +impl TryFrom<&str> for FeString { + type Error = String; + + fn try_from(value: &str) -> Result { + if !value.starts_with("string") { + return Err("Value must start with 'string'".to_string()); + } + + let max_size = value[6..].parse::().map_err(|err| err.to_string())? as usize; + + Ok(FeString { max_size }) + } +} + impl Integer { pub fn is_signed(&self) -> bool { matches!( @@ -594,12 +608,9 @@ pub fn type_desc(defs: &HashMap, typ: &fe::TypeDesc) -> Result Ok(Type::Base(Base::Bool)), fe::TypeDesc::Base { base: "bytes" } => Ok(Type::Base(Base::Byte)), fe::TypeDesc::Base { base: "address" } => Ok(Type::Base(Base::Address)), - fe::TypeDesc::Base { base } if &base[..6] == "string" => { - let max_size = base[6..] - .parse::() - .map_err(|_| SemanticError::type_error())? as usize; - Ok(Type::String(FeString { max_size })) - } + fe::TypeDesc::Base { base } if base.starts_with("string") => Ok(Type::String( + TryFrom::try_from(*base).map_err(|_| SemanticError::type_error())?, + )), fe::TypeDesc::Base { base } => { if let Some(typ) = defs.get(base.to_owned()) { return Ok(typ.clone()); diff --git a/semantics/src/traversal/expressions.rs b/semantics/src/traversal/expressions.rs index 43a440c801..8f7fbd867f 100644 --- a/semantics/src/traversal/expressions.rs +++ b/semantics/src/traversal/expressions.rs @@ -4,6 +4,7 @@ use crate::namespace::scopes::{ ContractFunctionDef, Shared, }; +use std::convert::TryFrom; use crate::builtins; use crate::namespace::operations; @@ -27,6 +28,7 @@ use crate::{ ExpressionAttributes, Location, }; + use fe_parser::ast as fe; use fe_parser::span::Spanned; use std::rc::Rc; @@ -203,8 +205,9 @@ fn expr_str( exp: &Spanned, ) -> Result { if let fe::Expr::Str(lines) = &exp.node { - let string_length = lines.iter().map(|val| val.len()).sum(); let string_val = lines.join(""); + let string_length = string_val.len(); + scope .borrow_mut() .contract_scope() @@ -409,14 +412,23 @@ fn expr_call_type_constructor( return Err(SemanticError::wrong_number_of_params()); } - let num = validate_is_numeric_literal(&args.node[0].node)?; + call_arg(Rc::clone(&scope), Rc::clone(&context), &args.node[0])?; - if !matches!(typ, Type::Base(Base::Address)) { - validate_literal_fits_type(&num, &typ)?; - } + match typ { + Type::String(ref fe_string) => { + validate_str_literal_fits_type(&args.node[0].node, &fe_string)?; + Ok(ExpressionAttributes::new(typ, Location::Memory)) + } + _ => { + let num = validate_is_numeric_literal(&args.node[0].node)?; - call_arg(scope, context, &args.node[0])?; - Ok(ExpressionAttributes::new(typ, Location::Value)) + if !matches!(typ, Type::Base(Base::Address)) { + validate_numeric_literal_fits_type(&num, &typ)?; + } + + Ok(ExpressionAttributes::new(typ, Location::Value)) + } + } } fn validate_is_numeric_literal(call_arg: &fe::CallArg) -> Result { @@ -431,7 +443,7 @@ fn validate_is_numeric_literal(call_arg: &fe::CallArg) -> Result Result<(), SemanticError> { +fn validate_numeric_literal_fits_type(num: &str, typ: &Type) -> Result<(), SemanticError> { if let Type::Base(Base::Numeric(integer)) = typ { if integer.fits(num) { return Ok(()); @@ -443,6 +455,22 @@ fn validate_literal_fits_type(num: &str, typ: &Type) -> Result<(), SemanticError Err(SemanticError::type_error()) } +fn validate_str_literal_fits_type( + call_arg: &fe::CallArg, + typ: &FeString, +) -> Result<(), SemanticError> { + if let fe::CallArg::Arg(fe::Expr::Str(lines)) = call_arg { + let string_length: usize = lines.join("").len(); + if string_length > typ.max_size { + return Err(SemanticError::string_capacity_mismatch()); + } else { + return Ok(()); + } + } + + Err(SemanticError::type_error()) +} + fn expr_call_self_attribute( scope: Shared, context: Shared, @@ -568,6 +596,11 @@ fn expr_name_call_type( "i8" => Ok(CallType::TypeConstructor { typ: Type::Base(Base::Numeric(Integer::I8)), }), + value if value.starts_with("string") => Ok(CallType::TypeConstructor { + typ: Type::String( + TryFrom::try_from(value).map_err(|_| SemanticError::undefined_value())?, + ), + }), _ => Err(SemanticError::undefined_value()), } }