Преглед на файлове

Implement solidity user type syntax

Ethereum Solidity introduces a new syntax for user defined types, see:

https://docs.soliditylang.org/en/v0.8.13/types.html

Documentation for the syntax is in the documentation of this commit,
and not repeated here.

Signed-off-by: Sean Young <sean@mess.org>
Sean Young преди 3 години
родител
ревизия
9ca8c2a685

+ 1 - 0
Cargo.toml

@@ -59,6 +59,7 @@ byteorder = "1.4"
 assert_cmd = "2.0"
 bincode = "1.3"
 ed25519-dalek = "1.0"
+path-slash = "0.1"
 
 [package.metadata.docs.rs]
 no-default-features = true

+ 22 - 0
docs/language.rst

@@ -1024,6 +1024,28 @@ is called via the ABI encoder/decoder, it is not possible to pass references, ju
 However it is possible to use storage reference variables in public functions, as
 demonstrated in function all_pumas().
 
+User Defined Types
+__________________
+
+A user defined type is a new type which simply wraps an existing primitive type. First, a new type
+is declared with the ``type`` syntax. The name of the type can now be used anywhere where a type
+is used, for example in function arguments or return values.
+
+.. code-block:: javascript
+
+    type Value is uint128;
+
+    function inc_and_wrap(int128 v) returns (Value) {
+        return Value.wrap(v + 1);
+    }
+
+    function dec_and_unwrap(Value v) returns (uint128) {
+        return Value.unwrap(v) - 1;
+    }
+
+Note that the wrapped value ``Value v`` cannot be used in any type of arithmetic or comparision. It needs to
+be unwrapped before it can be used.
+
 Expressions
 -----------
 

+ 1 - 0
src/abi/substrate.rs

@@ -549,6 +549,7 @@ fn ty_to_abi(ty: &ast::Type, ns: &ast::Namespace, registry: &mut Abi) -> ParamTy
         }
         ast::Type::StorageRef(_, ty) => ty_to_abi(ty, ns, registry),
         ast::Type::Ref(ty) => ty_to_abi(ty, ns, registry),
+        ast::Type::UserType(no) => ty_to_abi(&ns.user_types[*no].ty, ns, registry),
         ast::Type::Bool | ast::Type::Uint(_) | ast::Type::Int(_) => {
             let scalety = match ty {
                 ast::Type::Bool => "bool".into(),

+ 4 - 0
src/codegen/expression.rs

@@ -552,6 +552,10 @@ pub fn expression(
             Box::new(expression(e, cfg, contract_no, func, ns, vartab, opt)),
         ),
         // for some built-ins, we have to inline special case code
+        ast::Expression::Builtin(_, _, ast::Builtin::UserTypeWrap, args)
+        | ast::Expression::Builtin(_, _, ast::Builtin::UserTypeUnwrap, args) => {
+            expression(&args[0], cfg, contract_no, func, ns, vartab, opt)
+        }
         ast::Expression::Builtin(loc, ty, ast::Builtin::ArrayPush, args) => {
             if args[0].ty().is_contract_storage() {
                 if ns.target == Target::Solana || args[0].ty().is_storage_bytes() {

+ 26 - 0
src/emit/ethabiencoder.rs

@@ -621,6 +621,9 @@ impl<'a, 'b> EncoderBuilder<'a, 'b> {
             }
             ast::Type::Ref(r) => EncoderBuilder::encoded_fixed_length(r, ns),
             ast::Type::StorageRef(_, r) => EncoderBuilder::encoded_fixed_length(r, ns),
+            ast::Type::UserType(no) => {
+                EncoderBuilder::encoded_fixed_length(&ns.user_types[*no].ty, ns)
+            }
             _ => unreachable!(),
         }
     }
@@ -714,6 +717,19 @@ impl<'a, 'b> EncoderBuilder<'a, 'b> {
                     )
                 };
             }
+            ast::Type::UserType(no) => {
+                self.encode_ty(
+                    binary,
+                    ns,
+                    load,
+                    function,
+                    &ns.user_types[*no].ty,
+                    arg,
+                    fixed,
+                    offset,
+                    dynamic,
+                );
+            }
             ast::Type::Enum(n) => {
                 self.encode_primitive(binary, load, function, &ns.enums[*n].ty, *fixed, arg, ns);
 
@@ -3076,6 +3092,16 @@ impl EthAbiDecoder {
 
                 v.into()
             }
+            ast::Type::UserType(no) => self.decode_primitive(
+                binary,
+                function,
+                &ns.user_types[*no].ty,
+                to,
+                offset,
+                data,
+                length,
+                ns,
+            ),
             _ => self.decode_primitive(binary, function, ty, to, offset, data, length, ns),
         }
     }

+ 1 - 0
src/emit/mod.rs

@@ -6347,6 +6347,7 @@ impl<'a> Binary<'a> {
                         false,
                     ),
                 ),
+                Type::UserType(no) => self.llvm_type(&ns.user_types[*no].ty, ns),
                 _ => unreachable!(),
             }
         }

+ 23 - 11
src/emit/substrate.rs

@@ -741,6 +741,9 @@ impl SubstrateTarget {
                 arg
             }
             ast::Type::Enum(n) => self.decode_ty(binary, function, &ns.enums[*n].ty, data, end, ns),
+            ast::Type::UserType(n) => {
+                self.decode_ty(binary, function, &ns.user_types[*n].ty, data, end, ns)
+            }
             ast::Type::Struct(n) => {
                 let llvm_ty = binary.llvm_type(ty.deref_any(), ns);
 
@@ -1214,17 +1217,26 @@ impl SubstrateTarget {
                     )
                 };
             }
-            ast::Type::Enum(n) => {
-                let arglen = self.encode_primitive(binary, load, &ns.enums[*n].ty, *data, arg, ns);
-
-                *data = unsafe {
-                    binary.builder.build_gep(
-                        *data,
-                        &[binary.context.i32_type().const_int(arglen, false)],
-                        "",
-                    )
-                };
-            }
+            ast::Type::UserType(no) => self.encode_ty(
+                binary,
+                ns,
+                load,
+                packed,
+                function,
+                &ns.user_types[*no].ty,
+                arg,
+                data,
+            ),
+            ast::Type::Enum(no) => self.encode_ty(
+                binary,
+                ns,
+                load,
+                packed,
+                function,
+                &ns.enums[*no].ty,
+                arg,
+                data,
+            ),
             ast::Type::Array(_, dim) if dim[0].is_some() => {
                 let arg = if load {
                     binary

+ 34 - 3
src/sema/ast.rs

@@ -27,9 +27,12 @@ pub enum Type {
     DynamicBytes,
     String,
     Array(Box<Type>, Vec<Option<BigInt>>),
+    /// The usize is an index into enums in the namespace
     Enum(usize),
+    /// The usize is an index into contracts in the namespace
     Struct(usize),
     Mapping(Box<Type>, Box<Type>),
+    /// The usize is an index into contracts in the namespace
     Contract(usize),
     Ref(Box<Type>),
     /// Reference to storage, first bool is true for immutables
@@ -44,6 +47,9 @@ pub enum Type {
         params: Vec<Type>,
         returns: Vec<Type>,
     },
+    /// User type definitions, e.g. `type Foo is int128;`. The usize
+    /// is an index into user_types in the namespace.
+    UserType(usize),
     /// There is no way to declare value in Solidity (should there be?)
     Value,
     Void,
@@ -111,7 +117,7 @@ impl EventDecl {
 }
 
 impl fmt::Display for StructDecl {
-    /// Make the struct name into a string for printing. The enum can be declared either
+    /// Make the struct name into a string for printing. The struct can be declared either
     /// inside or outside a contract.
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match &self.contract {
@@ -334,6 +340,26 @@ impl From<&pt::Type> for Type {
     }
 }
 
+#[derive(PartialEq, Clone, Debug)]
+pub struct UserTypeDecl {
+    pub tags: Vec<Tag>,
+    pub loc: pt::Loc,
+    pub name: String,
+    pub ty: Type,
+    pub contract: Option<String>,
+}
+
+impl fmt::Display for UserTypeDecl {
+    /// Make the user type name into a string for printing. The user type can
+    /// be declared either inside or outside a contract.
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match &self.contract {
+            Some(c) => write!(f, "{}.{}", c, self.name),
+            None => write!(f, "{}", self.name),
+        }
+    }
+}
+
 pub struct Variable {
     pub tags: Vec<Tag>,
     pub name: String,
@@ -356,6 +382,7 @@ pub enum Symbol {
     Event(Vec<(pt::Loc, usize)>),
     Contract(pt::Loc, usize),
     Import(pt::Loc, usize),
+    UserType(pt::Loc, usize),
 }
 
 impl CodeLocation for Symbol {
@@ -365,8 +392,8 @@ impl CodeLocation for Symbol {
             | Symbol::Variable(loc, ..)
             | Symbol::Struct(loc, _)
             | Symbol::Contract(loc, _)
-            | Symbol::Import(loc, _) => *loc,
-
+            | Symbol::Import(loc, _)
+            | Symbol::UserType(loc, _) => *loc,
             Symbol::Event(items) | Symbol::Function(items) => items[0].0,
         }
     }
@@ -410,6 +437,8 @@ pub struct Namespace {
     pub structs: Vec<StructDecl>,
     pub events: Vec<EventDecl>,
     pub contracts: Vec<Contract>,
+    /// All type declarations
+    pub user_types: Vec<UserTypeDecl>,
     /// All functions
     pub functions: Vec<Function>,
     /// Yul functions
@@ -969,6 +998,8 @@ pub enum Builtin {
     WriteUint256LE,
     WriteAddress,
     Accounts,
+    UserTypeWrap,
+    UserTypeUnwrap,
 }
 
 #[derive(PartialEq, Clone, Debug)]

+ 23 - 1
src/sema/builtin.rs

@@ -26,7 +26,7 @@ pub struct Prototype {
 }
 
 // A list of all Solidity builtins functions
-static BUILTIN_FUNCTIONS: Lazy<[Prototype; 25]> = Lazy::new(|| {
+static BUILTIN_FUNCTIONS: Lazy<[Prototype; 27]> = Lazy::new(|| {
     [
         Prototype {
             builtin: Builtin::Assert,
@@ -310,6 +310,28 @@ static BUILTIN_FUNCTIONS: Lazy<[Prototype; 25]> = Lazy::new(|| {
             doc: "ed25519 signature verification",
             constant: false,
         },
+        Prototype {
+            builtin: Builtin::UserTypeWrap,
+            namespace: None,
+            method: Some(Type::UserType(0)),
+            name: "wrap",
+            args: vec![],
+            ret: vec![Type::UserType(0)],
+            target: vec![],
+            doc: "wrap type into user defined type",
+            constant: false,
+        },
+        Prototype {
+            builtin: Builtin::UserTypeUnwrap,
+            namespace: None,
+            method: Some(Type::UserType(0)),
+            name: "unwrap",
+            args: vec![Type::UserType(0)],
+            ret: vec![],
+            target: vec![],
+            doc: "unwrap user defined type",
+            constant: false,
+        },
     ]
 });
 

+ 22 - 0
src/sema/dotgraphviz.rs

@@ -2147,6 +2147,28 @@ impl Namespace {
             }
         }
 
+        // user types
+        if !self.user_types.is_empty() {
+            let types = dot.add_node(Node::new("types", Vec::new()), None, None);
+
+            for decl in &self.user_types {
+                let mut labels = vec![
+                    format!("name:{} ty:{}", decl.name, decl.ty.to_string(self)),
+                    self.loc_to_string(&decl.loc),
+                ];
+
+                if let Some(contract) = &decl.contract {
+                    labels.insert(1, format!("contract: {}", contract));
+                }
+
+                let e = Node::new(&decl.name, labels);
+
+                let node = dot.add_node(e, Some(types), None);
+
+                dot.add_tags(&decl.tags, node);
+            }
+        }
+
         // free functions
         if !self.functions.iter().any(|func| func.contract_no.is_some()) {
             let functions = dot.add_node(Node::new("free_functions", Vec::new()), None, None);

+ 69 - 0
src/sema/expression.rs

@@ -5508,6 +5508,75 @@ fn method_call_pos_args(
         }
     }
 
+    if let Ok(Type::UserType(no)) = ns.resolve_type(
+        context.file_no,
+        context.contract_no,
+        false,
+        var,
+        &mut Vec::new(),
+    ) {
+        if let Some(loc) = call_args_loc {
+            diagnostics.push(Diagnostic::error(
+                loc,
+                "call arguments not allowed on builtins".to_string(),
+            ));
+            return Err(());
+        }
+
+        let elem_ty = ns.user_types[no].ty.clone();
+        let user_ty = Type::UserType(no);
+
+        if func.name == "unwrap" {
+            return if args.len() != 1 {
+                diagnostics.push(Diagnostic::error(
+                    func.loc,
+                    "method ‘unwrap()’ takes one argument".to_string(),
+                ));
+                Err(())
+            } else {
+                let expr = expression(
+                    &args[0],
+                    context,
+                    ns,
+                    symtable,
+                    diagnostics,
+                    ResolveTo::Type(&user_ty),
+                )?;
+
+                Ok(Expression::Builtin(
+                    *loc,
+                    vec![elem_ty],
+                    Builtin::UserTypeUnwrap,
+                    vec![expr.cast(&expr.loc(), &user_ty, true, ns, diagnostics)?],
+                ))
+            };
+        } else if func.name == "wrap" {
+            return if args.len() != 1 {
+                diagnostics.push(Diagnostic::error(
+                    func.loc,
+                    "method ‘wrap()’ takes one argument".to_string(),
+                ));
+                Err(())
+            } else {
+                let expr = expression(
+                    &args[0],
+                    context,
+                    ns,
+                    symtable,
+                    diagnostics,
+                    ResolveTo::Type(&elem_ty),
+                )?;
+
+                Ok(Expression::Builtin(
+                    *loc,
+                    vec![user_ty],
+                    Builtin::UserTypeWrap,
+                    vec![expr.cast(&expr.loc(), &elem_ty, true, ns, diagnostics)?],
+                ))
+            };
+        }
+    }
+
     let var_expr = expression(var, context, ns, symtable, diagnostics, ResolveTo::Unknown)?;
 
     if let Some(expr) =

+ 47 - 44
src/sema/mod.rs

@@ -5,7 +5,6 @@ use ast::{Diagnostic, Mutability};
 use num_bigint::BigInt;
 use num_traits::Signed;
 use num_traits::Zero;
-use solang_parser::pt::Expression;
 use std::{collections::HashMap, ffi::OsStr};
 
 mod address;
@@ -104,9 +103,6 @@ fn sema_file(file: &ResolvedFile, resolver: &mut FileResolver, ns: &mut ast::Nam
             pt::SourceUnitPart::ImportDirective(_, import) => {
                 resolve_import(import, Some(file), file_no, resolver, ns);
             }
-            pt::SourceUnitPart::TypeDefinition(def) => {
-                resolve_type_definition(def, ns);
-            }
             _ => (),
         }
     }
@@ -229,8 +225,6 @@ fn resolve_import(
                         }
                     }
 
-                    ns.check_shadowing(file_no, None, symbol);
-
                     ns.add_symbol(file_no, None, symbol, import);
                 } else {
                     ns.diagnostics.push(ast::Diagnostic::error(
@@ -273,14 +267,10 @@ fn resolve_import(
                     }
                 }
 
-                ns.check_shadowing(file_no, contract_no, &new_symbol);
-
                 ns.add_symbol(file_no, contract_no, &new_symbol, symbol);
             }
         }
         pt::Import::GlobalSymbol(_, symbol, _) => {
-            ns.check_shadowing(file_no, None, symbol);
-
             ns.add_symbol(
                 file_no,
                 None,
@@ -324,30 +314,6 @@ fn resolve_pragma(
     }
 }
 
-/// Resolve type definition and emit a diagnostic error if type is not an elementary value type
-fn resolve_type_definition(def: &pt::TypeDefinition, ns: &mut ast::Namespace) {
-    if let Expression::Type(_, ty) = &def.ty {
-        if !matches!(
-            ty,
-            pt::Type::Address
-                | pt::Type::AddressPayable
-                | pt::Type::Bool
-                | pt::Type::Int(_)
-                | pt::Type::Uint(_)
-                | pt::Type::Bytes(_)
-                | pt::Type::Rational
-        ) {
-            ns.diagnostics.push(ast::Diagnostic::error(
-                def.loc,
-                format!(
-                    "’{}’ is not an elementary value type",
-                    ast::Type::from(ty).to_string(ns),
-                ),
-            ));
-        }
-    }
-}
-
 impl ast::Namespace {
     /// Create a namespace and populate with the parameters for the target
     pub fn new(target: Target) -> Self {
@@ -367,6 +333,7 @@ impl ast::Namespace {
             structs: Vec::new(),
             events: Vec::new(),
             contracts: Vec::new(),
+            user_types: Vec::new(),
             functions: Vec::new(),
             yul_functions: Vec::new(),
             constants: Vec::new(),
@@ -478,6 +445,14 @@ impl ast::Namespace {
                         "location of previous definition".to_string(),
                     ));
                 }
+                ast::Symbol::UserType(loc, _) => {
+                    self.diagnostics.push(ast::Diagnostic::error_with_note(
+                        id.loc,
+                        format!("{} is already defined as an user type", id.name),
+                        *loc,
+                        "location of previous definition".to_string(),
+                    ));
+                }
                 ast::Symbol::Function(_) => unreachable!(),
             }
 
@@ -560,6 +535,14 @@ impl ast::Namespace {
                             "location of previous definition".to_string(),
                         ));
                     }
+                    ast::Symbol::UserType(loc, _) => {
+                        self.diagnostics.push(ast::Diagnostic::warning_with_note(
+                            id.loc,
+                            format!("{} is already defined as an user type", id.name),
+                            *loc,
+                            "location of previous definition".to_string(),
+                        ));
+                    }
                 }
             }
         }
@@ -761,6 +744,9 @@ impl ast::Namespace {
             Some(ast::Symbol::Import(..)) => {
                 ast::Diagnostic::decl_error(id.loc, format!("`{}' is an import", id.name))
             }
+            Some(ast::Symbol::UserType(..)) => {
+                ast::Diagnostic::decl_error(id.loc, format!("`{}' is an user type", id.name))
+            }
             Some(ast::Symbol::Variable(..)) => {
                 ast::Diagnostic::decl_error(id.loc, format!("`{}' is a contract variable", id.name))
             }
@@ -978,6 +964,15 @@ impl ast::Namespace {
                     "previous declaration of contract name".to_string(),
                 ));
             }
+            Some(ast::Symbol::UserType(loc, _)) => {
+                let loc = *loc;
+                self.diagnostics.push(ast::Diagnostic::warning_with_note(
+                    id.loc,
+                    format!("declaration of ‘{}’ shadows type", id.name),
+                    loc,
+                    "previous declaration of type".to_string(),
+                ));
+            }
             Some(ast::Symbol::Import(loc, _)) => {
                 let loc = *loc;
                 self.diagnostics.push(ast::Diagnostic::warning_with_note(
@@ -1320,6 +1315,7 @@ impl ast::Namespace {
                 ));
                 Err(())
             }
+            Some(ast::Symbol::UserType(_, n)) => Ok(ast::Type::UserType(*n)),
         }
     }
 
@@ -1375,36 +1371,43 @@ impl ast::Namespace {
                 }
                 Some(ast::Symbol::Function(_)) => {
                     diagnostics.push(ast::Diagnostic::decl_error(
-                        id.loc,
-                        format!("‘{}’ is a function", id.name),
+                        contract_name.loc,
+                        format!("‘{}’ is a function", contract_name.name),
                     ));
                     return Err(());
                 }
                 Some(ast::Symbol::Variable(..)) => {
                     diagnostics.push(ast::Diagnostic::decl_error(
-                        id.loc,
-                        format!("‘{}’ is a contract variable", id.name),
+                        contract_name.loc,
+                        format!("‘{}’ is a contract variable", contract_name.name),
                     ));
                     return Err(());
                 }
                 Some(ast::Symbol::Event(_)) => {
                     diagnostics.push(ast::Diagnostic::decl_error(
-                        id.loc,
-                        format!("‘{}’ is an event", id.name),
+                        contract_name.loc,
+                        format!("‘{}’ is an event", contract_name.name),
                     ));
                     return Err(());
                 }
                 Some(ast::Symbol::Struct(..)) => {
                     diagnostics.push(ast::Diagnostic::decl_error(
-                        id.loc,
-                        format!("‘{}’ is a struct", id.name),
+                        contract_name.loc,
+                        format!("‘{}’ is a struct", contract_name.name),
                     ));
                     return Err(());
                 }
                 Some(ast::Symbol::Enum(..)) => {
                     diagnostics.push(ast::Diagnostic::decl_error(
-                        id.loc,
-                        format!("‘{}’ is an enum variable", id.name),
+                        contract_name.loc,
+                        format!("‘{}’ is an enum variable", contract_name.name),
+                    ));
+                    return Err(());
+                }
+                Some(ast::Symbol::UserType(..)) => {
+                    diagnostics.push(ast::Diagnostic::decl_error(
+                        contract_name.loc,
+                        format!("‘{}’ is an user type", contract_name.name),
                     ));
                     return Err(());
                 }

+ 72 - 1
src/sema/types.rs

@@ -4,7 +4,7 @@ use super::SOLANA_BUCKET_SIZE;
 use super::{
     ast::{
         BuiltinStruct, Contract, Diagnostic, EnumDecl, EventDecl, Namespace, Parameter, StructDecl,
-        Symbol, Tag, Type,
+        Symbol, Tag, Type, UserTypeDecl,
     },
     SOLANA_SPARSE_ARRAY_SIZE,
 };
@@ -93,6 +93,9 @@ pub fn resolve_typenames<'a>(
 
                 delay.events.push((pos, def, None));
             }
+            pt::SourceUnitPart::TypeDefinition(ty) => {
+                type_decl(ty, file_no, None, ns);
+            }
             _ => (),
         }
     }
@@ -100,6 +103,66 @@ pub fn resolve_typenames<'a>(
     delay
 }
 
+fn type_decl(
+    def: &pt::TypeDefinition,
+    file_no: usize,
+    contract_no: Option<usize>,
+    ns: &mut Namespace,
+) {
+    let mut diagnostics = Vec::new();
+
+    let ty = match ns.resolve_type(file_no, contract_no, false, &def.ty, &mut diagnostics) {
+        Ok(ty) => ty,
+        Err(_) => {
+            ns.diagnostics.extend(diagnostics);
+            return;
+        }
+    };
+
+    // We could permit all types to be defined here, however:
+    // - This would require resolving the types definition after all other types are resolved
+    // - Need for circular checks (type a is b; type b is a;)
+    if !matches!(
+        ty,
+        Type::Address(_) | Type::Bool | Type::Int(_) | Type::Uint(_) | Type::Bytes(_)
+    ) {
+        ns.diagnostics.push(Diagnostic::error(
+            def.ty.loc(),
+            format!("’{}’ is not an elementary value type", ty.to_string(ns)),
+        ));
+        return;
+    }
+
+    let pos = ns.user_types.len();
+
+    if !ns.add_symbol(
+        file_no,
+        contract_no,
+        &def.name,
+        Symbol::UserType(def.name.loc, pos),
+    ) {
+        return;
+    }
+
+    let tags = resolve_tags(
+        def.name.loc.file_no(),
+        "type",
+        &def.doc,
+        None,
+        None,
+        None,
+        ns,
+    );
+
+    ns.user_types.push(UserTypeDecl {
+        tags,
+        loc: def.loc,
+        name: def.name.name.to_string(),
+        ty,
+        contract: contract_no.map(|no| ns.contracts[no].name.to_string()),
+    });
+}
+
 pub fn resolve_fields(delay: ResolveFields, file_no: usize, ns: &mut Namespace) {
     // now we can resolve the fields for the structs
     for (pos, def, contract) in delay.structs {
@@ -261,6 +324,9 @@ fn resolve_contract<'a>(
 
                 delay.events.push((pos, s, Some(contract_no)));
             }
+            pt::ContractPart::TypeDefinition(ty) => {
+                type_decl(ty, file_no, Some(contract_no), ns);
+            }
             _ => (),
         }
     }
@@ -721,6 +787,7 @@ impl Type {
                 s
             }
             Type::Contract(n) => format!("contract {}", ns.contracts[*n].name),
+            Type::UserType(n) => format!("usertype {}", ns.user_types[*n]),
             Type::Ref(r) => r.to_string(ns),
             Type::StorageRef(_, ty) => format!("{} storage", ty.to_string(ns)),
             Type::Void => "void".to_owned(),
@@ -785,6 +852,7 @@ impl Type {
                 )
             }
             Type::InternalFunction { .. } | Type::ExternalFunction { .. } => "function".to_owned(),
+            Type::UserType(n) => ns.user_types[*n].ty.to_signature_string(say_tuple, ns),
             _ => unreachable!(),
         }
     }
@@ -906,6 +974,7 @@ impl Type {
             }
             Type::Mapping(..) => BigInt::zero(),
             Type::Ref(ty) | Type::StorageRef(_, ty) => ty.size_of(ns),
+            Type::UserType(no) => ns.user_types[*no].ty.size_of(ns),
             _ => unimplemented!("sizeof on {:?}", self),
         }
     }
@@ -1112,6 +1181,7 @@ impl Type {
             Type::StorageRef(_, r) => r.is_reference_type(ns),
             Type::InternalFunction { .. } => false,
             Type::ExternalFunction { .. } => false,
+            Type::UserType(no) => ns.user_types[*no].ty.is_reference_type(ns),
             _ => unreachable!(),
         }
     }
@@ -1303,6 +1373,7 @@ impl Type {
             Type::ExternalFunction { .. } => "function".to_owned(),
             Type::Ref(r) => r.to_llvm_string(ns),
             Type::StorageRef(_, r) => r.to_llvm_string(ns),
+            Type::UserType(no) => ns.user_types[*no].ty.to_llvm_string(ns),
             _ => unreachable!(),
         }
     }

+ 41 - 14
tests/contract.rs

@@ -1,9 +1,10 @@
+use path_slash::PathExt;
 use solang::{codegen, file_resolver::FileResolver, parse_and_resolve, sema::ast::Level, Target};
 use std::{
     ffi::OsStr,
     fs::{read_dir, File},
     io::{self, Read},
-    path::PathBuf,
+    path::{Path, PathBuf},
 };
 
 #[test]
@@ -42,19 +43,7 @@ fn recurse_directory(path: PathBuf, target: Target) -> io::Result<()> {
 fn parse_file(path: PathBuf, target: Target) -> io::Result<()> {
     let mut cache = FileResolver::new();
 
-    let mut file = File::open(&path)?;
-
-    let mut source = String::new();
-
-    file.read_to_string(&mut source)?;
-
-    // make sure the path uses unix file separators, this is what the dot file uses
-    let filename = path.to_string_lossy().replace('\\', "/");
-
-    println!("Parsing {} for {}", path.display(), target);
-
-    // The files may have had their end of lines mangled on Windows
-    cache.set_file_contents(&filename, source.replace("\r\n", "\n"));
+    let filename = add_file(&mut cache, &path, target)?;
 
     let mut ns = parse_and_resolve(OsStr::new(&filename), &mut cache, target);
 
@@ -70,6 +59,14 @@ fn parse_file(path: PathBuf, target: Target) -> io::Result<()> {
         );
     }
 
+    #[cfg(windows)]
+    {
+        for file in &mut ns.files {
+            let filename = file.path.to_slash_lossy();
+            file.path = PathBuf::from(filename);
+        }
+    }
+
     let mut path = path;
 
     path.set_extension("dot");
@@ -94,3 +91,33 @@ fn parse_file(path: PathBuf, target: Target) -> io::Result<()> {
 
     Ok(())
 }
+
+fn add_file(cache: &mut FileResolver, path: &Path, target: Target) -> io::Result<String> {
+    let mut file = File::open(&path)?;
+
+    let mut source = String::new();
+
+    file.read_to_string(&mut source)?;
+
+    // make sure the path uses unix file separators, this is what the dot file uses
+    let filename = path.to_slash_lossy();
+
+    println!("Parsing {} for {}", filename, target);
+
+    // The files may have had their end of lines mangled on Windows
+    cache.set_file_contents(&filename, source.replace("\r\n", "\n"));
+
+    for line in source.lines() {
+        if line.starts_with("import") {
+            let start = line.find('"').unwrap();
+            let end = line.rfind('"').unwrap();
+            let file = &line[start + 1..end];
+            let mut import_path = path.parent().unwrap().to_path_buf();
+            import_path.push(file);
+            println!("adding import {}", import_path.display());
+            add_file(cache, &import_path, target)?;
+        }
+    }
+
+    Ok(filename)
+}

+ 16 - 0
tests/contract_testcases/solana/type_decl.dot

@@ -0,0 +1,16 @@
+strict digraph "tests/contract_testcases/solana/type_decl.sol" {
+	Addr [label="name:Addr ty:address payable\ntests/contract_testcases/solana/type_decl.sol:1:1-2:29"]
+	Binary [label="name:Binary ty:bool\ncontract: x\ntests/contract_testcases/solana/type_decl.sol:5:2-21"]
+	contract [label="contract x\ntests/contract_testcases/solana/type_decl.sol:3:1-4:12"]
+	f [label="function f\ncontract: x\ntests/contract_testcases/solana/type_decl.sol:7:2-33\nsignature f(bytes32,bool)\nvisibility public\nmutability nonpayable"]
+	parameters [label="parameters\nusertype Addr \nusertype x.Binary "]
+	diagnostic [label="found contract ‘x’\nlevel Debug\ntests/contract_testcases/solana/type_decl.sol:3:1-4:12"]
+	diagnostic_10 [label="function can be declared ‘pure’\nlevel Warning\ntests/contract_testcases/solana/type_decl.sol:7:2-33"]
+	types -> Addr
+	types -> Binary
+	contracts -> contract
+	contract -> f [label="function"]
+	f -> parameters [label="parameters"]
+	diagnostics -> diagnostic [label="Debug"]
+	diagnostics -> diagnostic_10 [label="Warning"]
+}

+ 8 - 0
tests/contract_testcases/solana/type_decl.sol

@@ -0,0 +1,8 @@
+
+type Addr is address payable;
+
+contract x {
+	type Binary is bool;
+
+	function f(Addr, Binary) public {}
+}

+ 26 - 0
tests/contract_testcases/solana/type_decl_broken.dot

@@ -0,0 +1,26 @@
+strict digraph "tests/contract_testcases/solana/type_decl_broken.sol" {
+	foo [label="name:foo\ncontract: c\ntests/contract_testcases/solana/type_decl_broken.sol:4:9-12\nfield name:f1 ty:int256"]
+	GlobalFoo [label="name:GlobalFoo\ncontract: c\ntests/contract_testcases/solana/type_decl_broken.sol:7:9-18\nfield name:f1 ty:int256"]
+	GlobalFoo_4 [label="name:GlobalFoo ty:address payable\ntests/contract_testcases/solana/type_decl_broken.sol:1:1-34"]
+	Value [label="name:Value ty:uint128\ncontract: c\ntests/contract_testcases/solana/type_decl_broken.sol:9:2-23"]
+	contract [label="contract c\ntests/contract_testcases/solana/type_decl_broken.sol:2:1-3:12"]
+	diagnostic [label="’int256[2]’ is not an elementary value type\nlevel Error\ntests/contract_testcases/solana/type_decl_broken.sol:5:14-20"]
+	diagnostic_11 [label="foo is already defined as a struct\nlevel Error\ntests/contract_testcases/solana/type_decl_broken.sol:6:7-10"]
+	note [label="location of previous definition\ntests/contract_testcases/solana/type_decl_broken.sol:4:9-12"]
+	diagnostic_13 [label="GlobalFoo is already defined as an user type\nlevel Warning\ntests/contract_testcases/solana/type_decl_broken.sol:7:9-18"]
+	note_14 [label="location of previous definition\ntests/contract_testcases/solana/type_decl_broken.sol:1:6-15"]
+	diagnostic_15 [label="Value is already defined as an user type\nlevel Error\ntests/contract_testcases/solana/type_decl_broken.sol:10:9-14"]
+	note_16 [label="location of previous definition\ntests/contract_testcases/solana/type_decl_broken.sol:9:7-12"]
+	structs -> foo
+	structs -> GlobalFoo
+	types -> GlobalFoo_4
+	types -> Value
+	contracts -> contract
+	diagnostics -> diagnostic [label="Error"]
+	diagnostics -> diagnostic_11 [label="Error"]
+	diagnostic_11 -> note [label="note"]
+	diagnostics -> diagnostic_13 [label="Warning"]
+	diagnostic_13 -> note_14 [label="note"]
+	diagnostics -> diagnostic_15 [label="Error"]
+	diagnostic_15 -> note_16 [label="note"]
+}

+ 19 - 0
tests/contract_testcases/solana/type_decl_broken.sol

@@ -0,0 +1,19 @@
+type GlobalFoo is address payable;
+
+contract c {
+	struct foo { int f1; }
+	type foo is int[2];
+	type foo is int;
+	struct GlobalFoo { int f1; }
+
+	type Value is uint128;
+	struct Value { int f1; }
+
+	function inc_and_wrap(int128 v) public returns (Value) {
+		return Value.wrap(v + 1);
+	}
+
+	function dec_and_unwrap(Value v) public returns (uint128) {
+		return Value.unwrap(v) - 1;
+	}
+}

+ 22 - 0
tests/contract_testcases/solana/type_decl_broken_more.dot

@@ -0,0 +1,22 @@
+strict digraph "tests/contract_testcases/solana/type_decl_broken_more.sol" {
+	Addr [label="name:Addr ty:address payable\ntests/contract_testcases/solana/type_decl.sol:1:1-2:29"]
+	Binary [label="name:Binary ty:bool\ncontract: x\ntests/contract_testcases/solana/type_decl.sol:5:2-21"]
+	contract [label="contract x\ntests/contract_testcases/solana/type_decl.sol:3:1-4:12"]
+	f [label="function f\ncontract: x\ntests/contract_testcases/solana/type_decl.sol:7:2-33\nsignature f(bytes32,bool)\nvisibility public\nmutability nonpayable"]
+	parameters [label="parameters\nusertype Addr \nusertype x.Binary "]
+	diagnostic [label="found contract ‘x’\nlevel Debug\ntests/contract_testcases/solana/type_decl.sol:3:1-4:12"]
+	diagnostic_10 [label="function can be declared ‘pure’\nlevel Warning\ntests/contract_testcases/solana/type_decl.sol:7:2-33"]
+	diagnostic_11 [label="‘Addr’ is an user type\nlevel Error\ntests/contract_testcases/solana/type_decl_broken_more.sol:3:14-18"]
+	diagnostic_12 [label="declaration of ‘Addr’ shadows type\nlevel Warning\ntests/contract_testcases/solana/type_decl_broken_more.sol:5:18-22"]
+	note [label="previous declaration of type\ntests/contract_testcases/solana/type_decl.sol:2:6-10"]
+	types -> Addr
+	types -> Binary
+	contracts -> contract
+	contract -> f [label="function"]
+	f -> parameters [label="parameters"]
+	diagnostics -> diagnostic [label="Debug"]
+	diagnostics -> diagnostic_10 [label="Warning"]
+	diagnostics -> diagnostic_11 [label="Error"]
+	diagnostics -> diagnostic_12 [label="Warning"]
+	diagnostic_12 -> note [label="note"]
+}

+ 5 - 0
tests/contract_testcases/solana/type_decl_broken_more.sol

@@ -0,0 +1,5 @@
+import "type_decl.sol";
+
+function foo(Addr.X x) {}
+
+function bar(int Addr) {}

+ 12 - 0
tests/contract_testcases/solana/type_decl_broken_used_as_event.dot

@@ -0,0 +1,12 @@
+strict digraph "tests/contract_testcases/solana/type_decl_broken_used_as_event.sol" {
+	X [label="name:X ty:int256\ntests/contract_testcases/solana/type_decl_broken_used_as_event.sol:1:1-14"]
+	contract [label="contract c\ntests/contract_testcases/solana/type_decl_broken_used_as_event.sol:2:1-3:12"]
+	f [label="function f\ncontract: c\ntests/contract_testcases/solana/type_decl_broken_used_as_event.sol:4:2-21\nsignature f()\nvisibility public\nmutability nonpayable"]
+	diagnostic [label="found contract ‘c’\nlevel Debug\ntests/contract_testcases/solana/type_decl_broken_used_as_event.sol:2:1-3:12"]
+	diagnostic_8 [label="`X' is an user type\nlevel Error\ntests/contract_testcases/solana/type_decl_broken_used_as_event.sol:5:8-9"]
+	types -> X
+	contracts -> contract
+	contract -> f [label="function"]
+	diagnostics -> diagnostic [label="Debug"]
+	diagnostics -> diagnostic_8 [label="Error"]
+}

+ 7 - 0
tests/contract_testcases/solana/type_decl_broken_used_as_event.sol

@@ -0,0 +1,7 @@
+type X is int;
+
+contract c {
+	function f() public {
+		emit X();
+	}
+}

+ 48 - 0
tests/contract_testcases/solana/type_decl_import.dot

@@ -0,0 +1,48 @@
+strict digraph "tests/contract_testcases/solana/type_decl_import.sol" {
+	Addr [label="name:Addr ty:address payable\ntests/contract_testcases/solana/type_decl.sol:1:1-2:29"]
+	Binary [label="name:Binary ty:bool\ncontract: x\ntests/contract_testcases/solana/type_decl.sol:5:2-21"]
+	contract [label="contract d\ntests/contract_testcases/solana/type_decl_import.sol:2:1-3:12"]
+	f [label="function f\ncontract: d\ntests/contract_testcases/solana/type_decl_import.sol:4:2-28\nsignature f(bytes32)\nvisibility public\nmutability nonpayable"]
+	parameters [label="parameters\ncontract x c"]
+	var_decl [label="variable decl usertype Addr a\ntests/contract_testcases/solana/type_decl_import.sol:5:3-41"]
+	builtins [label="builtin UserTypeWrap\ntests/contract_testcases/solana/type_decl_import.sol:5:16-41"]
+	builtins_10 [label="builtin Sender\ntests/contract_testcases/solana/type_decl_import.sol:5:30-40"]
+	var_decl_11 [label="variable decl usertype x.Binary b\ntests/contract_testcases/solana/type_decl_import.sol:6:3-44"]
+	builtins_12 [label="builtin UserTypeWrap\ntests/contract_testcases/solana/type_decl_import.sol:6:20-44"]
+	bool_literal [label="bool literal: false\ntests/contract_testcases/solana/type_decl_import.sol:6:38-43"]
+	expr [label="expression\ntests/contract_testcases/solana/type_decl_import.sol:8:3-12"]
+	call_external_function [label="call external function\ntests/contract_testcases/solana/type_decl_import.sol:8:3-12"]
+	external_function [label="function(usertype Addr,usertype x.Binary) external returns (void)\nx.f\ntests/contract_testcases/solana/type_decl_import.sol:8:3-12"]
+	variable [label="variable: c\ncontract x\ntests/contract_testcases/solana/type_decl_import.sol:8:3-4"]
+	variable_18 [label="variable: a\nusertype Addr\ntests/contract_testcases/solana/type_decl_import.sol:8:7-8"]
+	variable_19 [label="variable: b\nusertype x.Binary\ntests/contract_testcases/solana/type_decl_import.sol:8:10-11"]
+	contract_20 [label="contract x\ntests/contract_testcases/solana/type_decl.sol:3:1-4:12"]
+	f_21 [label="function f\ncontract: x\ntests/contract_testcases/solana/type_decl.sol:7:2-33\nsignature f(bytes32,bool)\nvisibility public\nmutability nonpayable"]
+	parameters_22 [label="parameters\nusertype Addr \nusertype x.Binary "]
+	diagnostic [label="found contract ‘x’\nlevel Debug\ntests/contract_testcases/solana/type_decl.sol:3:1-4:12"]
+	diagnostic_25 [label="function can be declared ‘pure’\nlevel Warning\ntests/contract_testcases/solana/type_decl.sol:7:2-33"]
+	diagnostic_26 [label="found contract ‘d’\nlevel Debug\ntests/contract_testcases/solana/type_decl_import.sol:2:1-3:12"]
+	types -> Addr
+	types -> Binary
+	contracts -> contract
+	contract -> f [label="function"]
+	f -> parameters [label="parameters"]
+	f -> var_decl [label="body"]
+	var_decl -> builtins [label="init"]
+	builtins -> builtins_10 [label="arg #0"]
+	var_decl -> var_decl_11 [label="next"]
+	var_decl_11 -> builtins_12 [label="init"]
+	builtins_12 -> bool_literal [label="arg #0"]
+	var_decl_11 -> expr [label="next"]
+	expr -> call_external_function [label="expr"]
+	call_external_function -> external_function [label="function"]
+	external_function -> variable [label="address"]
+	call_external_function -> variable_18 [label="arg #0"]
+	call_external_function -> variable_19 [label="arg #1"]
+	contracts -> contract_20
+	contract_20 -> f_21 [label="function"]
+	f_21 -> parameters_22 [label="parameters"]
+	diagnostics -> diagnostic [label="Debug"]
+	diagnostics -> diagnostic_25 [label="Warning"]
+	diagnostics -> diagnostic_26 [label="Debug"]
+}

+ 10 - 0
tests/contract_testcases/solana/type_decl_import.sol

@@ -0,0 +1,10 @@
+import "type_decl.sol" as IMP;
+
+contract d {
+	function f(IMP.x c) public {
+		IMP.Addr a = IMP.Addr.wrap(msg.sender);
+		IMP.x.Binary b = IMP.x.Binary.wrap(false);
+
+		c.f(a, b);
+	}
+}