internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: rwth-i6/returnn

Issue:
`ConvLayer` `with_bias=False` default is inconsistent to `LinearLayer` `with_bias=True` default
I don't really know if we should do anything now, or change this.
I just want to raise this issue here.
Maybe we can also just close it if this is fine or we cannot really do anything about it now.

I also want to know whether one of the settings is very unexpected so users might have used it wrongly. That would be an argument to make it explicit or change the default (of course via new behavior version (#508)).

Also, as usual, we should check the defaults of other frameworks.

* [PyTorch `Linear`](https://pytorch.org/docs/stable/generated/torch.nn.Linear.html): `bias=True`
* [PyTorch `Conv1d`](https://pytorch.org/docs/stable/generated/torch.nn.Conv1d.html): `bias=True`
* [Keras `Dense`](https://www.tensorflow.org/api_docs/python/tf/keras/layers/Dense): `use_bias=True`
* [Keras `Conv1D`](https://www.tensorflow.org/api_docs/python/tf/keras/layers/Conv1D): `use_bias=True`

So in all cases, it seems that `with_bias=True` would be more expected.



Discussion:
There was not so much feedback yet (also not on Slack).

I think `with_bias=True` makes more sense to have it consistent to everything else.

The question is how to introduce this. Just via a new behavior version?

I agree `with_bias=True` is more consistent. A new behavior version makes sense to me.

Resolving patch:
diff --git a/returnn/tf/layers/basic.py b/returnn/tf/layers/basic.py
--- a/returnn/tf/layers/basic.py
+++ b/returnn/tf/layers/basic.py
@@ -4319,6 +4319,7 @@ def __init__(self, filter_size, padding, strides=1, dilation_rate=1, groups=1,
     :param dict[str,str]|None filter_perm: transposes the filter (input filter as layer)
     :param LayerBase|None bias: if given, will not create an own parameter, but use this as the bias
     """
+    from returnn.util import BehaviorVersion
     padding = padding.upper()
     assert padding in ["SAME", "VALID"], "no other padding supported at the moment"
     assert "out_type" not in kwargs, "don't set out_type explicitly for this layer"
@@ -4425,7 +4426,10 @@ def __init__(self, filter_size, padding, strides=1, dilation_rate=1, groups=1,
       y = tf.reshape(y, tf.concat([extended_batch_shape, tf.shape(y)[1:]], axis=0))
     # y shape is [batch] + dynamic_dims + [n_out].
     if with_bias is NotSpecified:
-      with_bias = True if bias else False
+      if bias or BehaviorVersion.get() >= 10:
+        with_bias = True
+      else:
+        with_bias = False
     if bias:
       assert with_bias
     self.bias_layer = None
diff --git a/returnn/util/basic.py b/returnn/util/basic.py
--- a/returnn/util/basic.py
+++ b/returnn/util/basic.py
@@ -209,7 +209,7 @@ class BehaviorVersion:
   The version will be set after the config is defined at __main__.init_config() or Engine.__init__()
   """
 
-  _latest_behavior_version = 9
+  _latest_behavior_version = 10
   _behavior_version = None  # type: typing.Optional[int]
 
   @classmethod


Test patch:
Repository: rwth-i6/returnn

Issue:
NormLayer, axis arg in addition to axes
More consistent to others.

Related: #597


Discussion:

Resolving patch:
diff --git a/returnn/tf/layers/basic.py b/returnn/tf/layers/basic.py
--- a/returnn/tf/layers/basic.py
+++ b/returnn/tf/layers/basic.py
@@ -817,9 +817,11 @@ class NormLayer(_ConcatInputLayer):
   """
   layer_class = "norm"
 
-  def __init__(self, axes, param_shape=NotSpecified, scale=True, bias=True, epsilon=1e-6, **kwargs):
+  def __init__(self, axis=NotSpecified, axes=NotSpecified,
+               param_shape=NotSpecified, scale=True, bias=True, epsilon=1e-6, **kwargs):
     """
-    :param Dim|str|list[Dim|str] axes: axes over which the mean and variance are computed, e.g. "F" or "TF"
+    :param Dim|str|list[Dim|str] axis: axis or axes over which the mean and variance are computed, e.g. "F" or "TF"
+    :param Dim|str|list[Dim|str] axes: axis or axes over which the mean and variance are computed, e.g. "F" or "TF"
     :param Dim|str|list[Dim|str]|tuple[Dim|str] param_shape: shape of the scale and bias parameters.
       You can also refer to (static) axes of the input, such as the feature-dim.
       This is also the default, i.e. a param-shape of [F], independent of the axes to normalize over.
@@ -827,6 +829,9 @@ def __init__(self, axes, param_shape=NotSpecified, scale=True, bias=True, epsilo
     :param bool bias: add trainable bias parameters
     :param float epsilon: epsilon for numerical stability
     """
+    if axis is not NotSpecified:
+      assert axes is NotSpecified
+      axes = axis
     super(NormLayer, self).__init__(**kwargs)
     assert not self.input_data.sparse
     x = self.input_data.placeholder


Test patch:
Repository: rwth-i6/returnn

Issue:
LayerNormLayer should support in_dim and out_dim
Related: #597


Discussion:

Resolving patch:
diff --git a/returnn/tf/layers/basic.py b/returnn/tf/layers/basic.py
--- a/returnn/tf/layers/basic.py
+++ b/returnn/tf/layers/basic.py
@@ -754,15 +754,26 @@ class LayerNormLayer(_ConcatInputLayer):
   """
   layer_class = "layer_norm"
 
-  def __init__(self, epsilon=1e-6, **kwargs):
+  def __init__(self, in_dim=None, out_dim=None, epsilon=1e-6, **kwargs):
     """
+    :param Dim|None in_dim:
+    :param Dim|None out_dim:
     :param float epsilon:
     """
     super(LayerNormLayer, self).__init__(**kwargs)
     assert not self.input_data.sparse
     x = self.input_data.placeholder
-    dim = self.input_data.dim
-    axis = self.input_data.feature_dim_axis
+    if not in_dim and out_dim:
+      in_dim = out_dim
+    if in_dim:
+      if out_dim:
+        assert in_dim == out_dim
+      assert isinstance(in_dim, Dim)
+      axis = self.input_data.get_axis_from_description(in_dim)
+    else:
+      axis = self.input_data.feature_dim_axis
+    dim = self.input_data.batch_shape[axis]
+    assert dim is not None, "%s: in_dim %i must be static in input %s" % (self, in_dim or axis, self.input_data)
     with self.var_creation_scope():
       scale = self.add_param(tf_compat.v1.get_variable("scale", [dim], initializer=tf.ones_initializer()))
       bias = self.add_param(tf_compat.v1.get_variable("bias", [dim], initializer=tf.zeros_initializer()))


Test patch:
Repository: rwth-i6/returnn

Issue:
Issues in SplitDimsLayer
dbaa97a from #845 introduces an issue when unflattening the batch dim axis. All resulting axes now get a batch dim dim_tag, which was previously not the case.

The issue also occurs in pytorch-to-returnn, as described here: https://github.com/rwth-i6/pytorch-to-returnn/pull/78



Discussion:

Resolving patch:
diff --git a/returnn/tf/layers/basic.py b/returnn/tf/layers/basic.py
--- a/returnn/tf/layers/basic.py
+++ b/returnn/tf/layers/basic.py
@@ -3487,11 +3487,13 @@ def get_out_data_from_opts(cls, name, axis, dims, pad_to_multiples=None, sources
     if not resolved_dims:
       resolved_dims = tuple(
         Dim(
-          kind=axis_dim_tag.kind,
+          kind=axis_dim_tag.kind if not axis_dim_tag.is_batch_dim() else Dim.Types.Spatial,
           description="%s_split_dims%i" % (name, i),
           dimension=shape_dim)
         if rem_dim is None or i != rem_dim_idx else rem_dim
         for i, shape_dim in enumerate(resolved_shape_dims))
+    if axis_dim_tag.is_batch_dim():
+      assert len([d for d in resolved_dims if d.is_batch_dim()]) == 1
 
     new_dim_tags = data.dim_tags[:axis] + resolved_dims + data.dim_tags[axis + 1:]
     out = data.copy_template_new_dim_tags(new_dim_tags)


Test patch:
diff --git a/tests/test_TFNetworkLayer.py b/tests/test_TFNetworkLayer.py
--- a/tests/test_TFNetworkLayer.py
+++ b/tests/test_TFNetworkLayer.py
@@ -2066,6 +2066,21 @@ def test_SplitDimsLayer_dim_tags():
     })
 
 
+def test_SplitDimsLayer_dim_tags_split_batch():
+  # https://github.com/rwth-i6/returnn/issues/908
+  # https://github.com/rwth-i6/pytorch-to-returnn/pull/78
+  from returnn.tf.util.data import batch_dim
+  time_dim = SpatialDim("in-time")
+  feat_dim = FeatureDim("feat", 3)
+  config = Config({
+    "extern_data": {"data": {"dim_tags": [batch_dim, time_dim, feat_dim]}}})
+  net = TFNetwork(config=config)
+  net.construct_from_dict({
+    "output": {
+      'class': 'split_dims', 'from': 'data', 'axis': batch_dim, 'dims': [1, -1]}
+  })
+
+
 def test_out_shape():
   # https://github.com/rwth-i6/returnn/issues/706
   # Note: Using SplitDimsLayer would also be nice to test out_shape. Or any layer which creates a new dim.
Repository: rwth-i6/returnn

Issue:
construct_layer with subnetworks
After some recent updates, it is not possible anymore to construct layers (via `net.construct_layer`) containing subnetworks. A simple test case to reproduce is
```
def test_subnet_construct_layer():
  net = TFNetwork(config=Config({"extern_data": {"data": {"dim": 1}}}))
  layer = net.construct_layer(name="subnet", net_dict={
    "subnet": {"class": "subnetwork", "from": "data", "subnetwork": {"output": {"class": "copy", "from": "data"}}}})
```

This showed up in https://github.com/rwth-i6/pytorch-to-returnn/pull/117. It seems to be caused by this commit https://github.com/rwth-i6/returnn/commit/15a42ca30f05363ecf19f472eb35eeb18bb3e55e which was introducing `GetLayer` to fix https://github.com/rwth-i6/returnn/issues/993. I'm not sure if this is a bug or if after these changes it is deprecated to call `construct_layers` like this in which case the converter would need to be fixed.


Discussion:

Resolving patch:
diff --git a/returnn/tf/network.py b/returnn/tf/network.py
--- a/returnn/tf/network.py
+++ b/returnn/tf/network.py
@@ -838,7 +838,7 @@ def construct_layer(self, net_dict, name, get_layer=None, add_layer=None, check_
       except (LayerNotFound, DataNotFound):
         pass  # ok, we will try to construct it then
     if not get_layer:
-      get_layer = GetLayer(network=self, add_layer_func=add_layer)
+      get_layer = GetLayer(network=self, add_layer_func=add_layer, net_dict=net_dict)
     full_name = name
     sub_layer_name = None
     if '/' in name:


Test patch:
diff --git a/tests/test_TFNetworkLayer.py b/tests/test_TFNetworkLayer.py
--- a/tests/test_TFNetworkLayer.py
+++ b/tests/test_TFNetworkLayer.py
@@ -6150,6 +6150,16 @@ def _create_subnet_layer_dict(depth):
     network.construct_from_dict(net_dict)
 
 
+def test_subnet_construct_layer():
+  # https://github.com/rwth-i6/returnn/issues/1014
+  with make_scope() as session:
+    net = TFNetwork(config=Config({"extern_data": {"data": {"dim": 1}}}))
+    subnet_layer = net.construct_layer(name="subnet", net_dict={
+      "subnet": {"class": "subnetwork", "from": "data", "subnetwork": {"output": {"class": "copy", "from": "data"}}}})
+    assert isinstance(subnet_layer, SubnetworkLayer)
+    assert subnet_layer.output.placeholder is net.extern_data.get_default_input_data().placeholder
+
+
 def test_extra_search():
   class Callbacks:
     history = []
Repository: rwth-i6/returnn

Issue:
NativeOp `destroy_map`
https://github.com/rwth-i6/returnn/blob/master/returnn/native_op.py#L72

`NativeOpBaseMixin` has the member `destroy_map` which is now (or was already) unused, so probably it can be removed with the function initializing it.


Discussion:

Resolving patch:
diff --git a/returnn/native_op.py b/returnn/native_op.py
--- a/returnn/native_op.py
+++ b/returnn/native_op.py
@@ -17,7 +17,7 @@
 import copy
 import numpy
 import typing
-from returnn.util.basic import make_hashable, unicode, long
+from returnn.util.basic import make_hashable, unicode
 
 
 class NativeOpBaseMixin(object):
@@ -69,7 +69,6 @@ def __init__(self, in_info, out_info,
     self.cpu_support = cpu_support
     self.name = name or "<anonNativeOp>"
     self.grad_input_map = self._convert_grad_input_map(grad_input_map, len(in_info) + len(out_info) * 2)
-    self.destroy_map = self._construct_destroy_map(in_info)
 
   @classmethod
   def _resolve_want_inplace_dummy(cls, in_info, out_info):
@@ -99,22 +98,6 @@ def _reduce_c_extra_support_code(cls, c):
     assert isinstance(c, (str, unicode))
     return c
 
-  @classmethod
-  def _construct_destroy_map(cls, in_info):
-    destroy_map = {}
-    for in_idx, info in enumerate(in_info):
-      want_inplace = info.get("want_inplace", -1)
-      assert isinstance(want_inplace, (int, long))
-      if want_inplace >= 0 and info.get("is_inplace", False):
-        out_idx = want_inplace
-        # https://deeplearning.net/software/theano/extending/inplace.html
-        # https://github.com/Theano/Theano/issues/3506
-        # It's strange that we must mark which output operates on which input -
-        # I would expect that it must only know which inputs are destroyed.
-        assert out_idx not in destroy_map, "Theano cannot handle that yet"
-        destroy_map[out_idx] = [in_idx]
-    return destroy_map
-
   @classmethod
   def _convert_grad_input_map(cls, gi_map, num_params):
     """


Test patch:
Repository: rwth-i6/returnn

Issue:
batch size in tf compute priors
In the tf engine in `compute_priors()`, we currently have `batch_size = config.int("batch_size", 1)`. In my config, `batch_size` is a `NumbersDict` which crashes. Right now, I mitigate this in my setup by replacing the batch size in the config before computing the priors, but we could easily allow `NumbersDict` here as well, right?


Discussion:
Yes sure. It's just that way because this code is very old.

Resolving patch:
diff --git a/returnn/tf/engine.py b/returnn/tf/engine.py
--- a/returnn/tf/engine.py
+++ b/returnn/tf/engine.py
@@ -2962,7 +2962,7 @@ def __call__(self, outputs):
                 self.seq_len += seq_len
 
         accumulator = Accumulator()
-        batch_size = config.int("batch_size", 1)
+        batch_size = config.typed_value("batch_size", default=1)
         max_seqs = config.int("max_seqs", -1)
         epoch = config.int("epoch", 1)
         max_seq_length = config.float("max_seq_length", 0)


Test patch:
Repository: rwth-i6/returnn

Issue:
TF 2.14 compatibility issues for tf util `cond()`
In tf util [`cond()`](https://github.com/rwth-i6/returnn/blob/master/returnn/tf/util/basic.py#L3698), we have

```
from tensorflow.python.ops import control_flow_ops

return control_flow_ops.cond(pred, true_fn, false_fn, name=name)
```

With TF 2.14, this seems to face compatibility issues.

```
AttributeError: module 'tensorflow.python.ops.control_flow_ops' has no attribute 'cond'
```


Discussion:
See https://github.com/tensorflow/models/issues/11099#issuecomment-1775351071 which points to this commit https://github.com/tensorflow/tensorflow/commit/8d0ac689ff65c0f0bf0dbb1add84e1c7d4baabe0.
Based on the proposed solution there, we could do
```
    from tensorflow.python.ops import cond as control_flow_ops_cond                                                          
                                                                                                                         
    return control_flow_ops_cond.cond(pred, true_fn, false_fn, name=name)
```
instead of the current import. This seems to work here as well and I also tested it for TF 2.8, but I'm not sure if it breaks things for other versions.
Why don't we just use `tf.cond`?
With TF 2.8, `tf.cond` is the function `cond_for_tf_v2` which is different from what is currently used. Not sure if this is a problem though.
```
>>> import tensorflow as tf
>>> tf.__version__
'2.8.0'
>>> from tensorflow.python.ops import control_flow_ops
>>> control_flow_ops.cond
<function cond at 0x7f5f8bfc9ca0>
>>> tf.cond
<function cond_for_tf_v2 at 0x7f5f8bfc9dc0>
```
But what is the difference? I think `tf.cond` makes in any case more sense here. I don't really know why we did not use that?
Note, we have the `TF_ENABLE_COND_V2` env var to control whether it should use V1 or V2 control flow (including `cond`). Maybe `control_flow_ops.cond` is always V1? If that is not the difference, then I would assume, if you follow the code, they probably end up in the same code.


Resolving patch:
diff --git a/returnn/tf/util/basic.py b/returnn/tf/util/basic.py
--- a/returnn/tf/util/basic.py
+++ b/returnn/tf/util/basic.py
@@ -3697,7 +3697,7 @@ def debug_register_better_repr():
 
 def cond(pred, true_fn, false_fn, name=None):
     """
-    This is a wrapper around tf.control_flow_ops.cond().
+    This is a wrapper around tf.cond().
     This will be a branched execution, i.e. either fn1() or fn2() will be executed,
     or at least the resulting graph will be evaluated.
     If pred can is constant at the call, only the corresponding fn will be called.
@@ -3727,9 +3727,8 @@ def cond(pred, true_fn, false_fn, name=None):
             return true_fn()
         else:
             return false_fn()
-    from tensorflow.python.ops import control_flow_ops
 
-    return control_flow_ops.cond(pred, true_fn, false_fn, name=name)
+    return tf.cond(pred, true_fn, false_fn, name=name)
 
 
 def single_strided_slice(x, axis, begin=None, end=None, step=None):


Test patch:
Repository: rwth-i6/returnn

Issue:
Compile native op: native signal handler
When running `tools/compile_native_op.py` for example for `NativeLstm2` op, if the output file is specified it now looks like this:
```
/var/tmp/agerstenberger/returnn_native/native_signal_handler/3eb0034669/native_signal_handler.so
/var/tmp/agerstenberger/returnn_tf_cache/ops/NativeLstm2/8c9954fa8e/NativeLstm2.so
/var/tmp/agerstenberger/returnn_tf_cache/ops/GradOfNativeLstm2/d1a9d7605d/GradOfNativeLstm2.so
```

You would not expect to find native_signal_handler.so here. 
Also the `i6_core` job `CompileNativeOpJob` does not check names of the op but just copies the first entry and the second entry as gradient .so., which is now wrong.

So now i'm asking, should we fix it here or do a more robust check in `i6_core`?

A fix here is very simply just moving the line
```python
NativeCodeCompiler.CollectedCompilers = []
```
after the init function is called.


Discussion:
The script is about collecting compiled native ops, and native signal handler is not a native op, so this is the bug that it was collected here. It should only collect outputs from the TF native op compiler, I guess, not from the base class, the native code compiler.




Resolving patch:
diff --git a/tools/compile_native_op.py b/tools/compile_native_op.py
--- a/tools/compile_native_op.py
+++ b/tools/compile_native_op.py
@@ -57,10 +57,10 @@ def main(argv):
     """
     Main entry.
     """
-    from returnn.tf.util.basic import CudaEnv, NativeCodeCompiler
+    from returnn.tf.util.basic import CudaEnv, OpCodeCompiler
 
     CudaEnv.verbose_find_cuda = True
-    NativeCodeCompiler.CollectedCompilers = []
+    OpCodeCompiler.CollectedCompilers = []
 
     argparser = argparse.ArgumentParser(description="Compile some op")
     argparser.add_argument("--config", help="filename to config-file")
@@ -106,8 +106,8 @@ def main(argv):
     elif OpMaker.with_cuda is False:
         print("No CUDA.")
 
-    for compiler in NativeCodeCompiler.CollectedCompilers:
-        assert isinstance(compiler, NativeCodeCompiler)
+    for compiler in OpCodeCompiler.CollectedCompilers:
+        assert isinstance(compiler, OpCodeCompiler)
         print(compiler)
         # noinspection PyProtectedMember
         libs.append(compiler._so_filename)


Test patch:
Repository: python/mypy

Issue:
Disallow __init__ in interfaces
An interface that defines `__init__` should be rejected. Only classes can have constructors.

Disallow __init__ in interfaces
An interface that defines `__init__` should be rejected. Only classes can have constructors.



Discussion:

Resolving patch:
diff --git a/checker.py b/checker.py
--- a/checker.py
+++ b/checker.py
@@ -338,13 +338,22 @@ def infer_local_variable_type(self, x, y, z):
     
     Type visit_type_def(self, TypeDef defn):
         """Type check a type definition (class or interface)."""
-        typ = self.lookup(defn.name, GDEF).node
+        typ = (TypeInfo)self.lookup(defn.name, GDEF).node
         self.errors.set_type(defn.name, defn.is_interface)
-        self.check_unique_interface_implementations((TypeInfo)typ)
-        self.check_interface_errors((TypeInfo)typ)
+        self.check_unique_interface_implementations(typ)
+        self.check_interface_errors(typ)
+        self.check_no_constructor_if_interface(typ)
         self.accept(defn.defs)
         self.report_error_if_statements_in_class_body(defn.defs)
         self.errors.set_type(None, False)
+
+    void check_no_constructor_if_interface(self, TypeInfo typ):
+        if not typ.is_interface:
+            return
+        ctor = typ.get_method('__init__')
+        if ctor is None:
+            return
+        self.msg.interface_has_constructor(ctor)
     
     void check_unique_interface_implementations(self, TypeInfo typ):
         """Check that each interface is implemented only once."""
diff --git a/messages.py b/messages.py
--- a/messages.py
+++ b/messages.py
@@ -480,6 +480,9 @@ class MessageBuilder:
         else:
             return False
 
+    void interface_has_constructor(self, Context context):
+        self.fail('An interface must not define a constructor', context)
+
 
 str capitalize(str s):
     """Capitalize the first character of a string."""


Test patch:
Repository: python/mypy

Issue:
Runtime error "Broken installation: can't determine base dir" occurs when installed with brew
Brew install python binaries under

/usr/local/share/python[3]/

and lib directory is under

/usr/local/lib

The function default_data_dir() throws an error because base_dir is is 'python3'. A simple check would fix this.



Discussion:
Thanks for reporting the issue! There needs to be an extra check to handle this case.


Resolving patch:
diff --git a/mypy/build.py b/mypy/build.py
--- a/mypy/build.py
+++ b/mypy/build.py
@@ -188,6 +188,9 @@ def default_data_dir(bin_dir: str) -> str:
     elif base == 'bin':
         # Installed to somewhere (can be under /usr/local or anywhere).
         return os.path.join(dir, 'lib', 'mypy')
+    elif base == 'python3':
+        # Assume we installed python3 with brew on os x
+        return os.path.join(os.path.dirname(dir), 'lib', 'mypy')
     else:
         # Don't know where to find the data files!
         raise RuntimeError("Broken installation: can't determine base dir")


Test patch:
Repository: python/mypy

Issue:
Lambda with void return value rejected
Code like this is rejected, because the lambda has None as the inferred return type:

```
import typing
x = lambda: print(1) # type: typing.Function[[], None]
```

The type checker should accept lambdas that don't return a value. Any type for the lambda body should be accepted in this case.



Discussion:

Resolving patch:
diff --git a/mypy/checker.py b/mypy/checker.py
--- a/mypy/checker.py
+++ b/mypy/checker.py
@@ -923,7 +923,9 @@ def visit_return_stmt(self, s: ReturnStmt) -> Type:
                 # Returning a value of type Any is always fine.
                 if not isinstance(typ, AnyType):
                     if isinstance(self.return_types[-1], Void):
-                        self.fail(messages.NO_RETURN_VALUE_EXPECTED, s)
+                        # FuncExpr (lambda) may have a Void return.
+                        if not isinstance(self.function_stack[-1], FuncExpr):
+                            self.fail(messages.NO_RETURN_VALUE_EXPECTED, s)
                     else:
                         self.check_subtype(
                             typ, self.return_types[-1], s,


Test patch:
diff --git a/mypy/test/data/check-expressions.test b/mypy/test/data/check-expressions.test
--- a/mypy/test/data/check-expressions.test
+++ b/mypy/test/data/check-expressions.test
@@ -815,6 +815,11 @@ main, line 3: "str" has no attribute "x"
 main, line 4: Incompatible return value type
 main, line 4: Incompatible types in assignment
 
+[case testVoidLambda]
+import typing
+def void() -> None:
+    pass
+x = lambda: void() # type: typing.Function[[], None]
 
 -- List comprehensions
 -- -------------------