Skip to content

Commit

Permalink
use ListTypeData::new() for list type construction. allow size of NoT…
Browse files Browse the repository at this point in the history
…ype to be queried (fixes a bug in the type checker for (legal) lists with notypes (e.g., (list none none)). add typemap to the contract analysis object: allows querying inferred types. used now just for tests, but will eventually have other uses as well.
  • Loading branch information
kantai committed Aug 26, 2019
1 parent 5742328 commit ce3b289
Show file tree
Hide file tree
Showing 8 changed files with 67 additions and 40 deletions.
1 change: 1 addition & 0 deletions src/vm/analysis/build_contract_interface/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub fn build_contract_interface(contract_analysis: &ContractAnalysis) -> Contrac
non_fungible_tokens,
top_level_expression_sorting: _,
expressions: _,
type_map: _,
} = contract_analysis;

contract_interface.functions.append(
Expand Down
8 changes: 7 additions & 1 deletion src/vm/analysis/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ pub mod build_contract_interface;

use self::types::{ContractAnalysis, AnalysisPass};
use vm::representations::{SymbolicExpression};
use vm::types::{TypeSignature};

pub use self::errors::{CheckResult, CheckError, CheckErrors};
pub use self::analysis_db::{AnalysisDatabase};
Expand All @@ -20,11 +21,16 @@ use self::read_only_checker::ReadOnlyChecker;
use self::type_checker::TypeChecker;

#[cfg(test)]
pub fn mem_type_check(snippet: &str) -> CheckResult<ContractAnalysis> {
pub fn mem_type_check(snippet: &str) -> CheckResult<(Option<TypeSignature>, ContractAnalysis)> {
use vm::parser::parse;
let mut contract = parse(snippet).unwrap();
let mut analysis_db = AnalysisDatabase::memory();
type_check(&":transient:", &mut contract, &mut analysis_db, false)
.map(|x| {
// return the first type result of the type checker
let first_type = x.type_map.as_ref().unwrap()
.get_type(&x.expressions[0]).cloned();
(first_type, x) })
}

// Legacy function
Expand Down
5 changes: 5 additions & 0 deletions src/vm/analysis/type_checker/contexts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use vm::contexts::MAX_CONTEXT_DEPTH;
use vm::analysis::errors::{CheckResult, CheckError, CheckErrors};
use vm::analysis::types::{ContractAnalysis};

#[derive(Debug, PartialEq, Eq)]
pub struct TypeMap {
map: HashMap<u64, TypeSignature>
}
Expand Down Expand Up @@ -40,6 +41,10 @@ impl TypeMap {
Ok(())
}
}

pub fn get_type(&self, expr: &SymbolicExpression) -> Option<&TypeSignature> {
self.map.get(&expr.id)
}
}

impl ContractContext {
Expand Down
1 change: 1 addition & 0 deletions src/vm/analysis/type_checker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ impl <'a, 'b> TypeChecker <'a, 'b> {

fn into_contract_analysis(self, contract_analysis: &mut ContractAnalysis) {
self.contract_context.into_contract_analysis(contract_analysis);
contract_analysis.type_map = Some(self.type_map);
}

pub fn track_return_type(&mut self, return_type: TypeSignature) -> CheckResult<()> {
Expand Down
2 changes: 1 addition & 1 deletion src/vm/analysis/type_checker/tests/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ fn test_names_tokens_contracts_interface() {
";


let contract_analysis = mem_type_check(INTERFACE_TEST_CONTRACT).unwrap();
let contract_analysis = mem_type_check(INTERFACE_TEST_CONTRACT).unwrap().1;
let test_contract_json_str = build_contract_interface(&contract_analysis).serialize();
let test_contract_json = serde_json::from_str(&test_contract_json_str).unwrap();

Expand Down
13 changes: 12 additions & 1 deletion src/vm/analysis/type_checker/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ fn test_function_arg_names() {
];

for (func_test, arg_names) in functions.iter().zip(expected_arg_names.iter()) {
let contract_analysis = mem_type_check(func_test).unwrap();
let contract_analysis = mem_type_check(func_test).unwrap().1;

let func_type_priv = contract_analysis.get_private_function("test").unwrap();
let func_type_pub = contract_analysis.get_public_function_type("test-pub").unwrap();
Expand Down Expand Up @@ -350,6 +350,17 @@ fn test_options() {

}


#[test]
fn test_list_nones() {
let contract = "
(begin
(let ((a (list none none none))) (print a)))";
assert_eq!(
"(list 3 (optional NoType))",
&format!("{}", mem_type_check(contract).unwrap().0.unwrap()));
}

#[test]
fn test_set_int_variable() {
let contract_src = r#"
Expand Down
4 changes: 4 additions & 0 deletions src/vm/analysis/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use vm::{SymbolicExpression, ClarityName};
use vm::types::{TypeSignature, FunctionType};
use vm::analysis::analysis_db::{AnalysisDatabase};
use vm::analysis::errors::{CheckResult};
use vm::analysis::type_checker::contexts::TypeMap;

const DESERIALIZE_FAIL_MESSAGE: &str = "PANIC: Failed to deserialize bad database data in contract analysis.";
const SERIALIZE_FAIL_MESSAGE: &str = "PANIC: Failed to deserialize bad database data in contract analysis.";
Expand All @@ -13,6 +14,8 @@ pub trait AnalysisPass {

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct ContractAnalysis {
#[serde(skip)]
pub type_map: Option<TypeMap>,
// matt: is okay to let these new fields end up in the db?
pub private_function_types: BTreeMap<ClarityName, FunctionType>,
pub variable_types: BTreeMap<ClarityName, TypeSignature>,
Expand All @@ -31,6 +34,7 @@ pub struct ContractAnalysis {
impl ContractAnalysis {
pub fn new(expressions: Vec<SymbolicExpression>) -> ContractAnalysis {
ContractAnalysis {
type_map: None,
expressions: expressions,
private_function_types: BTreeMap::new(),
public_function_types: BTreeMap::new(),
Expand Down
73 changes: 36 additions & 37 deletions src/vm/types/signatures.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// TypeSignatures
use std::hash::{Hash, Hasher};
use std::{fmt, cmp};
use std::convert::TryFrom;
use std::collections::BTreeMap;

use address::c32;
Expand Down Expand Up @@ -84,6 +85,22 @@ impl From<ListTypeData> for TypeSignature {
}

impl ListTypeData {
pub fn new_list(atomic_type: AtomTypeIdentifier, max_len: u32, dimension: u8) -> Result<ListTypeData> {
if dimension == 0 {
Err(RuntimeErrorType::InvalidTypeDescription.into())
} else {
let list_data = ListTypeData {
atomic_type,
max_len: max_len as u32,
dimension: dimension as u8 };
if list_data.size()? > MAX_VALUE_SIZE {
Err(RuntimeErrorType::ValueTooLarge.into())
} else {
Ok(list_data)
}
}
}

pub fn get_list_item_type(&self) -> TypeSignature {
if self.dimension == 0 {
panic!("Invalid dimension of 0")
Expand Down Expand Up @@ -113,9 +130,9 @@ impl ListTypeData {
impl AtomTypeIdentifier {
pub fn size(&self) -> Result<i128> {
match self {
// NoType should _never_ be asked for size. It is only ever used
// in type checking native functions.
AtomTypeIdentifier::NoType => Err(RuntimeErrorType::BadTypeConstruction.into()),
// NoType's may be asked for their size at runtime --
// legal constructions like `(ok 1)` have NoType parts (if they have unknown error variant types).
AtomTypeIdentifier::NoType => Ok(1),
AtomTypeIdentifier::IntType => Ok(16),
AtomTypeIdentifier::BoolType => Ok(1),
AtomTypeIdentifier::PrincipalType => Ok(21),
Expand Down Expand Up @@ -398,25 +415,6 @@ impl TypeSignature {
}
}

pub fn new_list(atomic_type: AtomTypeIdentifier, max_len: i128, dimension: i128) -> Result<TypeSignature> {
if dimension == 0 {
Err(RuntimeErrorType::InvalidTypeDescription.into())
} else if max_len > u32::max_value() as i128 || dimension > u8::max_value() as i128 {
Err(RuntimeErrorType::ListTooLarge.into())
} else {
let list_data = ListTypeData {
atomic_type,
max_len: max_len as u32,
dimension: dimension as u8 };
let type_sig = TypeSignature::List(list_data);
if type_sig.size()? > MAX_VALUE_SIZE {
Err(RuntimeErrorType::ValueTooLarge.into())
} else {
Ok(type_sig)
}
}
}

pub fn list_max_len(&self) -> Option<u32> {
if let TypeSignature::List(ListTypeData{ max_len, .. }) = self {
Some(max_len.clone())
Expand All @@ -426,7 +424,7 @@ impl TypeSignature {
}

pub fn list_of(item_type: TypeSignature, max_len: u32) -> Result<TypeSignature> {
let list_type = match item_type {
let (atomic_type, max_len, dimension) = match item_type {
TypeSignature::List(ListTypeData { max_len: item_max_len,
dimension: item_dim,
atomic_type }) => {
Expand All @@ -439,14 +437,14 @@ impl TypeSignature {
max_len
}
};
ListTypeData { max_len, dimension, atomic_type }
(atomic_type, max_len, dimension)
}
TypeSignature::Atom(atomic_type) => {
ListTypeData { max_len, dimension: 1, atomic_type }
(atomic_type, max_len, 1)
}
};

Ok(TypeSignature::List(list_type))
ListTypeData::new_list(atomic_type, max_len, dimension).map(|x| x.into())
}

fn new_atom_checked(atom_type: AtomTypeIdentifier) -> Result<TypeSignature> {
Expand Down Expand Up @@ -577,9 +575,9 @@ impl TypeSignature {
}
};

Ok(ListTypeData { atomic_type,
max_len: parent_max_len,
dimension: parent_dimension })
Ok(ListTypeData::new_list(atomic_type,
parent_max_len,
parent_dimension)?)
} else {
Ok(TypeSignature::get_empty_list_type())
}
Expand Down Expand Up @@ -658,20 +656,21 @@ impl TypeSignature {
let dimension = {
if type_args.len() == 2 {
Ok(1)
} else if let SymbolicExpressionType::AtomValue(Value::Int(dimension)) = &type_args[1].expr {
Ok(u8::try_from(*dimension)
.map_err(|_| RuntimeErrorType::InvalidTypeDescription)?)
} else {
if let SymbolicExpressionType::AtomValue(Value::Int(dimension)) = &type_args[1].expr {
Ok(*dimension)
} else {
Err(RuntimeErrorType::InvalidTypeDescription)
}
Err(RuntimeErrorType::InvalidTypeDescription)
}
}?;

if let SymbolicExpressionType::AtomValue(Value::Int(max_len)) = &type_args[0].expr {
let atomic_type_arg = &type_args[type_args.len()-1];
let atomic_type = TypeSignature::parse_type_repr(atomic_type_arg, false)?;
let max_len = u32::try_from(*max_len)
.map_err(|_| RuntimeErrorType::InvalidTypeDescription)?;
if let TypeSignature::Atom(atomic_type) = atomic_type {
TypeSignature::new_list(atomic_type, *max_len, dimension)
ListTypeData::new_list(atomic_type, max_len, dimension).map(|x| x.into())
} else {
panic!("Parser should not have returned a non-atom")
}
Expand Down Expand Up @@ -821,9 +820,9 @@ impl fmt::Display for TypeSignature {
TypeSignature::List(ref list_type_data) => {
write!(f, "(list {}", list_type_data.max_len)?;
if list_type_data.dimension > 1 {
write!(f, "{}", list_type_data.dimension)?;
write!(f, " {}", list_type_data.dimension)?;
}
write!(f, "{})", list_type_data.atomic_type)
write!(f, " {})", list_type_data.atomic_type)
}
}
}
Expand Down

0 comments on commit ce3b289

Please sign in to comment.