浏览代码

Implement super.f() to call base contract's implementation

Signed-off-by: Sean Young <sean@mess.org>
Sean Young 5 年之前
父节点
当前提交
a6d3588c01
共有 6 个文件被更改,包括 276 次插入6 次删除
  1. 52 2
      docs/language.rst
  2. 1 2
      docs/status.rst
  3. 8 0
      src/codegen/cfg.rs
  4. 1 1
      src/sema/builtin.rs
  5. 90 1
      src/sema/expression.rs
  6. 124 0
      tests/substrate_tests/inheritance.rs

+ 52 - 2
docs/language.rst

@@ -1999,8 +1999,8 @@ contracts it is overriding.
         }
         }
     }
     }
 
 
-Calling function in specific base contract
-__________________________________________
+Calling function in base contract
+_________________________________
 
 
 When a virtual function is called, the dispatch is *virtual*. If the function being called is
 When a virtual function is called, the dispatch is *virtual*. If the function being called is
 overriden in another contract, then the overriding function is called. For example:
 overriden in another contract, then the overriding function is called. For example:
@@ -2036,6 +2036,56 @@ overriden in another contract, then the overriding function is called. For examp
         }
         }
     }
     }
 
 
+Rather than specifying the base contract, use ``super`` as the contract to call the base contract
+function.
+
+.. code-block:: javascript
+
+    contract a is b {
+        function baz() public returns (uint64) {
+            // this will return 1
+            return super.foo();
+        }
+
+        function foo() internal override returns (uint64) {
+            return 2;
+        }
+    }
+
+    contract b {
+        function foo() internal virtual returns (uint64) {
+            return 1;
+        }
+    }
+
+If there are multiple base contracts which the define the same function, the function of the first base
+contract is called.
+
+.. code-block:: javascript
+
+    contract a is b1, b2 {
+        function baz() public returns (uint64) {
+            // this will return 100
+            return super.foo();
+        }
+
+        function foo() internal override(b2, b2) returns (uint64) {
+            return 2;
+        }
+    }
+
+    contract b1 {
+        function foo() internal virtual returns (uint64) {
+            return 100;
+        }
+   }
+
+    contract b2 {
+        function foo() internal virtual returns (uint64) {
+            return 200;
+        }
+    }
+
 
 
 Specifying constructor arguments
 Specifying constructor arguments
 ________________________________
 ________________________________

+ 1 - 2
docs/status.rst

@@ -23,13 +23,12 @@ Solang wants to be compatible with the latest version of
 The project is under active development, and new language features are being added
 The project is under active development, and new language features are being added
 on a continuous basis.
 on a continuous basis.
 
 
-Missing features:
+Differences:
 
 
 - ``immutable`` is not supported. Note this is impossible to implement on any than chain other than Ethereum; this is purely an ethereum feature
 - ``immutable`` is not supported. Note this is impossible to implement on any than chain other than Ethereum; this is purely an ethereum feature
 - libraries functions are always statically linked into the contract code
 - libraries functions are always statically linked into the contract code
 - Solang generates WebAssembly or BPF rather than EVM. This means that the ``assembly {}``
 - Solang generates WebAssembly or BPF rather than EVM. This means that the ``assembly {}``
   statement using EVM instructions is not supported
   statement using EVM instructions is not supported
-- Calling parent contract via ``super``
 
 
 Unique features to Solang:
 Unique features to Solang:
 
 

+ 8 - 0
src/codegen/cfg.rs

@@ -1110,6 +1110,14 @@ fn function_cfg(
         false
         false
     };
     };
 
 
+    // if a function is virtual, and it is overriden, do not make it public
+    // Otherwise the runtime function dispatch will have two identical functions to dispatch to
+    if func.is_virtual
+        && Some(ns.contracts[contract_no].virtual_functions[&func.signature]) != function_no
+    {
+        cfg.public = false;
+    }
+
     cfg.ty = func.ty;
     cfg.ty = func.ty;
     cfg.nonpayable = if ns.target == Target::Substrate {
     cfg.nonpayable = if ns.target == Target::Substrate {
         !func.is_constructor() && !func.is_payable()
         !func.is_constructor() && !func.is_payable()

+ 1 - 1
src/sema/builtin.rs

@@ -396,7 +396,7 @@ pub fn builtin_var(
 
 
 /// Is name reserved for builtins
 /// Is name reserved for builtins
 pub fn is_reserved(fname: &str) -> bool {
 pub fn is_reserved(fname: &str) -> bool {
-    if fname == "type" {
+    if fname == "type" || fname == "super" {
         return true;
         return true;
     }
     }
 
 

+ 90 - 1
src/sema/expression.rs

@@ -18,7 +18,7 @@ use super::ast::{
     Builtin, CallTy, Diagnostic, Expression, Function, Namespace, StringLocation, Symbol, Type,
     Builtin, CallTy, Diagnostic, Expression, Function, Namespace, StringLocation, Symbol, Type,
 };
 };
 use super::builtin;
 use super::builtin;
-use super::contracts::is_base;
+use super::contracts::{is_base, visit_bases};
 use super::eval::eval_const_number;
 use super::eval::eval_const_number;
 use super::format::string_format;
 use super::format::string_format;
 use super::symtable::Symtable;
 use super::symtable::Symtable;
@@ -3724,6 +3724,32 @@ pub fn available_functions(
     list
     list
 }
 }
 
 
+/// Create a list of functions that can be called via super
+pub fn available_super_functions(name: &str, contract_no: usize, ns: &Namespace) -> Vec<usize> {
+    let mut list = Vec::new();
+
+    for base_contract_no in visit_bases(contract_no, ns).into_iter().rev() {
+        if base_contract_no == contract_no {
+            continue;
+        }
+
+        list.extend(
+            ns.contracts[base_contract_no]
+                .all_functions
+                .keys()
+                .filter_map(|func_no| {
+                    if ns.functions[*func_no].name == name {
+                        Some(*func_no)
+                    } else {
+                        None
+                    }
+                }),
+        );
+    }
+
+    list
+}
+
 /// Resolve a function call with positional arguments
 /// Resolve a function call with positional arguments
 pub fn call_position_args(
 pub fn call_position_args(
     loc: &pt::Loc,
     loc: &pt::Loc,
@@ -4083,6 +4109,38 @@ fn method_call_pos_args(
             );
             );
         }
         }
 
 
+        // is it a call to super
+        if namespace.name == "super" {
+            if let Some(cur_contract_no) = contract_no {
+                if let Some(loc) = call_args_loc {
+                    ns.diagnostics.push(Diagnostic::error(
+                        loc,
+                        "call arguments not allowed on super calls".to_string(),
+                    ));
+                    return Err(());
+                }
+
+                return call_position_args(
+                    loc,
+                    func,
+                    pt::FunctionTy::Function,
+                    args,
+                    file_no,
+                    available_super_functions(&func.name, cur_contract_no, ns),
+                    false,
+                    contract_no,
+                    ns,
+                    symtable,
+                );
+            } else {
+                ns.diagnostics.push(Diagnostic::error(
+                    *loc,
+                    "super not available outside contracts".to_string(),
+                ));
+                return Err(());
+            }
+        }
+
         // library or base contract call
         // library or base contract call
         if let Some(call_contract_no) = ns.resolve_contract(file_no, &namespace) {
         if let Some(call_contract_no) = ns.resolve_contract(file_no, &namespace) {
             if ns.contracts[call_contract_no].is_library() {
             if ns.contracts[call_contract_no].is_library() {
@@ -4705,6 +4763,37 @@ fn method_call_named_args(
     symtable: &Symtable,
     symtable: &Symtable,
 ) -> Result<Expression, ()> {
 ) -> Result<Expression, ()> {
     if let pt::Expression::Variable(namespace) = var {
     if let pt::Expression::Variable(namespace) = var {
+        // is it a call to super
+        if namespace.name == "super" {
+            if let Some(cur_contract_no) = contract_no {
+                if let Some(loc) = call_args_loc {
+                    ns.diagnostics.push(Diagnostic::error(
+                        loc,
+                        "call arguments not allowed on super calls".to_string(),
+                    ));
+                    return Err(());
+                }
+
+                return function_call_with_named_args(
+                    loc,
+                    func_name,
+                    args,
+                    file_no,
+                    available_super_functions(&func_name.name, cur_contract_no, ns),
+                    false,
+                    contract_no,
+                    ns,
+                    symtable,
+                );
+            } else {
+                ns.diagnostics.push(Diagnostic::error(
+                    *loc,
+                    "super not available outside contracts".to_string(),
+                ));
+                return Err(());
+            }
+        }
+
         // library or base contract call
         // library or base contract call
         if let Some(call_contract_no) = ns.resolve_contract(file_no, &namespace) {
         if let Some(call_contract_no) = ns.resolve_contract(file_no, &namespace) {
             if ns.contracts[call_contract_no].is_library() {
             if ns.contracts[call_contract_no].is_library() {

+ 124 - 0
tests/substrate_tests/inheritance.rs

@@ -1874,3 +1874,127 @@ fn cast_contract() {
         "implicit conversion not allowed since contract foo is not a base contract of contract IFoo"
         "implicit conversion not allowed since contract foo is not a base contract of contract IFoo"
     );
     );
 }
 }
+
+#[test]
+fn test_super() {
+    let ns = parse_and_resolve(r#"contract super {}"#, Target::Substrate);
+
+    assert_eq!(
+        first_error(ns.diagnostics),
+        "‘super’ shadows name of a builtin"
+    );
+
+    let ns = parse_and_resolve(
+        r#"
+        function f1() { super.a(); }
+        "#,
+        Target::Substrate,
+    );
+
+    assert_eq!(
+        first_error(ns.diagnostics),
+        "super not available outside contracts"
+    );
+
+    let ns = parse_and_resolve(
+        r#"
+        contract a {
+            function f1() public {}
+        }
+
+        contract b is a {
+            function f2() public {
+                super.f2();
+            }
+        }"#,
+        Target::Substrate,
+    );
+
+    assert_eq!(first_error(ns.diagnostics), "unknown function or type ‘f2’");
+
+    let mut runtime = build_solidity(
+        r##"
+        contract b is a {
+            function bar() public returns (uint64) {
+                super.foo();
+
+                return var;
+            }
+
+            function foo() internal override {
+                var = 103;
+            }
+        }
+
+        contract a {
+            uint64 var;
+
+            function foo() internal virtual {
+                var = 102;
+            }
+        }"##,
+    );
+
+    runtime.constructor(0, Vec::new());
+    runtime.function("bar", Vec::new());
+
+    assert_eq!(runtime.vm.output, 102u64.encode());
+
+    let mut runtime = build_solidity(
+        r##"
+        contract b is a {
+            function bar() public returns (uint64) {
+                super.foo({x: 10});
+
+                return var;
+            }
+
+            function foo2(uint64 x) internal {
+                var = 103 + x;
+            }
+        }
+
+        contract a {
+            uint64 var;
+
+            function foo(uint64 x) internal virtual {
+                var = 102 + x;
+            }
+        }"##,
+    );
+
+    runtime.constructor(0, Vec::new());
+    runtime.function("bar", Vec::new());
+
+    assert_eq!(runtime.vm.output, 112u64.encode());
+
+    let mut runtime = build_solidity(
+        r##"
+        contract b is a, aa {
+            function bar() public returns (uint64) {
+                return super.foo({x: 10});
+            }
+
+            function foo(uint64 x) public override(a, aa) returns (uint64) {
+                return 103 + x;
+            }
+        }
+
+        contract a {
+            function foo(uint64 x) public virtual returns (uint64) {
+                return 102 + x;
+            }
+        }
+
+        contract aa {
+            function foo(uint64 x) public virtual returns (uint64) {
+                return 202 + x;
+            }
+        }"##,
+    );
+
+    runtime.constructor(0, Vec::new());
+    runtime.function("bar", Vec::new());
+
+    assert_eq!(runtime.vm.output, 112u64.encode());
+}