浏览代码

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"
 assert_cmd = "2.0"
 bincode = "1.3"
 bincode = "1.3"
 ed25519-dalek = "1.0"
 ed25519-dalek = "1.0"
+path-slash = "0.1"
 
 
 [package.metadata.docs.rs]
 [package.metadata.docs.rs]
 no-default-features = true
 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
 However it is possible to use storage reference variables in public functions, as
 demonstrated in function all_pumas().
 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
 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::StorageRef(_, ty) => ty_to_abi(ty, ns, registry),
         ast::Type::Ref(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(_) => {
         ast::Type::Bool | ast::Type::Uint(_) | ast::Type::Int(_) => {
             let scalety = match ty {
             let scalety = match ty {
                 ast::Type::Bool => "bool".into(),
                 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)),
             Box::new(expression(e, cfg, contract_no, func, ns, vartab, opt)),
         ),
         ),
         // for some built-ins, we have to inline special case code
         // 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) => {
         ast::Expression::Builtin(loc, ty, ast::Builtin::ArrayPush, args) => {
             if args[0].ty().is_contract_storage() {
             if args[0].ty().is_contract_storage() {
                 if ns.target == Target::Solana || args[0].ty().is_storage_bytes() {
                 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::Ref(r) => EncoderBuilder::encoded_fixed_length(r, ns),
             ast::Type::StorageRef(_, 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!(),
             _ => 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) => {
             ast::Type::Enum(n) => {
                 self.encode_primitive(binary, load, function, &ns.enums[*n].ty, *fixed, arg, ns);
                 self.encode_primitive(binary, load, function, &ns.enums[*n].ty, *fixed, arg, ns);
 
 
@@ -3076,6 +3092,16 @@ impl EthAbiDecoder {
 
 
                 v.into()
                 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),
             _ => 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,
                         false,
                     ),
                     ),
                 ),
                 ),
+                Type::UserType(no) => self.llvm_type(&ns.user_types[*no].ty, ns),
                 _ => unreachable!(),
                 _ => unreachable!(),
             }
             }
         }
         }

+ 23 - 11
src/emit/substrate.rs

@@ -741,6 +741,9 @@ impl SubstrateTarget {
                 arg
                 arg
             }
             }
             ast::Type::Enum(n) => self.decode_ty(binary, function, &ns.enums[*n].ty, data, end, ns),
             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) => {
             ast::Type::Struct(n) => {
                 let llvm_ty = binary.llvm_type(ty.deref_any(), ns);
                 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() => {
             ast::Type::Array(_, dim) if dim[0].is_some() => {
                 let arg = if load {
                 let arg = if load {
                     binary
                     binary

+ 34 - 3
src/sema/ast.rs

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

+ 23 - 1
src/sema/builtin.rs

@@ -26,7 +26,7 @@ pub struct Prototype {
 }
 }
 
 
 // A list of all Solidity builtins functions
 // A list of all Solidity builtins functions
-static BUILTIN_FUNCTIONS: Lazy<[Prototype; 25]> = Lazy::new(|| {
+static BUILTIN_FUNCTIONS: Lazy<[Prototype; 27]> = Lazy::new(|| {
     [
     [
         Prototype {
         Prototype {
             builtin: Builtin::Assert,
             builtin: Builtin::Assert,
@@ -310,6 +310,28 @@ static BUILTIN_FUNCTIONS: Lazy<[Prototype; 25]> = Lazy::new(|| {
             doc: "ed25519 signature verification",
             doc: "ed25519 signature verification",
             constant: false,
             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
         // free functions
         if !self.functions.iter().any(|func| func.contract_no.is_some()) {
         if !self.functions.iter().any(|func| func.contract_no.is_some()) {
             let functions = dot.add_node(Node::new("free_functions", Vec::new()), None, None);
             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)?;
     let var_expr = expression(var, context, ns, symtable, diagnostics, ResolveTo::Unknown)?;
 
 
     if let Some(expr) =
     if let Some(expr) =

+ 47 - 44
src/sema/mod.rs

@@ -5,7 +5,6 @@ use ast::{Diagnostic, Mutability};
 use num_bigint::BigInt;
 use num_bigint::BigInt;
 use num_traits::Signed;
 use num_traits::Signed;
 use num_traits::Zero;
 use num_traits::Zero;
-use solang_parser::pt::Expression;
 use std::{collections::HashMap, ffi::OsStr};
 use std::{collections::HashMap, ffi::OsStr};
 
 
 mod address;
 mod address;
@@ -104,9 +103,6 @@ fn sema_file(file: &ResolvedFile, resolver: &mut FileResolver, ns: &mut ast::Nam
             pt::SourceUnitPart::ImportDirective(_, import) => {
             pt::SourceUnitPart::ImportDirective(_, import) => {
                 resolve_import(import, Some(file), file_no, resolver, ns);
                 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);
                     ns.add_symbol(file_no, None, symbol, import);
                 } else {
                 } else {
                     ns.diagnostics.push(ast::Diagnostic::error(
                     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);
                 ns.add_symbol(file_no, contract_no, &new_symbol, symbol);
             }
             }
         }
         }
         pt::Import::GlobalSymbol(_, symbol, _) => {
         pt::Import::GlobalSymbol(_, symbol, _) => {
-            ns.check_shadowing(file_no, None, symbol);
-
             ns.add_symbol(
             ns.add_symbol(
                 file_no,
                 file_no,
                 None,
                 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 {
 impl ast::Namespace {
     /// Create a namespace and populate with the parameters for the target
     /// Create a namespace and populate with the parameters for the target
     pub fn new(target: Target) -> Self {
     pub fn new(target: Target) -> Self {
@@ -367,6 +333,7 @@ impl ast::Namespace {
             structs: Vec::new(),
             structs: Vec::new(),
             events: Vec::new(),
             events: Vec::new(),
             contracts: Vec::new(),
             contracts: Vec::new(),
+            user_types: Vec::new(),
             functions: Vec::new(),
             functions: Vec::new(),
             yul_functions: Vec::new(),
             yul_functions: Vec::new(),
             constants: Vec::new(),
             constants: Vec::new(),
@@ -478,6 +445,14 @@ impl ast::Namespace {
                         "location of previous definition".to_string(),
                         "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!(),
                 ast::Symbol::Function(_) => unreachable!(),
             }
             }
 
 
@@ -560,6 +535,14 @@ impl ast::Namespace {
                             "location of previous definition".to_string(),
                             "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(..)) => {
             Some(ast::Symbol::Import(..)) => {
                 ast::Diagnostic::decl_error(id.loc, format!("`{}' is an import", id.name))
                 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(..)) => {
             Some(ast::Symbol::Variable(..)) => {
                 ast::Diagnostic::decl_error(id.loc, format!("`{}' is a contract variable", id.name))
                 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(),
                     "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, _)) => {
             Some(ast::Symbol::Import(loc, _)) => {
                 let loc = *loc;
                 let loc = *loc;
                 self.diagnostics.push(ast::Diagnostic::warning_with_note(
                 self.diagnostics.push(ast::Diagnostic::warning_with_note(
@@ -1320,6 +1315,7 @@ impl ast::Namespace {
                 ));
                 ));
                 Err(())
                 Err(())
             }
             }
+            Some(ast::Symbol::UserType(_, n)) => Ok(ast::Type::UserType(*n)),
         }
         }
     }
     }
 
 
@@ -1375,36 +1371,43 @@ impl ast::Namespace {
                 }
                 }
                 Some(ast::Symbol::Function(_)) => {
                 Some(ast::Symbol::Function(_)) => {
                     diagnostics.push(ast::Diagnostic::decl_error(
                     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(());
                     return Err(());
                 }
                 }
                 Some(ast::Symbol::Variable(..)) => {
                 Some(ast::Symbol::Variable(..)) => {
                     diagnostics.push(ast::Diagnostic::decl_error(
                     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(());
                     return Err(());
                 }
                 }
                 Some(ast::Symbol::Event(_)) => {
                 Some(ast::Symbol::Event(_)) => {
                     diagnostics.push(ast::Diagnostic::decl_error(
                     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(());
                     return Err(());
                 }
                 }
                 Some(ast::Symbol::Struct(..)) => {
                 Some(ast::Symbol::Struct(..)) => {
                     diagnostics.push(ast::Diagnostic::decl_error(
                     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(());
                     return Err(());
                 }
                 }
                 Some(ast::Symbol::Enum(..)) => {
                 Some(ast::Symbol::Enum(..)) => {
                     diagnostics.push(ast::Diagnostic::decl_error(
                     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(());
                     return Err(());
                 }
                 }

+ 72 - 1
src/sema/types.rs

@@ -4,7 +4,7 @@ use super::SOLANA_BUCKET_SIZE;
 use super::{
 use super::{
     ast::{
     ast::{
         BuiltinStruct, Contract, Diagnostic, EnumDecl, EventDecl, Namespace, Parameter, StructDecl,
         BuiltinStruct, Contract, Diagnostic, EnumDecl, EventDecl, Namespace, Parameter, StructDecl,
-        Symbol, Tag, Type,
+        Symbol, Tag, Type, UserTypeDecl,
     },
     },
     SOLANA_SPARSE_ARRAY_SIZE,
     SOLANA_SPARSE_ARRAY_SIZE,
 };
 };
@@ -93,6 +93,9 @@ pub fn resolve_typenames<'a>(
 
 
                 delay.events.push((pos, def, None));
                 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
     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) {
 pub fn resolve_fields(delay: ResolveFields, file_no: usize, ns: &mut Namespace) {
     // now we can resolve the fields for the structs
     // now we can resolve the fields for the structs
     for (pos, def, contract) in delay.structs {
     for (pos, def, contract) in delay.structs {
@@ -261,6 +324,9 @@ fn resolve_contract<'a>(
 
 
                 delay.events.push((pos, s, Some(contract_no)));
                 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
                 s
             }
             }
             Type::Contract(n) => format!("contract {}", ns.contracts[*n].name),
             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::Ref(r) => r.to_string(ns),
             Type::StorageRef(_, ty) => format!("{} storage", ty.to_string(ns)),
             Type::StorageRef(_, ty) => format!("{} storage", ty.to_string(ns)),
             Type::Void => "void".to_owned(),
             Type::Void => "void".to_owned(),
@@ -785,6 +852,7 @@ impl Type {
                 )
                 )
             }
             }
             Type::InternalFunction { .. } | Type::ExternalFunction { .. } => "function".to_owned(),
             Type::InternalFunction { .. } | Type::ExternalFunction { .. } => "function".to_owned(),
+            Type::UserType(n) => ns.user_types[*n].ty.to_signature_string(say_tuple, ns),
             _ => unreachable!(),
             _ => unreachable!(),
         }
         }
     }
     }
@@ -906,6 +974,7 @@ impl Type {
             }
             }
             Type::Mapping(..) => BigInt::zero(),
             Type::Mapping(..) => BigInt::zero(),
             Type::Ref(ty) | Type::StorageRef(_, ty) => ty.size_of(ns),
             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),
             _ => unimplemented!("sizeof on {:?}", self),
         }
         }
     }
     }
@@ -1112,6 +1181,7 @@ impl Type {
             Type::StorageRef(_, r) => r.is_reference_type(ns),
             Type::StorageRef(_, r) => r.is_reference_type(ns),
             Type::InternalFunction { .. } => false,
             Type::InternalFunction { .. } => false,
             Type::ExternalFunction { .. } => false,
             Type::ExternalFunction { .. } => false,
+            Type::UserType(no) => ns.user_types[*no].ty.is_reference_type(ns),
             _ => unreachable!(),
             _ => unreachable!(),
         }
         }
     }
     }
@@ -1303,6 +1373,7 @@ impl Type {
             Type::ExternalFunction { .. } => "function".to_owned(),
             Type::ExternalFunction { .. } => "function".to_owned(),
             Type::Ref(r) => r.to_llvm_string(ns),
             Type::Ref(r) => r.to_llvm_string(ns),
             Type::StorageRef(_, 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!(),
             _ => 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 solang::{codegen, file_resolver::FileResolver, parse_and_resolve, sema::ast::Level, Target};
 use std::{
 use std::{
     ffi::OsStr,
     ffi::OsStr,
     fs::{read_dir, File},
     fs::{read_dir, File},
     io::{self, Read},
     io::{self, Read},
-    path::PathBuf,
+    path::{Path, PathBuf},
 };
 };
 
 
 #[test]
 #[test]
@@ -42,19 +43,7 @@ fn recurse_directory(path: PathBuf, target: Target) -> io::Result<()> {
 fn parse_file(path: PathBuf, target: Target) -> io::Result<()> {
 fn parse_file(path: PathBuf, target: Target) -> io::Result<()> {
     let mut cache = FileResolver::new();
     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);
     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;
     let mut path = path;
 
 
     path.set_extension("dot");
     path.set_extension("dot");
@@ -94,3 +91,33 @@ fn parse_file(path: PathBuf, target: Target) -> io::Result<()> {
 
 
     Ok(())
     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);
+	}
+}