diff --git a/doc/tutorial.rst b/doc/tutorial.rst
index 69f89548618e86b408a31af240bee84678c859c1..7196dad863474d9b6ea9df9d9d0ae90b3e14986d 100644
--- a/doc/tutorial.rst
+++ b/doc/tutorial.rst
@@ -111,9 +111,9 @@ always see loopy's view of a kernel by printing it.
     KERNEL: loopy_kernel
     ---------------------------------------------------------------------------
     ARGUMENTS:
-    a: GlobalArg, type: <runtime>, shape: (n), dim_tags: (N0:stride:1)
-    n: ValueArg, type: <runtime>
-    out: GlobalArg, type: <runtime>, shape: (n), dim_tags: (N0:stride:1)
+    a: GlobalArg, type: <auto/runtime>, shape: (n), dim_tags: (N0:stride:1)
+    n: ValueArg, type: <auto/runtime>
+    out: GlobalArg, type: <auto/runtime>, shape: (n), dim_tags: (N0:stride:1)
     ---------------------------------------------------------------------------
     DOMAINS:
     [n] -> { [i] : 0 <= i < n }
@@ -154,7 +154,7 @@ following:
   See :ref:`specifying-arguments`.
 
 * Loopy has not determined the type of ``a`` and ``out``. The data type is
-  given as ``<runtime>``, which means that these types will be determined
+  given as ``<auto/runtime>``, which means that these types will be determined
   by the data passed in when the kernel is invoked. Loopy generates (and
   caches!) a copy of the kernel for each combination of types passed in.
 
diff --git a/loopy/kernel/array.py b/loopy/kernel/array.py
index 5d4240b9ab3e1ce2ad356a93b5e21b3bbf4d499e..34f58e2864b51db9ffa1f2c0657d8fc4e406931f 100644
--- a/loopy/kernel/array.py
+++ b/loopy/kernel/array.py
@@ -549,15 +549,55 @@ class ArrayBase(ImmutableRecord):
     .. attribute :: name
 
     .. attribute :: dtype
+        the :class:`loopy.loopytype` of the array.
+        if this is *none*, :mod:`loopy` will try to continue without
+        knowing the type of this array, where the idea is that precise
+        knowledge of the type will become available at invocation time.
+        :class:`loopy.compiledkernel` (and thereby
+        :meth:`loopy.loopkernel.__call__`) automatically add this type
+        information based on invocation arguments.
+
+        note that some transformations, such as :func:`loopy.add_padding`
+        cannot be performed without knowledge of the exact *dtype*.
 
     .. attribute :: shape
 
+        May be one of the following:
+
+        * *None*. In this case, no shape is intended to be specified,
+          only the strides will be used to access the array. Bounds checking
+          will not be performed.
+
+        * :class:`loopy.auto`. The shape will be determined by finding the
+          access footprint.
+
+        * a tuple like like :attr:`numpy.ndarray.shape`.
+
+          Each entry of the tuple is also allowed to be a :mod:`pymbolic`
+          expression involving kernel parameters, or a (potentially-comma
+          separated) or a string that can be parsed to such an expression.
+
+          Any element of the shape tuple not used to compute strides
+          may be *None*.
+
     .. attribute:: dim_tags
 
         See :ref:`data-dim-tags`.
 
     .. attribute:: offset
 
+        Offset from the beginning of the buffer to the point from
+            which the strides are counted. May be one of
+
+            * 0 or None
+            * a string (that is interpreted as an argument name).
+            * a pymbolic expression
+            * :class:`loopy.auto`, in which case an offset argument
+              is added automatically, immediately following this argument.
+              :class:`loopy.CompiledKernel` is even smarter in its treatment of
+              this case and will compile custom versions of the kernel based on
+              whether the passed arrays have offsets or not.
+
     .. attribute:: dim_names
 
         A tuple of strings providing names for the array axes, or *None*.
@@ -590,40 +630,12 @@ class ArrayBase(ImmutableRecord):
         All of the following (except *name*) are optional.
         Specify either strides or shape.
 
-        :arg name: May contain multiple names separated by
-            commas, in which case multiple arguments,
-            each with identical properties, are created
-            for each name.
-        :arg dtype: the :class:`numpy.dtype` of the array.
-            If this is *None*, :mod:`loopy` will try to continue without
-            knowing the type of this array, where the idea is that precise
-            knowledge of the type will become available at invocation time.
-            :class:`loopy.CompiledKernel` (and thereby
-            :meth:`loopy.LoopKernel.__call__`) automatically add this type
-            information based on invocation arguments.
-
-            Note that some transformations, such as :func:`loopy.add_padding`
-            cannot be performed without knowledge of the exact *dtype*.
-
-        :arg shape: May be one of the following:
+        :arg name: When passed to :class:`loopy.make_kernel`, this may contain
+            multiple names separated by commas, in which case multiple arguments,
+            each with identical properties, are created for each name.
 
-            * *None*. In this case, no shape is intended to be specified,
-              only the strides will be used to access the array. Bounds checking
-              will not be performed.
-
-            * :class:`loopy.auto`. The shape will be determined by finding the
-              access footprint.
-
-            * a tuple like like :attr:`numpy.ndarray.shape`.
-
-              Each entry of the tuple is also allowed to be a :mod:`pymbolic`
-              expression involving kernel parameters, or a (potentially-comma
-              separated) or a string that can be parsed to such an expression.
-
-              Any element of the shape tuple not used to compute strides
-              may be *None*.
-
-            * A string which can be parsed into the previous form.
+        :arg shape: May be any of the things specified under :attr:`shape`,
+            or a string which can be parsed into the previous form.
 
         :arg dim_tags: A comma-separated list of tags as understood by
             :func:`parse_array_dim_tag`.
@@ -649,17 +661,8 @@ class ArrayBase(ImmutableRecord):
         :arg for_atomic:
             Whether the array is declared for atomic access, and, if necessary,
             using atomic-capable data types.
-        :arg offset: Offset from the beginning of the buffer to the point from
-            which the strides are counted. May be one of
+        :arg offset: (See :attr:`offset`)
 
-            * 0 or None
-            * a string (that is interpreted as an argument name).
-            * a pymbolic expression
-            * :class:`loopy.auto`, in which case an offset argument
-              is added automatically, immediately following this argument.
-              :class:`loopy.CompiledKernel` is even smarter in its treatment of
-              this case and will compile custom versions of the kernel based on
-              whether the passed arrays have offsets or not.
         """
 
         for kwarg_name in kwargs:
@@ -672,6 +675,14 @@ class ArrayBase(ImmutableRecord):
         dtype = to_loopy_type(dtype, allow_auto=True, allow_none=True,
                 for_atomic=for_atomic, target=target)
 
+        if dtype is lp.auto:
+            from warnings import warn
+            warn("Argument/temporary data type should be None if unspecified, "
+                    "not auto. This usage will be disallowed in 2018.",
+                    DeprecationWarning, stacklevel=2)
+
+            dtype = None
+
         strides_known = strides is not None and strides is not lp.auto
         shape_known = shape is not None and shape is not lp.auto
 
@@ -832,10 +843,10 @@ class ArrayBase(ImmutableRecord):
         if include_typename:
             info_entries.append(type(self).__name__)
 
-        if self.dtype is lp.auto:
-            type_str = "<auto>"
-        elif self.dtype is None:
-            type_str = "<runtime>"
+        assert self.dtype is not lp.auto
+
+        if self.dtype is None:
+            type_str = "<auto/runtime>"
         else:
             type_str = str(self.dtype)
 
diff --git a/loopy/kernel/data.py b/loopy/kernel/data.py
index a4e6036cbac0235590d7cc66a201c47ac87d6030..63f5ce7113143f70b82713ccbb974e71b617a879 100644
--- a/loopy/kernel/data.py
+++ b/loopy/kernel/data.py
@@ -219,9 +219,20 @@ class KernelArgument(ImmutableRecord):
 
         dtype = kwargs.pop("dtype", None)
         from loopy.types import to_loopy_type
-        kwargs["dtype"] = to_loopy_type(
+        dtype = to_loopy_type(
                 dtype, allow_auto=True, allow_none=True, target=target)
 
+        import loopy as lp
+        if dtype is lp.auto:
+            from warnings import warn
+            warn("Argument/temporary data type should be None if unspecified, "
+                    "not auto. This usage will be disallowed in 2018.",
+                    DeprecationWarning, stacklevel=2)
+
+            dtype = None
+
+        kwargs["dtype"] = dtype
+
         ImmutableRecord.__init__(self, **kwargs)
 
 
@@ -268,10 +279,10 @@ class ValueArg(KernelArgument):
 
     def __str__(self):
         import loopy as lp
-        if self.dtype is lp.auto:
-            type_str = "<auto>"
-        elif self.dtype is None:
-            type_str = "<runtime>"
+        assert self.dtype is not lp.auto
+
+        if self.dtype is None:
+            type_str = "<auto/runtime>"
         else:
             type_str = str(self.dtype)
 
diff --git a/loopy/preprocess.py b/loopy/preprocess.py
index f2b5e7a87022e01bd51368cc3ef3cc60d507d958..ad119e94e74b294e16cdc15c5ab1f723cf7f254b 100644
--- a/loopy/preprocess.py
+++ b/loopy/preprocess.py
@@ -797,11 +797,10 @@ def _hackily_ensure_multi_assignment_return_values_are_scoped_private(kernel):
 
             newly_added_assignments_ids.add(new_assignment_id)
 
-            import loopy as lp
             new_temporaries[new_assignee_name] = (
                     TemporaryVariable(
                         name=new_assignee_name,
-                        dtype=lp.auto,
+                        dtype=None,
                         scope=temp_var_scope.PRIVATE))
 
             from pymbolic import var
@@ -987,7 +986,7 @@ def realize_reduction(kernel, insn_id_filter=None, unknown_types_ok=True,
             new_temporary_variables[name] = TemporaryVariable(
                     name=name,
                     shape=(),
-                    dtype=lp.auto,
+                    dtype=None,
                     scope=temp_var_scope.PRIVATE)
 
         from pymbolic import var
diff --git a/loopy/type_inference.py b/loopy/type_inference.py
index 0f58ae64e1789ebe9b9b5ca37a3f7dae534e0a2a..fcf8f965b68fd258b0c0f1eae94ec84a39a5b7ee 100644
--- a/loopy/type_inference.py
+++ b/loopy/type_inference.py
@@ -312,15 +312,8 @@ class TypeInferenceMapper(CombineMapper):
 
         from loopy.kernel.data import TemporaryVariable, KernelArgument
         import loopy as lp
-        if isinstance(obj, TemporaryVariable):
-            result = [obj.dtype]
-            if result[0] is lp.auto:
-                self.symbols_with_unknown_types.add(expr.name)
-                return []
-            else:
-                return result
-
-        elif isinstance(obj, KernelArgument):
+        if isinstance(obj, (KernelArgument, TemporaryVariable)):
+            assert obj.dtype is not lp.auto
             result = [obj.dtype]
             if result[0] is None:
                 self.symbols_with_unknown_types.add(expr.name)
@@ -515,10 +508,12 @@ def infer_unknown_types(kernel, expect_completion=False):
 
     import loopy as lp
     for tv in six.itervalues(kernel.temporary_variables):
-        if tv.dtype is lp.auto:
+        assert tv.dtype is not lp.auto
+        if tv.dtype is None:
             names_for_type_inference.append(tv.name)
 
     for arg in kernel.args:
+        assert arg.dtype is not lp.auto
         if arg.dtype is None:
             names_for_type_inference.append(arg.name)
 
diff --git a/loopy/types.py b/loopy/types.py
index f095d1d58f9eaebb7dcc9c8d41afa73951f2ba84..8f0f310c305b3d5b24bd6e771b501bb6d9c69224 100644
--- a/loopy/types.py
+++ b/loopy/types.py
@@ -177,13 +177,20 @@ class AtomicNumpyType(NumpyType, AtomicType):
 # }}}
 
 
-def to_loopy_type(dtype, allow_none=False, allow_auto=False, for_atomic=False,
+def to_loopy_type(dtype, allow_auto=False, allow_none=False, for_atomic=False,
         target=None):
     from loopy.kernel.data import auto
-    if allow_none and dtype is None:
-        return dtype
-    elif allow_auto and dtype is auto:
-        return dtype
+    if dtype is None:
+        if allow_none:
+            return None
+        else:
+            raise LoopyError("dtype may not be none")
+
+    elif dtype is auto:
+        if allow_auto:
+            return dtype
+        else:
+            raise LoopyError("dtype may not be auto")
 
     numpy_dtype = None