Explorar o código

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

Signed-off-by: Sean Young <sean@mess.org>
Sean Young %!s(int64=5) %!d(string=hai) anos
pai
achega
a6d3588c01
Modificáronse 6 ficheiros con 276 adicións e 6 borrados
  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
 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
 ________________________________

+ 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
 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
 - libraries functions are always statically linked into the contract code
 - Solang generates WebAssembly or BPF rather than EVM. This means that the ``assembly {}``
   statement using EVM instructions is not supported
-- Calling parent contract via ``super``
 
 Unique features to Solang:
 

+ 8 - 0
src/codegen/cfg.rs

@@ -1110,6 +1110,14 @@ fn function_cfg(
         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.nonpayable = if ns.target == Target::Substrate {
         !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
 pub fn is_reserved(fname: &str) -> bool {
-    if fname == "type" {
+    if fname == "type" || fname == "super" {
         return true;
     }
 

+ 90 - 1
src/sema/expression.rs

@@ -18,7 +18,7 @@ use super::ast::{
     Builtin, CallTy, Diagnostic, Expression, Function, Namespace, StringLocation, Symbol, Type,
 };
 use super::builtin;
-use super::contracts::is_base;
+use super::contracts::{is_base, visit_bases};
 use super::eval::eval_const_number;
 use super::format::string_format;
 use super::symtable::Symtable;
@@ -3724,6 +3724,32 @@ pub fn available_functions(
     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
 pub fn call_position_args(
     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
         if let Some(call_contract_no) = ns.resolve_contract(file_no, &namespace) {
             if ns.contracts[call_contract_no].is_library() {
@@ -4705,6 +4763,37 @@ fn method_call_named_args(
     symtable: &Symtable,
 ) -> Result<Expression, ()> {
     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
         if let Some(call_contract_no) = ns.resolve_contract(file_no, &namespace) {
             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"
     );
 }
+
+#[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());
+}