Repository: ipython/ipython

Issue:
%%time cell magic suppresses cell output
The following code does not display the value of `x` when run.
```
%%time
x = 10
x = x*2
x
```

Expected output includes the execution time and the value of `x`.

Using the `%time` line magic seems to work, even for multi-line code.

Additional info:
ipython_version: 7.5.0
os_name: nt
platform: Windows-10-10.0.17134-SP0
sys_platform': win32
sys_version: 3.7.3 [MSC v.1915 64 bit (AMD64)]


Discussion:
I believe this is on purpose, `%timeit` may run the code several time; and does some modifications to the actual statement you run; so returning the value of the "Last" statement might not be what you expect. 
> I believe this is on purpose, `%timeit` may run the code several time; and does some modifications to the actual statement you run; so returning the value of the "Last" statement might not be what you expect.

`%time` and `%%time` only run the code once though.

`In [1]: x = 5`

1. Using `%timeit`, `y` is not assigned a value

```
In [2]: %timeit y = x*2
# 32.6 ns ± 0.341 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
```
```
In [3]: y
# NameError: name 'y' is not defined
```

2. Using `%time` however, a value is assigned to `y`.

```
In [4]: %time y = x*2
# Wall time: 0 ns
```
```
In [5]: y
Out[5]: 10
```

3. Similarly, `%time` outputs a value in `Out[ ]` when the statement is not assigned to `y`.

```
In [6]: %time x*2
# Wall time: 0 ns
Out[6]: 10
```

4. However, `%%time`'s behaviour is inconsistent with `%time`.

- It does not output a value as in 3.:
```
In [7]: %%time
        z = x*2
        z
# Wall time: 0 ns
```
- But it does assign a value to `z` as in 2.:
```
In [8]: z
Out[8]: 10
```

Is there a reason why the behaviour of `%%time` is not consistent with `%time`?
@Carreau  , @angertdevsingh  I am planning on working on this. This will be my very first-issue I will be contributing to.

To understand better , %%time should display the output for the last line in the cell, instead of suppressing it, like it currently does?

Resolving patch:
diff --git a/IPython/core/displayhook.py b/IPython/core/displayhook.py
--- a/IPython/core/displayhook.py
+++ b/IPython/core/displayhook.py
@@ -298,7 +298,7 @@ def flush(self):
         self._, self.__, self.___ = '', '', ''
 
         if '_' not in builtin_mod.__dict__:
-            self.shell.user_ns.update({'_':None,'__':None, '___':None})
+            self.shell.user_ns.update({'_':self._,'__':self.__,'___':self.___})
         import gc
         # TODO: Is this really needed?
         # IronPython blocks here forever
diff --git a/IPython/core/magics/execution.py b/IPython/core/magics/execution.py
--- a/IPython/core/magics/execution.py
+++ b/IPython/core/magics/execution.py
@@ -49,6 +49,14 @@
 from logging import error
 from io import StringIO
 
+if sys.version_info > (3,8):
+    from ast import Module
+else :
+    # mock the new API, ignore second argument
+    # see https://github.com/ipython/ipython/issues/11590
+    from ast import Module as OriginalModule
+    Module = lambda nodelist, type_ignores: OriginalModule(nodelist)
+
 
 #-----------------------------------------------------------------------------
 # Magic implementation classes
@@ -1261,6 +1269,7 @@ def time(self,line='', cell=None, local_ns=None):
         # Minimum time above which compilation time will be reported
         tc_min = 0.1
 
+        expr_val=None
         if len(expr_ast.body)==1 and isinstance(expr_ast.body[0], ast.Expr):
             mode = 'eval'
             source = '<timed eval>'
@@ -1268,6 +1277,15 @@ def time(self,line='', cell=None, local_ns=None):
         else:
             mode = 'exec'
             source = '<timed exec>'
+            # multi-line %%time case
+            if len(expr_ast.body) > 1 :
+                expr_val=expr_ast.body[-1]
+                code_val = self.shell.compile(ast.Expression(expr_val.value)
+                                                , '<timed eval>'
+                                                , 'eval')
+                expr_ast=expr_ast.body[:-1]
+                expr_ast = Module(expr_ast, [])
+
         t0 = clock()
         code = self.shell.compile(expr_ast, source, mode)
         tc = clock()-t0
@@ -1289,11 +1307,15 @@ def time(self,line='', cell=None, local_ns=None):
             st = clock2()
             try:
                 exec(code, glob, local_ns)
+                out=None
+                # multi-line %%time case
+                if expr_val and isinstance(expr_val, ast.Expr):
+                    out = eval(code_val, glob, local_ns)  
             except:
                 self.shell.showtraceback()
                 return
             end = clock2()
-            out = None
+
         wall_end = wtime()
         # Compute actual times and report
         wall_time = wall_end-wall_st


Test patch:
diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py
--- a/IPython/core/tests/test_magic.py
+++ b/IPython/core/tests/test_magic.py
@@ -9,6 +9,7 @@
 import re
 import sys
 import warnings
+from textwrap import dedent
 from unittest import TestCase
 from importlib import invalidate_caches
 from io import StringIO
@@ -419,6 +420,19 @@ def test_time3():
                     "run = 0\n"
                     "run += 1")
 
+def test_multiline_time():
+    """Make sure last statement from time return a value."""
+    ip = get_ipython()
+    ip.user_ns.pop('run', None)
+
+    ip.run_cell(dedent("""\
+        %%time
+        a = "ho"
+        b = "hey"
+        a+b
+        """))
+    nt.assert_equal(ip.user_ns_hidden['_'], 'hohey')
+
 def test_time_local_ns():
     """
     Test that local_ns is actually global_ns when running a cell magic