Эх сурвалжийг харах

Represent type(f) correctly in the ast (#1609)

This makes the ast correct, and also the following code is now parsed
correctly:
    
            function foo() {
                    type(int);
            }

 `type(enum-type).min` or `type(enum).min` is now supported. 

This fixes all the issues wrt `type(T)`  in the solc tests.

Signed-off-by: Sean Young <sean@mess.org>
Sean Young 1 жил өмнө
parent
commit
92a6c6c89d

+ 0 - 10
src/bin/languageserver/mod.rs

@@ -662,16 +662,6 @@ impl<'a> Builder<'a> {
                     },
                 ));
             }
-            ast::Expression::CodeLiteral { loc, .. } => {
-                self.hovers.push((
-                    loc.file_no(),
-                    HoverEntry {
-                        start: loc.start(),
-                        stop: loc.exclusive_end(),
-                        val: make_code_block("bytes"),
-                    },
-                ));
-            }
             ast::Expression::NumberLiteral { loc, ty, value,.. } => {
                 if let Type::Enum(id) = ty {
                     self.references.push((

+ 59 - 8
src/codegen/expression.rs

@@ -32,8 +32,7 @@ use crate::sema::{
 use crate::Target;
 use num_bigint::{BigInt, Sign};
 use num_traits::{FromPrimitive, One, ToPrimitive, Zero};
-use solang_parser::pt;
-use solang_parser::pt::{CodeLocation, Loc};
+use solang_parser::pt::{self, CodeLocation, Loc};
 use std::{cmp::Ordering, ops::Mul};
 
 pub fn expression(
@@ -1006,6 +1005,22 @@ pub fn expression(
         } if expr.len() == 1 && ns.target == Target::EVM => {
             builtin_evm_gasprice(loc, expr, cfg, contract_no, func, ns, vartab, opt)
         }
+        ast::Expression::Builtin {
+            loc,
+            tys,
+            kind: ast::Builtin::TypeMin | ast::Builtin::TypeMax,
+            ..
+        } => {
+            let Ok((_, value)) = eval_const_number(expr, ns, &mut Diagnostics::default()) else {
+                unreachable!();
+            };
+
+            Expression::NumberLiteral {
+                loc: *loc,
+                ty: tys[0].clone(),
+                value,
+            }
+        }
         ast::Expression::Builtin {
             loc,
             tys,
@@ -1051,7 +1066,6 @@ pub fn expression(
             right,
             opt,
         ),
-        ast::Expression::InterfaceId { loc, contract_no } => interfaceid(ns, *contract_no, loc),
         ast::Expression::BoolLiteral { loc, value } => Expression::BoolLiteral {
             loc: *loc,
             value: *value,
@@ -1061,9 +1075,6 @@ pub fn expression(
             ty: ty.clone(),
             value: value.clone(),
         },
-        ast::Expression::CodeLiteral {
-            loc, contract_no, ..
-        } => code(loc, *contract_no, ns, opt),
         ast::Expression::NumberLiteral { loc, ty, value } => Expression::NumberLiteral {
             loc: *loc,
             ty: ty.clone(),
@@ -1145,8 +1156,9 @@ pub fn expression(
                 ty,
             }
         }
-
-        ast::Expression::List { .. } => unreachable!("List shall not appear in the CFG"),
+        ast::Expression::TypeOperator { .. } | ast::Expression::List { .. } => {
+            unreachable!("List and Type Operator shall not appear in the CFG")
+        }
     }
 }
 
@@ -2238,6 +2250,45 @@ fn expr_builtin(
                 value: 0.into(),
             }
         }
+        ast::Builtin::TypeName => {
+            let ast::Expression::TypeOperator {
+                ty: Type::Contract(no),
+                ..
+            } = &args[0]
+            else {
+                unreachable!();
+            };
+
+            let value = ns.contracts[*no].id.name.as_bytes().to_owned();
+
+            Expression::BytesLiteral {
+                loc: *loc,
+                ty: Type::String,
+                value,
+            }
+        }
+        ast::Builtin::TypeInterfaceId => {
+            let ast::Expression::TypeOperator {
+                ty: Type::Contract(contract_no),
+                ..
+            } = &args[0]
+            else {
+                unreachable!();
+            };
+
+            interfaceid(ns, *contract_no, loc)
+        }
+        ast::Builtin::TypeRuntimeCode | ast::Builtin::TypeCreatorCode => {
+            let ast::Expression::TypeOperator {
+                ty: Type::Contract(contract_no),
+                ..
+            } = &args[0]
+            else {
+                unreachable!();
+            };
+
+            code(loc, *contract_no, ns, opt)
+        }
         _ => {
             let arguments: Vec<Expression> = args
                 .iter()

+ 0 - 3
src/codegen/mod.rs

@@ -9,7 +9,6 @@ pub(crate) mod dispatch;
 pub(crate) mod encoding;
 mod events;
 mod expression;
-mod external_functions;
 pub(super) mod polkadot;
 mod reaching_definitions;
 pub mod revert;
@@ -196,8 +195,6 @@ fn contract(contract_no: usize, ns: &mut Namespace, opt: &Options) {
         let mut cfg_no = 0;
         let mut all_cfg = Vec::new();
 
-        external_functions::add_external_functions(contract_no, ns);
-
         // all the functions should have a cfg_no assigned, so we can generate call instructions to the correct function
         for (_, func_cfg) in ns.contracts[contract_no].all_functions.iter_mut() {
             *func_cfg = cfg_no;

+ 35 - 27
src/codegen/statements/mod.rs

@@ -170,37 +170,45 @@ pub(crate) fn statement(
             }
         }
         Statement::Expression(_, _, expr) => {
-            if let ast::Expression::Assign { left, right, .. } = &expr {
-                if should_remove_assignment(left, func, opt, ns) {
-                    let mut params = SideEffectsCheckParameters {
-                        cfg,
-                        contract_no,
-                        func: Some(func),
-                        ns,
-                        vartab,
-                        opt,
-                    };
-                    right.recurse(&mut params, process_side_effects_expressions);
-
-                    return;
+            match expr {
+                ast::Expression::Assign { left, right, .. } => {
+                    if should_remove_assignment(left, func, opt, ns) {
+                        let mut params = SideEffectsCheckParameters {
+                            cfg,
+                            contract_no,
+                            func: Some(func),
+                            ns,
+                            vartab,
+                            opt,
+                        };
+                        right.recurse(&mut params, process_side_effects_expressions);
+
+                        return;
+                    }
                 }
-            } else if let ast::Expression::Builtin { args, .. } = expr {
-                // When array pop and push are top-level expressions, they can be removed
-                if should_remove_assignment(expr, func, opt, ns) {
-                    let mut params = SideEffectsCheckParameters {
-                        cfg,
-                        contract_no,
-                        func: Some(func),
-                        ns,
-                        vartab,
-                        opt,
-                    };
-                    for arg in args {
-                        arg.recurse(&mut params, process_side_effects_expressions);
+                ast::Expression::Builtin { args, .. } => {
+                    // When array pop and push are top-level expressions, they can be removed
+                    if should_remove_assignment(expr, func, opt, ns) {
+                        let mut params = SideEffectsCheckParameters {
+                            cfg,
+                            contract_no,
+                            func: Some(func),
+                            ns,
+                            vartab,
+                            opt,
+                        };
+                        for arg in args {
+                            arg.recurse(&mut params, process_side_effects_expressions);
+                        }
+
+                        return;
                     }
-
+                }
+                ast::Expression::TypeOperator { .. } => {
+                    // just a stray type(int), no code to generate
                     return;
                 }
+                _ => (),
             }
 
             let _ = expression(expr, cfg, contract_no, Some(func), ns, vartab, opt);

+ 17 - 16
src/sema/ast.rs

@@ -356,6 +356,8 @@ pub struct Function {
     /// This indexmap stores the accounts this functions needs to be called on Solana
     /// The string is the account's name
     pub solana_accounts: RefCell<IndexMap<String, SolanaAccount>>,
+    /// List of contracts this function creates
+    pub creates: Vec<(pt::Loc, usize)>,
 }
 
 /// This struct represents a Solana account. There is no name field, because
@@ -463,6 +465,7 @@ impl Function {
             annotations: ConstructorAnnotations::default(),
             mangled_name_contracts: HashSet::new(),
             solana_accounts: IndexMap::new().into(),
+            creates: Vec::new(),
         }
     }
 
@@ -888,12 +891,6 @@ pub enum Expression {
         ty: Type,
         value: Vec<u8>,
     },
-    CodeLiteral {
-        loc: pt::Loc,
-        contract_no: usize,
-        // runtime code rather than deployer (evm only)
-        runtime: bool,
-    },
     NumberLiteral {
         loc: pt::Loc,
         ty: Type,
@@ -1248,10 +1245,6 @@ pub enum Expression {
         kind: Builtin,
         args: Vec<Expression>,
     },
-    InterfaceId {
-        loc: pt::Loc,
-        contract_no: usize,
-    },
     List {
         loc: pt::Loc,
         list: Vec<Expression>,
@@ -1268,6 +1261,10 @@ pub enum Expression {
         ty: Type,
         event_no: usize,
     },
+    TypeOperator {
+        loc: pt::Loc,
+        ty: Type,
+    },
 }
 
 #[derive(PartialEq, Eq, Clone, Default, Debug)]
@@ -1508,16 +1505,15 @@ impl Recurse for Expression {
                 }
 
                 Expression::NumberLiteral { .. }
-                | Expression::InterfaceId { .. }
                 | Expression::InternalFunction { .. }
                 | Expression::ConstantVariable { .. }
                 | Expression::StorageVariable { .. }
                 | Expression::Variable { .. }
                 | Expression::RationalNumberLiteral { .. }
-                | Expression::CodeLiteral { .. }
                 | Expression::BytesLiteral { .. }
                 | Expression::BoolLiteral { .. }
-                | Expression::EventSelector { .. } => (),
+                | Expression::EventSelector { .. }
+                | Expression::TypeOperator { .. } => (),
             }
         }
     }
@@ -1528,7 +1524,6 @@ impl CodeLocation for Expression {
         match self {
             Expression::BoolLiteral { loc, .. }
             | Expression::BytesLiteral { loc, .. }
-            | Expression::CodeLiteral { loc, .. }
             | Expression::NumberLiteral { loc, .. }
             | Expression::RationalNumberLiteral { loc, .. }
             | Expression::StructLiteral { loc, .. }
@@ -1587,11 +1582,11 @@ impl CodeLocation for Expression {
             | Expression::Assign { loc, .. }
             | Expression::List { loc, list: _ }
             | Expression::FormatString { loc, format: _ }
-            | Expression::InterfaceId { loc, .. }
             | Expression::And { loc, .. }
             | Expression::NamedMember { loc, .. }
             | Expression::UserDefinedOperator { loc, .. }
-            | Expression::EventSelector { loc, .. } => *loc,
+            | Expression::EventSelector { loc, .. }
+            | Expression::TypeOperator { loc, .. } => *loc,
         }
     }
 }
@@ -1777,6 +1772,12 @@ pub enum Builtin {
     ECRecover,
     StringConcat,
     BytesConcat,
+    TypeMin,
+    TypeMax,
+    TypeName,
+    TypeInterfaceId,
+    TypeRuntimeCode,
+    TypeCreatorCode,
 }
 
 #[derive(PartialEq, Eq, Clone, Debug)]

+ 12 - 32
src/sema/dotgraphviz.rs

@@ -293,26 +293,6 @@ impl Dot {
                     Some(parent_rel),
                 );
             }
-            Expression::CodeLiteral {
-                loc,
-                contract_no,
-                runtime,
-            } => {
-                let labels = vec![
-                    format!(
-                        "code {}literal contract {}",
-                        if *runtime { "runtime " } else { "" },
-                        ns.contracts[*contract_no].id,
-                    ),
-                    ns.loc_to_string(PathDisplay::FullPath, loc),
-                ];
-
-                self.add_node(
-                    Node::new("code_literal", labels),
-                    Some(parent),
-                    Some(parent_rel),
-                );
-            }
             Expression::NumberLiteral { loc, ty, value } => {
                 let labels = vec![
                     format!("{} literal: {}", ty.to_string(ns), value),
@@ -1362,18 +1342,6 @@ impl Dot {
                     self.add_expression(arg, func, ns, node, format!("arg #{no}"));
                 }
             }
-            Expression::InterfaceId { loc, contract_no } => {
-                let labels = vec![
-                    format!("interfaceid contract {}", ns.contracts[*contract_no].id),
-                    ns.loc_to_string(PathDisplay::FullPath, loc),
-                ];
-
-                self.add_node(
-                    Node::new("interfaceid", labels),
-                    Some(parent),
-                    Some(parent_rel),
-                );
-            }
             Expression::UserDefinedOperator {
                 loc,
                 oper,
@@ -1447,6 +1415,18 @@ impl Dot {
                     Some(parent_rel),
                 );
             }
+            Expression::TypeOperator { loc, ty } => {
+                let labels = vec![
+                    format!("type({})", ty.to_string(ns)),
+                    ns.loc_to_string(PathDisplay::FullPath, loc),
+                ];
+
+                self.add_node(
+                    Node::new("type_operator", labels),
+                    Some(parent),
+                    Some(parent_rel),
+                );
+            }
         }
     }
 

+ 38 - 1
src/sema/eval.rs

@@ -1,7 +1,7 @@
 // SPDX-License-Identifier: Apache-2.0
 
 use super::{
-    ast::{Diagnostic, Expression, Namespace, Type},
+    ast::{Builtin, Diagnostic, Expression, Namespace, Type},
     diagnostics::Diagnostics,
     Recurse,
 };
@@ -198,6 +198,43 @@ pub fn eval_const_number(
                 Err(EvaluationError::NotAConstant)
             }
         }
+        Expression::Builtin {
+            loc,
+            kind: Builtin::TypeMin,
+            args,
+            ..
+        } => {
+            let Expression::TypeOperator { ty, .. } = &args[0] else {
+                unreachable!();
+            };
+
+            let value = if let Type::Int(bits) = ty {
+                BigInt::zero().sub(BigInt::one().shl(*bits as usize - 1))
+            } else {
+                BigInt::zero()
+            };
+
+            Ok((*loc, value))
+        }
+        Expression::Builtin {
+            loc,
+            kind: Builtin::TypeMax,
+            args,
+            ..
+        } => {
+            let Expression::TypeOperator { ty, .. } = &args[0] else {
+                unreachable!();
+            };
+
+            let value = match ty {
+                Type::Uint(bits) => BigInt::one().shl(*bits as usize).sub(1),
+                Type::Int(bits) => BigInt::one().shl(*bits as usize - 1).sub(1),
+                Type::Enum(no) => (ns.enums[*no].values.len() - 1).into(),
+                _ => unreachable!(),
+            };
+
+            Ok((*loc, value))
+        }
         _ => {
             diagnostics.push(Diagnostic::error(
                 expr.loc(),

+ 104 - 60
src/sema/expression/constructor.rs

@@ -14,6 +14,7 @@ use solang_parser::diagnostics::Diagnostic;
 use solang_parser::pt;
 use solang_parser::pt::{CodeLocation, Visibility};
 use std::collections::BTreeMap;
+use std::mem::swap;
 
 /// Resolve an new contract expression with positional arguments
 fn constructor(
@@ -26,11 +27,23 @@ fn constructor(
     symtable: &mut Symtable,
     diagnostics: &mut Diagnostics,
 ) -> Result<Expression, ()> {
+    if !ns.contracts[no].instantiable {
+        diagnostics.push(Diagnostic::error(
+            *loc,
+            format!(
+                "cannot construct '{}' of type '{}'",
+                ns.contracts[no].id, ns.contracts[no].ty
+            ),
+        ));
+
+        return Err(());
+    }
+
     // The current contract cannot be constructed with new. In order to create
     // the contract, we need the code hash of the contract. Part of that code
     // will be code we're emitted here. So we end up with a crypto puzzle.
-    let context_contract_no = match context.contract_no {
-        Some(n) if n == no => {
+    if let Some(context_contract_no) = context.contract_no {
+        if context_contract_no == no {
             diagnostics.push(Diagnostic::error(
                 *loc,
                 format!(
@@ -40,42 +53,30 @@ fn constructor(
             ));
             return Err(());
         }
-        Some(n) => n,
-        None => {
+
+        // check for circular references
+        if circular_reference(no, context_contract_no, ns) {
             diagnostics.push(Diagnostic::error(
                 *loc,
-                "new contract not allowed in this context".to_string(),
+                format!(
+                    "circular reference creating contract '{}'",
+                    ns.contracts[no].id
+                ),
             ));
             return Err(());
         }
-    };
-
-    if !ns.contracts[no].instantiable {
-        diagnostics.push(Diagnostic::error(
-            *loc,
-            format!(
-                "cannot construct '{}' of type '{}'",
-                ns.contracts[no].id, ns.contracts[no].ty
-            ),
-        ));
 
-        return Err(());
-    }
-
-    // check for circular references
-    if circular_reference(no, context_contract_no, ns) {
-        diagnostics.push(Diagnostic::error(
-            *loc,
-            format!(
-                "circular reference creating contract '{}'",
-                ns.contracts[no].id
-            ),
-        ));
-        return Err(());
+        if !ns.contracts[context_contract_no].creates.contains(&no) {
+            ns.contracts[context_contract_no].creates.push(no);
+        }
     }
 
-    if !ns.contracts[context_contract_no].creates.contains(&no) {
-        ns.contracts[context_contract_no].creates.push(no);
+    // This is not always in a function: e.g. contract variable:
+    // contract C {
+    //      D code = new D();
+    // }
+    if let Some(function_no) = context.function_no {
+        ns.functions[function_no].creates.push((*loc, no));
     }
 
     match match_constructor_to_args(loc, args, no, context, ns, symtable, diagnostics) {
@@ -262,11 +263,24 @@ pub fn constructor_named_args(
         diagnostics,
     )?;
 
+    if !ns.contracts[no].instantiable {
+        diagnostics.push(Diagnostic::error(
+            *loc,
+            format!(
+                "cannot construct '{}' of type '{}'",
+                ns.contracts[no].id, ns.contracts[no].ty
+            ),
+        ));
+
+        return Err(());
+    }
+
     // The current contract cannot be constructed with new. In order to create
     // the contract, we need the code hash of the contract. Part of that code
     // will be code we're emitted here. So we end up with a crypto puzzle.
-    let context_contract_no = match context.contract_no {
-        Some(n) if n == no => {
+
+    if let Some(context_contract_no) = context.contract_no {
+        if context_contract_no == no {
             diagnostics.push(Diagnostic::error(
                 *loc,
                 format!(
@@ -276,42 +290,30 @@ pub fn constructor_named_args(
             ));
             return Err(());
         }
-        Some(n) => n,
-        None => {
+
+        // check for circular references
+        if circular_reference(no, context_contract_no, ns) {
             diagnostics.push(Diagnostic::error(
                 *loc,
-                "new contract not allowed in this context".to_string(),
+                format!(
+                    "circular reference creating contract '{}'",
+                    ns.contracts[no].id
+                ),
             ));
             return Err(());
         }
-    };
 
-    if !ns.contracts[no].instantiable {
-        diagnostics.push(Diagnostic::error(
-            *loc,
-            format!(
-                "cannot construct '{}' of type '{}'",
-                ns.contracts[no].id, ns.contracts[no].ty
-            ),
-        ));
-
-        return Err(());
-    }
-
-    // check for circular references
-    if circular_reference(no, context_contract_no, ns) {
-        diagnostics.push(Diagnostic::error(
-            *loc,
-            format!(
-                "circular reference creating contract '{}'",
-                ns.contracts[no].id
-            ),
-        ));
-        return Err(());
+        if !ns.contracts[context_contract_no].creates.contains(&no) {
+            ns.contracts[context_contract_no].creates.push(no);
+        }
     }
 
-    if !ns.contracts[context_contract_no].creates.contains(&no) {
-        ns.contracts[context_contract_no].creates.push(no);
+    // This is not always in a function: e.g. contract variable:
+    // contract C {
+    //      D code = new D({});
+    // }
+    if let Some(function_no) = context.function_no {
+        ns.functions[function_no].creates.push((*loc, no));
     }
 
     let mut arguments: BTreeMap<&str, &pt::Expression> = BTreeMap::new();
@@ -673,6 +675,48 @@ pub(super) fn deprecated_constructor_arguments(
     Ok(())
 }
 
+/// Check that we are not creating contracts where we cannot
+pub(crate) fn check_circular_reference(contract_no: usize, ns: &mut Namespace) {
+    let mut creates = Vec::new();
+    let mut diagnostics = Diagnostics::default();
+
+    swap(&mut creates, &mut ns.contracts[contract_no].creates);
+
+    for function_no in ns.contracts[contract_no].all_functions.keys() {
+        for (loc, no) in &ns.functions[*function_no].creates {
+            if contract_no == *no {
+                diagnostics.push(Diagnostic::error(
+                    *loc,
+                    format!(
+                        "cannot construct current contract '{}'",
+                        ns.contracts[*no].id
+                    ),
+                ));
+                continue;
+            }
+
+            // check for circular references
+            if circular_reference(*no, contract_no, ns) {
+                diagnostics.push(Diagnostic::error(
+                    *loc,
+                    format!(
+                        "circular reference creating contract '{}'",
+                        ns.contracts[*no].id
+                    ),
+                ));
+                continue;
+            }
+
+            if !creates.contains(no) {
+                creates.push(*no);
+            }
+        }
+    }
+
+    swap(&mut creates, &mut ns.contracts[contract_no].creates);
+    ns.diagnostics.extend(diagnostics);
+}
+
 /// When calling a constructor on Solana, we must verify it the contract we are instantiating has
 /// a program id annotation and require the accounts call argument if the call is inside a loop.
 pub(super) fn solana_constructor_check(

+ 31 - 0
src/sema/expression/function_call.rs

@@ -284,6 +284,37 @@ pub fn function_call_pos_args(
     symtable: &mut Symtable,
     diagnostics: &mut Diagnostics,
 ) -> Result<Expression, ()> {
+    // type(..) expression
+    if id.identifiers.len() == 1 && id.identifiers[0].name == "type" {
+        match args.len() {
+            0 => {
+                diagnostics.push(Diagnostic::error(
+                    *loc,
+                    "missing type argument to type() operator".to_string(),
+                ));
+                return Err(());
+            }
+            1 => (),
+            _ => {
+                diagnostics.push(Diagnostic::error(
+                    *loc,
+                    "type() operator takes a single argument".to_string(),
+                ));
+                return Err(());
+            }
+        }
+
+        let ty = ns.resolve_type(
+            context.file_no,
+            context.contract_no,
+            ResolveTypeContext::FunctionType,
+            &args[0],
+            diagnostics,
+        )?;
+
+        return Ok(Expression::TypeOperator { loc: *loc, ty });
+    }
+
     if context.constant {
         diagnostics.push(Diagnostic::error(
             *loc,

+ 148 - 139
src/sema/expression/member_access.rs

@@ -10,17 +10,15 @@ use crate::sema::expression::function_call::function_type;
 use crate::sema::expression::integers::bigint_to_expression;
 use crate::sema::expression::resolve_expression::expression;
 use crate::sema::expression::{ExprContext, ResolveTo};
-use crate::sema::namespace::ResolveTypeContext;
 use crate::sema::solana_accounts::BuiltinAccounts;
 use crate::sema::symtable::Symtable;
 use crate::sema::unused_variable::{assigned_variable, used_variable};
 use crate::Target;
-use num_bigint::{BigInt, Sign};
-use num_traits::{FromPrimitive, One, Zero};
+use num_bigint::BigInt;
+use num_traits::FromPrimitive;
 use solang_parser::diagnostics::{Diagnostic, Note};
 use solang_parser::pt;
 use solang_parser::pt::CodeLocation;
-use std::ops::{Shl, Sub};
 
 /// Resolve an member access expression
 pub(super) fn member_access(
@@ -146,16 +144,12 @@ pub(super) fn member_access(
         }
     }
 
-    // is of the form "type(x).field", like type(c).min
-    if let pt::Expression::FunctionCall(_, name, args) = e {
-        if let pt::Expression::Variable(func_name) = name.as_ref() {
-            if func_name.name == "type" {
-                return type_name_expr(loc, args, id, context, ns, diagnostics, resolve_to);
-            }
-        }
+    let expr = expression(e, context, ns, symtable, diagnostics, resolve_to)?;
+
+    if let Expression::TypeOperator { .. } = &expr {
+        return type_name_expr(loc, expr, id, context, ns, diagnostics);
     }
 
-    let expr = expression(e, context, ns, symtable, diagnostics, resolve_to)?;
     let expr_ty = expr.ty();
 
     if let Type::Struct(struct_ty) = expr_ty.deref_memory() {
@@ -717,155 +711,170 @@ fn event_selector(
 /// Resolve type(x).foo
 fn type_name_expr(
     loc: &pt::Loc,
-    args: &[pt::Expression],
+    expr: Expression,
     field: &pt::Identifier,
     context: &mut ExprContext,
     ns: &mut Namespace,
     diagnostics: &mut Diagnostics,
-    resolve_to: ResolveTo,
 ) -> Result<Expression, ()> {
-    if args.is_empty() {
-        diagnostics.push(Diagnostic::error(
-            *loc,
-            "missing argument to type()".to_string(),
-        ));
-        return Err(());
-    }
-
-    if args.len() > 1 {
-        diagnostics.push(Diagnostic::error(
-            *loc,
-            format!("got {} arguments to type(), only one expected", args.len(),),
-        ));
-        return Err(());
-    }
+    let Expression::TypeOperator { ty, .. } = &expr else {
+        unreachable!();
+    };
 
-    let ty = ns.resolve_type(
-        context.file_no,
-        context.contract_no,
-        ResolveTypeContext::FunctionType,
-        &args[0],
-        diagnostics,
-    )?;
+    match field.name.as_str() {
+        "min" | "max" if matches!(ty, Type::Uint(_) | Type::Int(_) | Type::Enum(..)) => {
+            let ty = if matches!(ty, Type::Enum(..)) {
+                Type::Uint(8)
+            } else {
+                ty.clone()
+            };
+            let kind = if field.name == "min" {
+                Builtin::TypeMin
+            } else {
+                Builtin::TypeMax
+            };
 
-    match (&ty, field.name.as_str()) {
-        (Type::Uint(_), "min") => {
-            bigint_to_expression(loc, &BigInt::zero(), ns, diagnostics, resolve_to, None)
-        }
-        (Type::Uint(bits), "max") => {
-            let max = BigInt::one().shl(*bits as usize).sub(1);
-            bigint_to_expression(loc, &max, ns, diagnostics, resolve_to, None)
-        }
-        (Type::Int(bits), "min") => {
-            let min = BigInt::zero().sub(BigInt::one().shl(*bits as usize - 1));
-            bigint_to_expression(loc, &min, ns, diagnostics, resolve_to, None)
-        }
-        (Type::Int(bits), "max") => {
-            let max = BigInt::one().shl(*bits as usize - 1).sub(1);
-            bigint_to_expression(loc, &max, ns, diagnostics, resolve_to, None)
+            return Ok(Expression::Builtin {
+                loc: *loc,
+                tys: vec![ty],
+                kind,
+                args: vec![expr],
+            });
         }
-        (Type::Contract(n), "name") => Ok(Expression::BytesLiteral {
-            loc: *loc,
-            ty: Type::String,
-            value: ns.contracts[*n].id.name.as_bytes().to_vec(),
-        }),
-        (Type::Contract(n), "interfaceId") => {
-            let contract = &ns.contracts[*n];
-
-            if !contract.is_interface() {
-                diagnostics.push(Diagnostic::error(
-                    *loc,
-                    format!(
-                        "type(…).interfaceId is permitted on interface, not {} {}",
-                        contract.ty, contract.id
-                    ),
-                ));
-                Err(())
-            } else {
-                Ok(Expression::InterfaceId {
-                    loc: *loc,
-                    contract_no: *n,
-                })
-            }
+        "name" if matches!(ty, Type::Contract(..)) => {
+            return Ok(Expression::Builtin {
+                loc: *loc,
+                tys: vec![Type::String],
+                kind: Builtin::TypeName,
+                args: vec![expr],
+            })
         }
-        (Type::Contract(no), "program_id") => {
-            let contract = &ns.contracts[*no];
+        "interfaceId" => {
+            if let Type::Contract(no) = ty {
+                let contract = &ns.contracts[*no];
 
-            if let Some(v) = &contract.program_id {
-                Ok(Expression::NumberLiteral {
-                    loc: *loc,
-                    ty: Type::Address(false),
-                    value: BigInt::from_bytes_be(Sign::Plus, v),
-                })
-            } else {
-                diagnostics.push(Diagnostic::error(
-                    *loc,
-                    format!(
-                        "{} '{}' has no declared program_id",
-                        contract.ty, contract.id
-                    ),
-                ));
-                Err(())
+                return if !contract.is_interface() {
+                    diagnostics.push(Diagnostic::error(
+                        *loc,
+                        format!(
+                            "type(…).interfaceId is permitted on interface, not {} {}",
+                            contract.ty, contract.id
+                        ),
+                    ));
+                    Err(())
+                } else {
+                    Ok(Expression::Builtin {
+                        loc: *loc,
+                        tys: vec![Type::FunctionSelector],
+                        kind: Builtin::TypeInterfaceId,
+                        args: vec![expr],
+                    })
+                };
             }
         }
-        (Type::Contract(no), "creationCode") | (Type::Contract(no), "runtimeCode") => {
-            let contract_no = match context.contract_no {
-                Some(contract_no) => contract_no,
-                None => {
+        "creationCode" | "runtimeCode" => {
+            if let Type::Contract(no) = ty {
+                if !ns.contracts[*no].instantiable {
                     diagnostics.push(Diagnostic::error(
                         *loc,
                         format!(
-                            "type().{} not permitted outside of contract code",
-                            field.name
+                            "cannot construct '{}' of type '{}'",
+                            ns.contracts[*no].id, ns.contracts[*no].ty
                         ),
                     ));
+
                     return Err(());
                 }
-            };
 
-            // check for circular references
-            if *no == contract_no {
-                diagnostics.push(Diagnostic::error(
-                    *loc,
-                    format!(
-                        "containing our own contract code for '{}' would generate infinite size contract",
-                        ns.contracts[*no].id
-                    ),
-                ));
-                return Err(());
-            }
+                // This is not always in a function: e.g. contract constant:
+                // contract C {
+                //      bytes constant code = type(D).runtimeCode;
+                // }
+                if let Some(function_no) = context.function_no {
+                    ns.functions[function_no].creates.push((*loc, *no));
+                }
 
-            if circular_reference(*no, contract_no, ns) {
-                diagnostics.push(Diagnostic::error(
-                    *loc,
-                    format!(
-                        "circular reference creating contract code for '{}'",
-                        ns.contracts[*no].id
-                    ),
-                ));
-                return Err(());
-            }
+                if let Some(contract_no) = context.contract_no {
+                    // check for circular references
+                    if *no == contract_no {
+                        diagnostics.push(Diagnostic::error(
+                            *loc,
+                            format!(
+                                "cannot construct current contract '{}'",
+                                ns.contracts[*no].id
+                            ),
+                        ));
+                        return Err(());
+                    }
 
-            if !ns.contracts[contract_no].creates.contains(no) {
-                ns.contracts[contract_no].creates.push(*no);
-            }
+                    if circular_reference(*no, contract_no, ns) {
+                        diagnostics.push(Diagnostic::error(
+                            *loc,
+                            format!(
+                                "circular reference creating contract code for '{}'",
+                                ns.contracts[*no].id
+                            ),
+                        ));
+                        return Err(());
+                    }
 
-            Ok(Expression::CodeLiteral {
-                loc: *loc,
-                contract_no: *no,
-                runtime: field.name == "runtimeCode",
-            })
-        }
-        _ => {
-            diagnostics.push(Diagnostic::error(
-                *loc,
-                format!(
-                    "type '{}' does not have type function {}",
-                    ty.to_string(ns),
-                    field.name
-                ),
-            ));
-            Err(())
+                    if !ns.contracts[contract_no].creates.contains(no) {
+                        ns.contracts[contract_no].creates.push(*no);
+                    }
+                }
+
+                let kind = if field.name == "runtimeCode" {
+                    if ns.target == Target::EVM {
+                        let notes: Vec<_> = ns.contracts[*no]
+                            .variables
+                            .iter()
+                            .filter_map(|v| {
+                                if v.immutable {
+                                    Some(Note {
+                                        loc: v.loc,
+                                        message: format!("immutable variable {}", v.name),
+                                    })
+                                } else {
+                                    None
+                                }
+                            })
+                            .collect();
+
+                        if !notes.is_empty() {
+                            diagnostics.push(Diagnostic::error_with_notes(
+                                *loc,
+                                format!(
+                                    "runtimeCode is not available for contract '{}' with immutuables",
+                                    ns.contracts[*no].id
+                                ),
+                                notes,
+                            ));
+                        }
+                    }
+
+                    Builtin::TypeRuntimeCode
+                } else {
+                    Builtin::TypeCreatorCode
+                };
+
+                return Ok(Expression::Builtin {
+                    loc: *loc,
+                    tys: vec![Type::DynamicBytes],
+                    kind,
+                    args: vec![expr],
+                });
+            }
         }
-    }
+        _ => (),
+    };
+
+    diagnostics.push(Diagnostic::error(
+        *loc,
+        format!(
+            "type '{}' does not have type function {}",
+            ty.to_string(ns),
+            field.name
+        ),
+    ));
+    Err(())
 }

+ 2 - 2
src/sema/expression/mod.rs

@@ -434,7 +434,8 @@ impl Expression {
                         }
                     }
 
-                    diagnostics.push(Diagnostic::cast_error(
+                    // solc does not detect this problem, just warn about it
+                    diagnostics.push(Diagnostic::warning(
                         *loc,
                         format!(
                             "enum {} has no value with ordinal {}",
@@ -442,7 +443,6 @@ impl Expression {
                             big_number
                         ),
                     ));
-                    return Err(());
                 }
 
                 let to_width = enum_ty.ty.bits(ns);

+ 1 - 2
src/sema/expression/retrieve_type.rs

@@ -16,7 +16,6 @@ impl RetrieveType for Expression {
             | Expression::NotEqual { .. }
             | Expression::Not { .. }
             | Expression::StringCompare { .. } => Type::Bool,
-            Expression::CodeLiteral { .. } => Type::DynamicBytes,
             Expression::BytesLiteral { ty, .. }
             | Expression::NumberLiteral { ty, .. }
             | Expression::RationalNumberLiteral { ty, .. }
@@ -78,8 +77,8 @@ impl RetrieveType for Expression {
                 list[0].ty()
             }
             Expression::Constructor { contract_no, .. } => Type::Contract(*contract_no),
-            Expression::InterfaceId { .. } => Type::FunctionSelector,
             Expression::FormatString { .. } => Type::String,
+            Expression::TypeOperator { .. } => Type::Void,
         }
     }
 }

+ 5 - 1
src/codegen/external_functions.rs → src/sema/external_functions.rs

@@ -150,7 +150,11 @@ fn check_statement(stmt: &Statement, call_list: &mut CallList) -> bool {
             cond.recurse(call_list, check_expression);
         }
         Statement::Expression(_, _, expr) => {
-            expr.recurse(call_list, check_expression);
+            // if expression is a singular Expression::InternalFunction, then does nothing
+            // and it's never called.
+            if !matches!(expr, Expression::InternalFunction { .. }) {
+                expr.recurse(call_list, check_expression);
+            }
         }
         Statement::Delete(_, _, expr) => {
             expr.recurse(call_list, check_expression);

+ 10 - 2
src/sema/mod.rs

@@ -1,14 +1,14 @@
 // SPDX-License-Identifier: Apache-2.0
 
 use self::{
-    expression::strings::unescape,
+    expression::{constructor::check_circular_reference, strings::unescape},
     functions::{resolve_params, resolve_returns},
     symtable::Symtable,
     unused_variable::check_unused_errors,
+    unused_variable::{check_unused_events, check_unused_namespace_variables},
     variables::variable_decl,
 };
 use crate::file_resolver::{FileResolver, ResolvedFile};
-use crate::sema::unused_variable::{check_unused_events, check_unused_namespace_variables};
 use num_bigint::BigInt;
 use solang_parser::{
     doccomment::{parse_doccomments, DocComment},
@@ -26,6 +26,7 @@ pub mod diagnostics;
 mod dotgraphviz;
 pub(crate) mod eval;
 pub(crate) mod expression;
+mod external_functions;
 pub mod file;
 mod format;
 mod function_annotation;
@@ -186,6 +187,13 @@ fn sema_file(file: &ResolvedFile, resolver: &mut FileResolver, ns: &mut ast::Nam
         let _ = statements::resolve_function_body(func, &[], file_no, None, func_no, ns);
     }
 
+    if !ns.diagnostics.any_errors() {
+        for contract_no in 0..ns.contracts.len() {
+            external_functions::add_external_functions(contract_no, ns);
+            check_circular_reference(contract_no, ns);
+        }
+    }
+
     // check for stray semi colons
     for part in &pt.0 {
         match part {

+ 48 - 40
tests/contract_testcases/evm/pragmas.dot

@@ -1,52 +1,60 @@
 strict digraph "tests/contract_testcases/evm/pragmas.sol" {
-	contract [label="contract C\ntests/contract_testcases/evm/pragmas.sol:9:1-14"]
+	E [label="name: E\ncontract: C\ntests/contract_testcases/evm/pragmas.sol:10:2-20\nvalue: a\nvalue: b\nvalue: c"]
+	contract [label="contract C\ntests/contract_testcases/evm/pragmas.sol:9:1-12:2"]
+	var [label="variable maxE\nvisibility internal\nconstant\nuint8\ntests/contract_testcases/evm/pragmas.sol:11:2-35"]
+	builtins [label="builtin TypeMax\ntests/contract_testcases/evm/pragmas.sol:11:24-35"]
+	type_operator [label="type(enum C.E)\ntests/contract_testcases/evm/pragmas.sol:11:24-31"]
 	pragma [label="name: foo\nvalue: bar\ntests/contract_testcases/evm/pragmas.sol:1:1-15"]
-	pragma_4 [label="name: abicoder\nvalue: v2\ntests/contract_testcases/evm/pragmas.sol:2:1-19"]
-	pragma_5 [label="name: solidity\ntests/contract_testcases/evm/pragmas.sol:4:1-28"]
+	pragma_9 [label="name: abicoder\nvalue: v2\ntests/contract_testcases/evm/pragmas.sol:2:1-19"]
+	pragma_10 [label="name: solidity\ntests/contract_testcases/evm/pragmas.sol:4:1-28"]
 	range [label="version: 0 - 1\ntests/contract_testcases/evm/pragmas.sol:4:17-22"]
-	range_7 [label="version: 0 - 2\ntests/contract_testcases/evm/pragmas.sol:4:23-28"]
-	pragma_8 [label="name: abicoder\nvalue: 'v2'\ntests/contract_testcases/evm/pragmas.sol:5:1-21"]
-	pragma_9 [label="name: solidity\ntests/contract_testcases/evm/pragmas.sol:6:1-57"]
+	range_12 [label="version: 0 - 2\ntests/contract_testcases/evm/pragmas.sol:4:23-28"]
+	pragma_13 [label="name: abicoder\nvalue: 'v2'\ntests/contract_testcases/evm/pragmas.sol:5:1-21"]
+	pragma_14 [label="name: solidity\ntests/contract_testcases/evm/pragmas.sol:6:1-57"]
 	operator [label="version: ^0.5.16\ntests/contract_testcases/evm/pragmas.sol:6:17-24"]
 	or [label="||\ntests/contract_testcases/evm/pragmas.sol:6:25-44"]
-	operator_12 [label="version: =0.8.22\ntests/contract_testcases/evm/pragmas.sol:6:25-32"]
-	operator_13 [label="version: >=0.8.21\ntests/contract_testcases/evm/pragmas.sol:6:36-44"]
-	operator_14 [label="version: <=2\ntests/contract_testcases/evm/pragmas.sol:6:45-48"]
-	operator_15 [label="version: ~1\ntests/contract_testcases/evm/pragmas.sol:6:49-51"]
+	operator_17 [label="version: =0.8.22\ntests/contract_testcases/evm/pragmas.sol:6:25-32"]
+	operator_18 [label="version: >=0.8.21\ntests/contract_testcases/evm/pragmas.sol:6:36-44"]
+	operator_19 [label="version: <=2\ntests/contract_testcases/evm/pragmas.sol:6:45-48"]
+	operator_20 [label="version: ~1\ntests/contract_testcases/evm/pragmas.sol:6:49-51"]
 	plain [label="version: 0.6.2\ntests/contract_testcases/evm/pragmas.sol:6:52-57"]
-	pragma_17 [label="name: solidity\ntests/contract_testcases/evm/pragmas.sol:7:1-40"]
-	or_18 [label="||\ntests/contract_testcases/evm/pragmas.sol:7:17-40"]
-	range_19 [label="version: 0.4 - 1\ntests/contract_testcases/evm/pragmas.sol:7:17-24"]
-	range_20 [label="version: 0.3 - 0.5.16\ntests/contract_testcases/evm/pragmas.sol:7:28-40"]
+	pragma_22 [label="name: solidity\ntests/contract_testcases/evm/pragmas.sol:7:1-40"]
+	or_23 [label="||\ntests/contract_testcases/evm/pragmas.sol:7:17-40"]
+	range_24 [label="version: 0.4 - 1\ntests/contract_testcases/evm/pragmas.sol:7:17-24"]
+	range_25 [label="version: 0.3 - 0.5.16\ntests/contract_testcases/evm/pragmas.sol:7:28-40"]
 	diagnostic [label="unknown pragma 'foo' with value 'bar'\nlevel Error\ntests/contract_testcases/evm/pragmas.sol:1:1-15"]
-	diagnostic_23 [label="pragma 'abicoder' ignored\nlevel Debug\ntests/contract_testcases/evm/pragmas.sol:2:1-19"]
-	diagnostic_24 [label="'4294967296' is not a valid number\nlevel Error\ntests/contract_testcases/evm/pragmas.sol:3:17-28"]
-	diagnostic_25 [label="version ranges can only be combined with the || operator\nlevel Error\ntests/contract_testcases/evm/pragmas.sol:4:1-28"]
-	diagnostic_26 [label="pragma 'abicoder' ignored\nlevel Debug\ntests/contract_testcases/evm/pragmas.sol:5:1-21"]
-	diagnostic_27 [label="found contract 'C'\nlevel Debug\ntests/contract_testcases/evm/pragmas.sol:9:1-14"]
+	diagnostic_28 [label="pragma 'abicoder' ignored\nlevel Debug\ntests/contract_testcases/evm/pragmas.sol:2:1-19"]
+	diagnostic_29 [label="'4294967296' is not a valid number\nlevel Error\ntests/contract_testcases/evm/pragmas.sol:3:17-28"]
+	diagnostic_30 [label="version ranges can only be combined with the || operator\nlevel Error\ntests/contract_testcases/evm/pragmas.sol:4:1-28"]
+	diagnostic_31 [label="pragma 'abicoder' ignored\nlevel Debug\ntests/contract_testcases/evm/pragmas.sol:5:1-21"]
+	diagnostic_32 [label="found contract 'C'\nlevel Debug\ntests/contract_testcases/evm/pragmas.sol:9:1-12:2"]
+	enums -> E
 	contracts -> contract
+	contract -> var [label="variable"]
+	var -> builtins [label="initializer"]
+	builtins -> type_operator [label="arg #0"]
 	pragmas -> pragma
-	pragmas -> pragma_4
-	pragmas -> pragma_5
-	pragma_5 -> range [label="version 0"]
-	pragma_5 -> range_7 [label="version 1"]
-	pragmas -> pragma_8
 	pragmas -> pragma_9
-	pragma_9 -> operator [label="version 0"]
-	pragma_9 -> or [label="version 1"]
-	or -> operator_12 [label="left"]
-	or -> operator_13 [label="right"]
-	pragma_9 -> operator_14 [label="version 2"]
-	pragma_9 -> operator_15 [label="version 3"]
-	pragma_9 -> plain [label="version 4"]
-	pragmas -> pragma_17
-	pragma_17 -> or_18 [label="version 0"]
-	or_18 -> range_19 [label="left"]
-	or_18 -> range_20 [label="right"]
+	pragmas -> pragma_10
+	pragma_10 -> range [label="version 0"]
+	pragma_10 -> range_12 [label="version 1"]
+	pragmas -> pragma_13
+	pragmas -> pragma_14
+	pragma_14 -> operator [label="version 0"]
+	pragma_14 -> or [label="version 1"]
+	or -> operator_17 [label="left"]
+	or -> operator_18 [label="right"]
+	pragma_14 -> operator_19 [label="version 2"]
+	pragma_14 -> operator_20 [label="version 3"]
+	pragma_14 -> plain [label="version 4"]
+	pragmas -> pragma_22
+	pragma_22 -> or_23 [label="version 0"]
+	or_23 -> range_24 [label="left"]
+	or_23 -> range_25 [label="right"]
 	diagnostics -> diagnostic [label="Error"]
-	diagnostics -> diagnostic_23 [label="Debug"]
-	diagnostics -> diagnostic_24 [label="Error"]
-	diagnostics -> diagnostic_25 [label="Error"]
-	diagnostics -> diagnostic_26 [label="Debug"]
-	diagnostics -> diagnostic_27 [label="Debug"]
+	diagnostics -> diagnostic_28 [label="Debug"]
+	diagnostics -> diagnostic_29 [label="Error"]
+	diagnostics -> diagnostic_30 [label="Error"]
+	diagnostics -> diagnostic_31 [label="Debug"]
+	diagnostics -> diagnostic_32 [label="Debug"]
 }

+ 4 - 1
tests/contract_testcases/evm/pragmas.sol

@@ -6,7 +6,10 @@ pragma abicoder "v2";
 pragma solidity ^0.5.16 =0.8.22 || >=0.8.21 <=2 ~1 0.6.2;
 pragma solidity 0.4 - 1 || 0.3 - 0.5.16;
 
-contract C {}
+contract C {
+	enum E { a, b, c }
+	uint8 constant maxE = type(E).max;
+}
 
 // ---- Expect: dot ----
 // ---- Expect: diagnostics ----

+ 39 - 0
tests/contract_testcases/evm/type.sol

@@ -0,0 +1,39 @@
+contract C {
+	enum E { a, b, c }
+
+	function test1() public {
+		type(int);
+	}
+
+	function test2() public {
+		int a = type(int);
+	}
+
+	function test3() public {
+		assert(type(E).min == 0);
+		assert(type(E).max == 2);
+	}
+
+	function test4() public {
+		int a = type().min;
+	}
+
+	function test5() public {
+		int a = type(int, bool).min;
+	}
+
+	function test6() public {
+		int a = type(bool).min;
+	}
+
+	function test7() public {
+		E e = E(3);
+	}
+}
+
+// ---- Expect: diagnostics ----
+// error: 9:11-20: function or method does not return a value
+// error: 18:11-17: missing type argument to type() operator
+// error: 22:11-26: type() operator takes a single argument
+// error: 26:11-25: type 'bool' does not have type function min
+// warning: 30:9-13: enum enum C.E has no value with ordinal 3

+ 1 - 1
tests/contract_testcases/polkadot/contracts/creation_code_01.sol

@@ -5,4 +5,4 @@
             }
         }
 // ---- Expect: diagnostics ----
-// error: 4:34-53: containing our own contract code for 'a' would generate infinite size contract
+// error: 4:34-53: cannot construct current contract 'a'

+ 1 - 1
tests/evm.rs

@@ -255,7 +255,7 @@ fn ethereum_solidity_tests() {
         })
         .sum();
 
-    assert_eq!(errors, 916);
+    assert_eq!(errors, 909);
 }
 
 fn set_file_contents(source: &str, path: &Path) -> (FileResolver, Vec<String>) {

+ 6 - 0
tests/polkadot_tests/enums.rs

@@ -38,6 +38,12 @@ fn weekdays() {
 
                 x = Weekday(2);
                 assert(x == Weekday.Wednesday);
+
+                assert(type(Weekday).max == 6);
+                assert(type(Weekday).min == 0);
+
+                // stray type() does not do anything.
+                type(Weekday);
             }
         }",
     );