Skip to content

Commit

Permalink
Implement string casts
Browse files Browse the repository at this point in the history
  • Loading branch information
cburgdorf committed Jan 21, 2021
1 parent c313633 commit 9faf901
Show file tree
Hide file tree
Showing 8 changed files with 93 additions and 17 deletions.
1 change: 1 addition & 0 deletions compiler/tests/compile_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
8 changes: 8 additions & 0 deletions compiler/tests/evm_contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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![(
Expand All @@ -767,6 +774,7 @@ fn strings() {
string_token("string 3"),
address_token("1000000000000000000000000000000000000001"),
string_token("static string"),
string_token("foo"),
],
)],
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
contract Foo:
pub def bar() -> string3:
return string3("too long")
10 changes: 7 additions & 3 deletions compiler/tests/fixtures/strings.fe
Original file line number Diff line number Diff line change
Expand Up @@ -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")
7 changes: 7 additions & 0 deletions newsfragments/201.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Add support for string type casts

Example:

```
val: string100 = string100("foo")
```
9 changes: 9 additions & 0 deletions semantics/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub enum ErrorKind {
MissingReturn,
NotSubscriptable,
NumericCapacityMismatch,
StringCapacityMismatch,
UndefinedValue,
UnexpectedReturn,
TypeError,
Expand Down Expand Up @@ -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 {
Expand Down
23 changes: 17 additions & 6 deletions semantics/src/namespace/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self, Self::Error> {
if !value.starts_with("string") {
return Err("Value must start with 'string'".to_string());
}

let max_size = value[6..].parse::<u32>().map_err(|err| err.to_string())? as usize;

Ok(FeString { max_size })
}
}

impl Integer {
pub fn is_signed(&self) -> bool {
matches!(
Expand Down Expand Up @@ -594,12 +608,9 @@ pub fn type_desc(defs: &HashMap<String, Type>, typ: &fe::TypeDesc) -> Result<Typ
fe::TypeDesc::Base { base: "bool" } => 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::<u32>()
.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());
Expand Down
49 changes: 41 additions & 8 deletions semantics/src/traversal/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use crate::namespace::scopes::{
ContractFunctionDef,
Shared,
};
use std::convert::TryFrom;

use crate::builtins;
use crate::namespace::operations;
Expand All @@ -27,6 +28,7 @@ use crate::{
ExpressionAttributes,
Location,
};

use fe_parser::ast as fe;
use fe_parser::span::Spanned;
use std::rc::Rc;
Expand Down Expand Up @@ -203,8 +205,9 @@ fn expr_str(
exp: &Spanned<fe::Expr>,
) -> Result<ExpressionAttributes, SemanticError> {
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()
Expand Down Expand Up @@ -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<String, SemanticError> {
Expand All @@ -431,7 +443,7 @@ fn validate_is_numeric_literal(call_arg: &fe::CallArg) -> Result<String, Semanti
Err(SemanticError::numeric_literal_expected())
}

fn validate_literal_fits_type(num: &str, typ: &Type) -> 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(());
Expand All @@ -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<BlockScope>,
context: Shared<Context>,
Expand Down Expand Up @@ -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()),
}
}
Expand Down

0 comments on commit 9faf901

Please sign in to comment.