Repository: PennyLaneAI/pennylane
Pull request #7919 (2025-07-18 16:37:32)

Allow `set_shots(500)`
**Context:**
We want `shots` can be positional arg when `set_shots` used as decorator.

**Description of the Change:**
 - [x] refactored the `set_shots` code such that different functionalities can be well separated from each other
 - [x] added the pathway for `set_shots(shots)`
 - [x] used `ShotsLike` to do type hint; also exported it to `qml.measurements` for convenience

**Benefits:**
Better source code readability.
More intuitive, straightforward usage.

**Possible Drawbacks:**
Unfortunately did not find an elegant way to do dynamic multi-dispatch.

**Related GitHub Issues:**
[sc-95792]

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/7919?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
Attention: Patch coverage is `95.65217%` with `1 line` in your changes missing coverage. Please review.
> Project coverage is 99.68%. Comparing base [(`16f73da`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/16f73dab3ef2c6b309963a8187942612cb00eefb?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) to head [(`47be21f`)](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/47be21f0b2582fba0a1d777ea2869b5420a45db1?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).

| [Files with missing lines](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/7919?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Patch % | Lines |
|---|---|---|
| [pennylane/workflow/set\_shots.py](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/7919?src=pr&el=tree&filepath=pennylane%2Fworkflow%2Fset_shots.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3dvcmtmbG93L3NldF9zaG90cy5weQ==) | 95.65% | [1 Missing :warning: ](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/7919?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #7919      +/-   ##
==========================================
- Coverage   99.68%   99.68%   -0.01%     
==========================================
  Files         544      544              
  Lines       55471    55484      +13     
==========================================
+ Hits        55295    55307      +12     
- Misses        176      177       +1     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/7919?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 6366b57fd..19dcc1ffc 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,8 +1,14 @@
 repos:
+-   repo: https://github.com/gitleaks/gitleaks
+    rev: v8.28.0
+    hooks:
+      - id: gitleaks
+        name: "🔒 gitleaks · Detect hardcoded secrets"
 -   repo: https://github.com/psf/black
-    rev: 24.4.2
+    rev: 25.1.0
     hooks:
     - id: black
+      name: "🐍 black · Format code"
       args: [
               "--line-length=100",
               "-t", "py311",
@@ -11,9 +17,10 @@ repos:
             ]
       exclude: ^doc/
 -   repo: https://github.com/PyCQA/isort
-    rev: 5.13.2
+    rev: 6.0.1
     hooks:
     - id: isort
+      name: "🐍 isort · Sort imports"
       args:
         [
           "--py",
@@ -32,18 +39,20 @@ repos:
         ]
       files: ^(pennylane/|tests/)
 -   repo: https://github.com/gauge-sh/tach-pre-commit
-    rev: v0.28.5
+    rev: v0.29.0
     hooks:
     -   id: tach
+        name: "🐍 tach · Check module dependencies"
 -   repo: https://github.com/asottile/pyupgrade
-    rev: v3.16.0
+    rev: v3.20.0
     hooks:
     -   id: pyupgrade
+        name: "🐍 pyupgrade · Check language features"
         args: [--py311-plus]
 - repo: local
   hooks:
     - id: pylint
-      name: pylint
+      name: "🐍 pylint · Lint codebase"
       entry: pylint
       language: system
       types: [python]
@@ -55,7 +64,7 @@ repos:
         ]
       exclude: ^(doc/|tests/)
     - id: pylint-test
-      name: pylint-test
+      name: "🐍 pylint · Lint test suite"
       entry: pylint
       language: system
       types: [python]
@@ -67,7 +76,7 @@ repos:
         ]
       files: ^tests/
     - id: labs-pylint-test
-      name: labs-pylint-test
+      name: "🐍 pylint · Lint labs test suite"
       entry: pylint
       language: system
       types: [python]
@@ -78,4 +87,3 @@ repos:
           "--rcfile=tests/.pylintrc", # Link to your config file
         ]
       files: ^pennylane/labs/tests/
-
diff --git a/doc/development/deprecations.rst b/doc/development/deprecations.rst
index 1175e7e8a..3ca592201 100644
--- a/doc/development/deprecations.rst
+++ b/doc/development/deprecations.rst
@@ -39,16 +39,50 @@ Pending deprecations
   - Deprecated in v0.43
   - Will be removed in v0.44
 
-* The boolean functions provided by ``pennylane.operation`` are deprecated. See below for alternate code to
-  use instead.
+* Accessing ``lie_closure``, ``structure_constants`` and ``center`` via ``qml.pauli`` is deprecated. Top level import and usage is advised.
+
+ - Deprecated in v0.40
+ - Will be removed in v0.41
+
+Completed removal of legacy operator arithmetic
+-----------------------------------------------
+
+In PennyLane v0.40, the legacy operator arithmetic system has been removed, and is fully replaced by the new
+operator arithmetic functionality that was introduced in v0.36. Check out the :ref:`Updated operators <new_opmath>` page
+for details on how to port your legacy code to the new system. The following functionality has been removed:
+
+* In PennyLane v0.40, legacy operator arithmetic has been removed. This includes :func:`~pennylane.operation.enable_new_opmath`,
+  :func:`~pennylane.operation.disable_new_opmath`, :class:`~pennylane.ops.Hamiltonian`, and :class:`~pennylane.operation.Tensor`. Note
+  that ``qml.Hamiltonian`` will continue to dispatch to :class:`~pennylane.ops.LinearCombination`.
+
+  - Deprecated in v0.39
+  - Removed in v0.40
+
+* :meth:`~pennylane.pauli.PauliSentence.hamiltonian` and :meth:`~pennylane.pauli.PauliWord.hamiltonian` has been removed. Instead, please use
+  :meth:`~pennylane.pauli.PauliSentence.operation` and :meth:`~pennylane.pauli.PauliWord.operation` respectively.
+
+  - Deprecated in v0.39
+  - Removed in v0.40
+
+* :func:`pennylane.pauli.simplify` has been removed. Instead, please use :func:`pennylane.simplify` or :meth:`~pennylane.operation.Operator.simplify`.
+
+  - Deprecated in v0.39
+  - Removed in v0.40
+
+Completed deprecation cycles
+----------------------------
+
+* The boolean functions provided by ``pennylane.operation`` are deprecated. See below for an example of alternative code to use.
   These include ``not_tape``, ``has_gen``, ``has_grad_method``,  ``has_multipar``, ``has_nopar``, ``has_unitary_gen``,
   ``is_measurement``, ``defines_diagonalizing_gates``, and ``gen_is_multi_term_hamiltonian``.
 
   - Deprecated in v0.42
-  - Will be removed in v0.43
+  - Removed in v0.43
 
 .. code-block:: python
 
+    from pennylane.operation import TermsUndefinedError, Operator
+
     def not_tape(obj):
         return not isinstance(obj, qml.tape.QuantumScript)
 
@@ -78,39 +112,11 @@ Pending deprecations
             return False
         try:
             generator = obj.generator()
-            _, ops = generator.terms() 
+            _, ops = generator.terms()
             return len(ops) > 1
         except TermsUndefinedError:
             return False
 
-Completed removal of legacy operator arithmetic
------------------------------------------------
-
-In PennyLane v0.40, the legacy operator arithmetic system has been removed, and is fully replaced by the new
-operator arithmetic functionality that was introduced in v0.36. Check out the :ref:`Updated operators <new_opmath>` page
-for details on how to port your legacy code to the new system. The following functionality has been removed:
-
-* In PennyLane v0.40, legacy operator arithmetic has been removed. This includes :func:`~pennylane.operation.enable_new_opmath`,
-  :func:`~pennylane.operation.disable_new_opmath`, :class:`~pennylane.ops.Hamiltonian`, and :class:`~pennylane.operation.Tensor`. Note
-  that ``qml.Hamiltonian`` will continue to dispatch to :class:`~pennylane.ops.LinearCombination`.
-
-  - Deprecated in v0.39
-  - Removed in v0.40
-
-* :meth:`~pennylane.pauli.PauliSentence.hamiltonian` and :meth:`~pennylane.pauli.PauliWord.hamiltonian` has been removed. Instead, please use
-  :meth:`~pennylane.pauli.PauliSentence.operation` and :meth:`~pennylane.pauli.PauliWord.operation` respectively.
-
-  - Deprecated in v0.39
-  - Removed in v0.40
-
-* :func:`pennylane.pauli.simplify` has been removed. Instead, please use :func:`pennylane.simplify` or :meth:`~pennylane.operation.Operator.simplify`.
-
-  - Deprecated in v0.39
-  - Removed in v0.40
-
-Completed deprecation cycles
-----------------------------
-
 * Accessing ``lie_closure``, ``structure_constants`` and ``center`` via ``qml.pauli`` is deprecated. Top level import and usage is advised. They now live in
   the ``liealg`` module.
 
@@ -130,7 +136,6 @@ Completed deprecation cycles
   .. code-block:: python
 
       class MyObs(Operator):
-      
           is_hermitian = True
 
           def queue(self, context=qml.QueuingManager):
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 722a1dcc7..30b7406f0 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -72,9 +72,10 @@
 
 * The :func:`qml.workflow.set_shots` transform can now be directly applied to a QNode without the need for `functools.partial`, providing a more user-friendly syntax and negating having to import the `functools` package.
   [(#7876)](https://github.com/PennyLaneAI/pennylane/pull/7876)
+  [(#7919)](https://github.com/PennyLaneAI/pennylane/pull/7919)
 
   ```python
-  @qml.set_shots(shots=1000)
+  @qml.set_shots(shots=1000)  # or @qml.set_shots(1000)
   @qml.qnode(dev)
   def circuit():
       qml.H(0)
@@ -127,6 +128,12 @@
 
 <h3>Breaking changes 💔</h3>
 
+* The boolean functions provided in `qml.operation` are deprecated. See the 
+  :doc:`deprecations page </development/deprecations>` for equivalent code to use instead. These 
+  include `not_tape`, `has_gen`, `has_grad_method`, `has_multipar`, `has_nopar`, `has_unitary_gen`, 
+  `is_measurement`, `defines_diagonalizing_gates`, and `gen_is_multi_term_hamiltonian`.
+  [(#7924)](https://github.com/PennyLaneAI/pennylane/pull/7924)
+
 * Removed access for `lie_closure`, `structure_constants` and `center` via `qml.pauli`.
   Top level import and usage is advised. The functions now live in the `liealg` module.
 
@@ -141,6 +148,7 @@
   PennyLane now depends on the more general `Operator` interface instead. The
   `Operator.is_hermitian` property can instead be used to check whether or not it is highly likely
   that the operator instance is Hermitian.
+  [(#7927)](https://github.com/PennyLaneAI/pennylane/pull/7927)
 
 * `qml.operation.WiresEnum`, `qml.operation.AllWires`, and `qml.operation.AnyWires` have been removed. Setting `Operator.num_wires = None` (the default)
   should instead indicate that the `Operator` does not need wire validation.
@@ -183,6 +191,9 @@
 * Refactored the codebase to adopt modern type hint syntax for Python 3.11+ language features.
   [(#7860)](https://github.com/PennyLaneAI/pennylane/pull/7860)
 
+* Improve the pre-commit hook to add gitleaks.
+  [(#7922)](https://github.com/PennyLaneAI/pennylane/pull/7922)
+
 * Added a `run_filecheck_qjit` fixture that can be used to run FileCheck on integration tests for the
   `qml.compiler.python_compiler` submodule.
   [(#7888)](https://github.com/PennyLaneAI/pennylane/pull/7888)
@@ -215,8 +226,9 @@
 * The `TensorLike` type is now compatible with static type checkers.
   [(#7905)](https://github.com/PennyLaneAI/pennylane/pull/7905)
 
-* Update xDSL supported version to `0.45`.
+* Update xDSL supported version to `0.46`.
   [(#7923)](https://github.com/PennyLaneAI/pennylane/pull/7923)
+  [(#7932)](https://github.com/PennyLaneAI/pennylane/pull/7932)
 
 * Update JAX version used in tests to `0.6.2`
   [(#7925)](https://github.com/PennyLaneAI/pennylane/pull/7925)
diff --git a/pennylane/compiler/python_compiler/dialects/quantum.py b/pennylane/compiler/python_compiler/dialects/quantum.py
index 9f01aebf1..d912bb045 100644
--- a/pennylane/compiler/python_compiler/dialects/quantum.py
+++ b/pennylane/compiler/python_compiler/dialects/quantum.py
@@ -160,9 +160,9 @@ class AllocOp(IRDLOperation):
 
     name = "quantum.alloc"
 
-    # assembly_format = """
-    #        `(` ($nqubits^):($nqubits_attr)? `)` attr-dict `:` type(results)
-    #    """
+    assembly_format = """
+           `(` ($nqubits^):($nqubits_attr)? `)` attr-dict `:` type(results)
+    """
 
     nqubits = opt_operand_def(i64)
 
@@ -423,15 +423,15 @@ class ExtractOp(IRDLOperation):
 
     name = "quantum.extract"
 
-    # assembly_format = """
-    #        $qreg `[` ($idx^):($idx_attr)? `]` attr-dict `:` type($qreg) `->` type(results)
-    #    """
+    assembly_format = """
+           $qreg `[` ($idx^):($idx_attr)? `]` attr-dict `:` type($qreg) `->` type(results)
+    """
 
     qreg = operand_def(QuregType)
 
     idx = opt_operand_def(IntegerType(64))
 
-    idx_attr = opt_prop_def(AnyAttr())
+    idx_attr = opt_prop_def(IntegerAttr[I64])
 
     qubit = result_def(QubitType)
 
@@ -576,15 +576,15 @@ class InsertOp(IRDLOperation):
 
     name = "quantum.insert"
 
-    # assembly_format = """
-    #        $in_qreg `[` ($idx^):($idx_attr)? `]` `,` $qubit attr-dict `:` type($in_qreg) `,` type($qubit)
-    #    """
+    assembly_format = """
+           $in_qreg `[` ($idx^):($idx_attr)? `]` `,` $qubit attr-dict `:` type($in_qreg) `,` type($qubit)
+    """
 
     in_qreg = operand_def(QuregType)
 
     idx = opt_operand_def(IntegerType(64))
 
-    idx_attr = opt_prop_def(AnyAttr())
+    idx_attr = opt_prop_def(IntegerAttr[I64])
 
     qubit = operand_def(QubitType)
 
diff --git a/pennylane/measurements/__init__.py b/pennylane/measurements/__init__.py
index 846169c61..57761e8d5 100644
--- a/pennylane/measurements/__init__.py
+++ b/pennylane/measurements/__init__.py
@@ -289,7 +289,7 @@ from .null_measurement import NullMeasurement
 from .probs import ProbabilityMP, probs
 from .purity import PurityMP, purity
 from .sample import SampleMP, sample
-from .shots import ShotCopies, Shots, add_shots
+from .shots import ShotCopies, Shots, ShotsLike, add_shots
 from .state import DensityMatrixMP, StateMP, density_matrix, state
 from .var import VarianceMP, var
 from .vn_entropy import VnEntropyMP, vn_entropy
diff --git a/pennylane/operation.py b/pennylane/operation.py
index 80d843c84..4fe09692c 100644
--- a/pennylane/operation.py
+++ b/pennylane/operation.py
@@ -150,26 +150,12 @@ Boolean Functions
 :class:`~.BooleanFn`'s are functions of a single object that return ``True`` or ``False``.
 The ``operation`` module provides the following:
 
-.. warning::
-
-    All of the boolean functions in this module are currently deprecated. See the individual functions
-    for alternative code.
-
 .. currentmodule:: pennylane.operation
 
 .. autosummary::
     :toctree: api
 
-    ~defines_diagonalizing_gates
-    ~gen_is_multi_term_hamiltonian
-    ~has_gen
-    ~has_grad_method
-    ~has_multipar
-    ~has_nopar
-    ~has_unitary_gen
-    ~is_measurement
     ~is_trainable
-    ~not_tape
 
 Other
 ~~~~~
@@ -228,7 +214,6 @@ from scipy.sparse import spmatrix
 
 import pennylane as qml
 from pennylane import capture
-from pennylane.exceptions import PennyLaneDeprecationWarning
 from pennylane.math import expand_matrix, is_abstract
 from pennylane.queuing import QueuingManager
 from pennylane.typing import TensorLike
@@ -2386,119 +2371,6 @@ def operation_derivative(operation: Operation) -> TensorLike:
     return 1j * generator @ operation.matrix()
 
 
-@qml.BooleanFn
-def not_tape(obj):
-    """Returns ``True`` if the object is not a quantum tape.
-
-    .. warning::
-
-        **Deprecated**. Use ``not isinstance(obj, qml.tape.QuantumScript)`` instead.
-    """
-    warnings.warn(
-        "not_tape is deprecated. Use ``not isinstance(obj, qml.tape.QuantumScript)`` instead.",
-        PennyLaneDeprecationWarning,
-    )
-    return not isinstance(obj, qml.tape.QuantumScript)
-
-
-@qml.BooleanFn
-def has_gen(obj):
-    """Returns ``True`` if an operator has a generator defined.
-
-    .. warning::
-
-        **Deprecated**. Use ``obj.has_generator`` instead.
-    """
-    warnings.warn(
-        "has_gen is deprecated. Use Operator.has_generator instead.", PennyLaneDeprecationWarning
-    )
-    if isinstance(obj, Operator):
-        return obj.has_generator
-    try:
-        obj.generator()
-    except (AttributeError, OperatorPropertyUndefined, GeneratorUndefinedError):
-        return False
-    return True
-
-
-@qml.BooleanFn
-def has_grad_method(obj):
-    """Returns ``True`` if an operator has a grad_method defined.
-
-    .. warning::
-
-        **Deprecated**: Use ``obj.grad_method is not None`` instead.
-    """
-    warnings.warn(
-        "has_grad_method is deprecated. Use obj.grad_method is not None instead. ",
-        PennyLaneDeprecationWarning,
-    )
-    return obj.grad_method is not None
-
-
-@qml.BooleanFn
-def has_multipar(obj):
-    """Returns ``True`` if an operator has more than one parameter
-    according to ``num_params``.
-
-    .. warning::
-
-        **Deprecated**: Use ``obj.num_params > 1`` instead.
-    """
-    warnings.warn(
-        "has_multipar is deprecated. Use obj.num_params > 1 instead.", PennyLaneDeprecationWarning
-    )
-    return obj.num_params > 1
-
-
-@qml.BooleanFn
-def has_nopar(obj):
-    """Returns ``True`` if an operator has no parameters
-    according to ``num_params``.
-
-    .. warning::
-
-        **Deprecated**: Use ``obj.num_params == 0`` instead.
-    """
-    warnings.warn(
-        "has_nopar is deprecated. Use obj.num_params == 0 instead.", PennyLaneDeprecationWarning
-    )
-    return obj.num_params == 0
-
-
-@qml.BooleanFn
-def has_unitary_gen(obj):
-    """Returns ``True`` if an operator has a unitary_generator
-    according to the ``has_unitary_generator`` flag.
-
-    .. warning::
-
-        **Deprecated**: Use ``obj in qml.ops.qubit.attributes.has_unitary_gen`` insteaed.
-
-    """
-    warnings.warn(
-        "has_unitary_gen is deprecated. Use `obj in qml.ops.qubit.attributes.has_unitary_generator` instead.",
-        PennyLaneDeprecationWarning,
-    )
-    # Linting check disabled as static analysis can misidentify qml.ops as the set instance qml.ops.qubit.ops
-    return obj in qml.ops.qubit.attributes.has_unitary_generator
-
-
-@qml.BooleanFn
-def is_measurement(obj):
-    """Returns ``True`` if an operator is a ``MeasurementProcess`` instance.
-
-    .. warning::
-
-        **Deprecated**: Use ``isinstance(obj, qml.measurements.MeasurementProcess)`` instead.
-    """
-    warnings.warn(
-        "is_measurement is deprecated. Use isinstance(obj, qml.measurements.MeasurementProcess) instead.",
-        PennyLaneDeprecationWarning,
-    )
-    return isinstance(obj, qml.measurements.MeasurementProcess)
-
-
 @qml.BooleanFn
 def is_trainable(obj):
     """Returns ``True`` if any of the parameters of an operator is trainable
@@ -2507,22 +2379,6 @@ def is_trainable(obj):
     return any(qml.math.requires_grad(p) for p in obj.parameters)
 
 
-@qml.BooleanFn
-def defines_diagonalizing_gates(obj):
-    """Returns ``True`` if an operator defines the diagonalizing gates.
-
-    .. warning::
-
-        **Deprecated:** Use ``obj.has_diagonalizing_gates`` instead.
-
-    """
-    warnings.warn(
-        "defines_diagonalizing_gates is deprecated. Use obj.has_diagonalizing_gates instead.",
-        PennyLaneDeprecationWarning,
-    )
-    return obj.has_diagonalizing_gates
-
-
 _gen_is_multi_term_hamiltonian_code = """
 if not isinstance(obj, Operator) or not obj.has_generator:
     return False
@@ -2535,43 +2391,6 @@ except TermsUndefinedError:
 """
 
 
-@qml.BooleanFn
-def gen_is_multi_term_hamiltonian(obj):
-    """Returns ``True`` if an operator has a generator defined and it is a Hamiltonian
-    with more than one term.
-
-    .. warning::
-
-        **Deprecated**: Use the following code instead:
-
-        .. code-block:: python
-
-            def gen_is_multi_term_hamiltonian(obj):
-                if not isinstance(obj, Operator) or not obj.has_generator:
-                    return False
-                try:
-                    generator = obj.generator()
-                    _, ops = generator.terms()
-                    return len(ops) > 1
-                except TermsUndefinedError:
-                    return False
-
-
-    """
-    warnings.warn(
-        f"gen_is_multiterm_hamiltonian is deprecated. Use {_gen_is_multi_term_hamiltonian_code}",
-        PennyLaneDeprecationWarning,
-    )
-    if not isinstance(obj, Operator) or not obj.has_generator:
-        return False
-    try:
-        generator = obj.generator()
-        _, ops = generator.terms()  # len(coeffs) can be weird sometimes
-        return len(ops) > 1
-    except TermsUndefinedError:
-        return False
-
-
 def __getattr__(name):
     """To facilitate StatePrep rename"""
     if name == "StatePrep":
diff --git a/pennylane/workflow/set_shots.py b/pennylane/workflow/set_shots.py
index a6ba673fb..c9a66050c 100644
--- a/pennylane/workflow/set_shots.py
+++ b/pennylane/workflow/set_shots.py
@@ -16,22 +16,24 @@ This module contains the set_shots decorator.
 """
 from __future__ import annotations
 
-from collections.abc import Callable, Sequence
-from typing import TYPE_CHECKING
+from collections.abc import Callable
+from typing import TYPE_CHECKING, overload
 
 from .qnode import QNode
 
-# Sentinel value to distinguish decorator mode from direct calls
-_DECORATOR_MODE = object()
-
 if TYPE_CHECKING:
-    from pennylane.measurements import Shots
+    from pennylane.measurements import ShotsLike
+
 
+# Sentinel value to detect when shots parameter is not provided
+_SHOTS_NOT_PROVIDED = object()
 
-def set_shots(
-    qnode: QNode | object = _DECORATOR_MODE,
-    shots: Shots | int | Sequence[int | tuple[int, int]] | None = None,
-) -> QNode | Callable[[QNode], QNode]:
+
+@overload
+def set_shots(qnode: QNode, shots: ShotsLike) -> QNode: ...
+@overload
+def set_shots(shots: ShotsLike) -> Callable[[QNode], QNode]: ...
+def set_shots(*args, shots: ShotsLike = _SHOTS_NOT_PROVIDED):
     """Transform used to set or update a circuit's shots.
 
     Args:
@@ -51,7 +53,27 @@ def set_shots(
 
     **Examples**
 
-    Set the number of shots as a decorator:
+    Set the number of shots as a decorator (positional argument):
+
+    .. code-block:: python
+
+        @qml.set_shots(500)
+        @qml.qnode(qml.device("default.qubit", wires=1))
+        def circuit():
+            qml.RX(1.23, wires=0)
+            return qml.expval(qml.Z(0))
+
+    Set analytic mode as a decorator (positional argument):
+
+    .. code-block:: python
+
+        @qml.set_shots(None)
+        @qml.qnode(qml.device("default.qubit", wires=1))
+        def circuit():
+            qml.RX(1.23, wires=0)
+            return qml.expval(qml.Z(0))
+
+    Set the number of shots as a decorator (keyword argument):
 
     .. code-block:: python
 
@@ -61,6 +83,16 @@ def set_shots(
             qml.RX(1.23, wires=0)
             return qml.sample(qml.Z(0))
 
+    Set analytic mode as a decorator (keyword argument):
+
+    .. code-block:: python
+
+        @qml.set_shots(shots=None)
+        @qml.qnode(qml.device("default.qubit", wires=1))
+        def circuit():
+            qml.RX(1.23, wires=0)
+            return qml.expval(qml.Z(0))
+
     Run the circuit:
 
     >>> circuit()
@@ -72,19 +104,38 @@ def set_shots(
     >>> new_circ()
     (array([-1.,  1., -1.,  1.]), array([ 1.,  1.,  1., -1.,  1.,  1., -1., -1.,  1.,  1.]))
 
+    Set analytic mode in-line for an existing circuit:
+
+    >>> analytic_circ = qml.set_shots(circuit, shots=None)
+    >>> analytic_circ()
+    0.5403023058681398
     """
-    # When used as decorator with arguments: @set_shots(shots=...)
-    # This happens when qnode parameter is not provided (decorator mode)
-    if qnode is _DECORATOR_MODE:
+    # Keyword-only case: @set_shots(shots=500) or @set_shots(shots=None)
+    if len(args) == 0 and shots is not _SHOTS_NOT_PROVIDED:
+        return _set_shots_dispatch(shots)
+
+    if len(args) == 1 and shots is not _SHOTS_NOT_PROVIDED:
+        # Direct application: set_shots(qnode, shots=500) or set_shots(qnode, shots=None)
+        return _apply_shots_to_qnode(args[0], shots)
+    if len(args) == 1 and shots is _SHOTS_NOT_PROVIDED:
+        # Positional decorator: @set_shots(500) or @set_shots(None)
+        return _set_shots_dispatch(args[0])
+    if len(args) == 2 and shots is _SHOTS_NOT_PROVIDED:
+        return _apply_shots_to_qnode(*args)
+    raise ValueError(f"Invalid arguments to set_shots: {args=}, {shots=}")
+
+
+def _set_shots_dispatch(shots_value: ShotsLike) -> Callable[[QNode], QNode]:
+    """Default case: @set_shots(500) - positional shots value"""
 
-        def decorator(qnode_func):
-            return set_shots(qnode_func, shots)
+    def positional_decorator(qnode_func: QNode) -> QNode:
+        return _apply_shots_to_qnode(qnode_func, shots_value)
 
-        return decorator
+    return positional_decorator
 
-    # When called directly with a QNode
-    if isinstance(qnode, QNode):
-        return qnode.update_shots(shots)
 
-    # If qnode is not a QNode (including explicit None), raise error
-    raise ValueError("set_shots can only be applied to QNodes")
+def _apply_shots_to_qnode(qnode: QNode, shots: ShotsLike) -> QNode:
+    """Handle direct application to a QNode: set_shots(qnode, shots=500)"""
+    if not isinstance(qnode, QNode):
+        raise ValueError("set_shots can only be applied to QNodes")
+    return qnode.update_shots(shots)


Test patch:
diff --git a/.github/workflows/interface-unit-tests.yml b/.github/workflows/interface-unit-tests.yml
index bc80ec313..fd1bea998 100644
--- a/.github/workflows/interface-unit-tests.yml
+++ b/.github/workflows/interface-unit-tests.yml
@@ -533,7 +533,7 @@ jobs:
       pytest_additional_args: ${{ needs.warnings-as-errors-setup.outputs.pytest_warning_args }}
       pytest_xml_file_path: '${{ inputs.job_name_prefix }}external-libraries-tests (${{ matrix.python-version }})${{ inputs.job_name_suffix }}.xml'
       additional_pip_packages: |
-        pyzx matplotlib stim quimb==1.11.0 mitiq ply optax scipy-openblas32>=0.3.26 qualtran openqasm3 antlr4_python3_runtime xdsl==0.45 filecheck
+        pyzx matplotlib stim quimb==1.11.0 mitiq ply optax scipy-openblas32>=0.3.26 qualtran openqasm3 antlr4_python3_runtime xdsl==0.46 filecheck
         ${{ needs.default-dependency-versions.outputs.jax-version }}
         git+https://github.com/PennyLaneAI/pennylane-qiskit.git@master
         ${{ needs.default-dependency-versions.outputs.tensorflow-version }}
diff --git a/tests/python_compiler/dialects/test_quantum_dialect.py b/tests/python_compiler/dialects/test_quantum_dialect.py
index 4a82185b9..4bb60c50d 100644
--- a/tests/python_compiler/dialects/test_quantum_dialect.py
+++ b/tests/python_compiler/dialects/test_quantum_dialect.py
@@ -111,6 +111,13 @@ def test_all_attributes_names(attr):
 
 def test_assembly_format():
     program = """
+    // CHECK: quantum.alloc(1) : !quantum.reg
+    %qreg_alloc_static = quantum.alloc(1) : !quantum.reg
+
+    %i = "test.op"() : () -> i64
+    // CHECK: quantum.alloc(%i) : !quantum.reg
+    %qreg_alloc_dyn = quantum.alloc(%i) : !quantum.reg
+
     // CHECK: [[QREG:%.+]] = "test.op"() : () -> !quantum.reg
     %qreg = "test.op"() : () -> !quantum.reg
 
@@ -123,8 +130,20 @@ def test_assembly_format():
     // CHECK: [[PARAM:%.+]] = "test.op"() : () -> f64
     %cst = "test.op"() : () -> f64
 
-    // CHECK: [[QUBIT:%.+]] = "quantum.extract"([[QREG]])
-    %qubit = "quantum.extract"(%qreg) <{idx_attr = 0 : i64}> : (!quantum.reg) -> !quantum.bit
+    // CHECK: [[QUBIT:%.+]] = quantum.extract %qreg[0] : !quantum.reg -> !quantum.bit
+    %qubit = quantum.extract %qreg[0] : !quantum.reg -> !quantum.bit
+
+    // CHECK: [[WIRE_DYN:%.+]] = "test.op"() : () -> i64
+    %cst2 = "test.op"() : () -> i64
+
+    // CHECK: [[QUBIT_DYN:%.+]] = quantum.extract %qreg[%cst2] : !quantum.reg -> !quantum.bit
+    %qubit_dyn = quantum.extract %qreg[%cst2] : !quantum.reg -> !quantum.bit
+
+    // CHECK: quantum.insert [[QREG:%.+]][0], [[QUBIT]] : !quantum.reg, !quantum.bit
+    %qreg_insert_static = quantum.insert %qreg[0], %qubit : !quantum.reg, !quantum.bit
+
+    // CHECK: quantum.insert [[QREG:%.+]][[[WIRE_DYN]]], [[QUBIT]] : !quantum.reg, !quantum.bit
+    %qreg_insert_dyn = quantum.insert %qreg[%cst2], %qubit : !quantum.reg, !quantum.bit
 
     // CHECK: [[QUBIT2:%.+]] = quantum.custom "RX"([[PARAM]]) [[QUBIT]]
     %out_qubit = quantum.custom "RX"(%cst) %qubit : !quantum.bit
diff --git a/tests/python_compiler/transforms/test_xdsl_measurements_from_samples.py b/tests/python_compiler/transforms/test_xdsl_measurements_from_samples.py
index dc3d215b1..4140fdae2 100644
--- a/tests/python_compiler/transforms/test_xdsl_measurements_from_samples.py
+++ b/tests/python_compiler/transforms/test_xdsl_measurements_from_samples.py
@@ -324,8 +324,8 @@ class TestMeasurementsFromSamplesPass:
                 %1 = tensor.extract %0[] : tensor<i64>
                 quantum.device shots(%1) ["", "", ""]
 
-                // CHECK: [[qreg:%.+]] = {{.*}}quantum.alloc{{.*}} : () -> !quantum.reg
-                %2 = "quantum.alloc"() <{nqubits_attr = 2 : i64}> : () -> !quantum.reg
+                // CHECK: [[qreg:%.+]] = quantum.alloc
+                %2 = quantum.alloc(2) : !quantum.reg
 
                 // CHECK: [[compbasis:%.+]] = quantum.compbasis qreg [[qreg]] : !quantum.obs
                 %3 = quantum.compbasis qreg %2 : !quantum.obs
@@ -358,10 +358,11 @@ class TestMeasurementsFromSamplesPass:
                 %1 = tensor.extract %0[] : tensor<i64>
                 quantum.device shots(%1) ["", "", ""]
 
-                %2 = "quantum.alloc"() <{nqubits_attr = 2 : i64}> : () -> !quantum.reg
+                // CHECK: [[qreg:%.+]] = quantum.alloc
+                %2 = quantum.alloc(2) : !quantum.reg
 
-                // CHECK: [[q0:%.+]] = {{.*}}quantum.extract{{.*}}idx_attr = 0{{.*}} -> !quantum.bit
-                %3 = "quantum.extract"(%2) <{idx_attr = 0 : i64}> : (!quantum.reg) -> !quantum.bit
+                // CHECK: [[q0:%.+]] = quantum.extract [[qreg]][0]
+                %3 = quantum.extract %2[0] : !quantum.reg -> !quantum.bit
 
                 // CHECK: [[compbasis0:%.+]] = quantum.compbasis qubits [[q0]] : !quantum.obs
                 %4 = quantum.compbasis qubits %3 : !quantum.obs
@@ -372,8 +373,8 @@ class TestMeasurementsFromSamplesPass:
                 // CHECK-NOT: quantum.probs
                 %5 = quantum.probs %4 : tensor<2xf64>
 
-                // CHECK: [[q1:%.+]] = {{.*}}quantum.extract{{.*}}idx_attr = 1{{.*}} -> !quantum.bit
-                %6 = "quantum.extract"(%2) <{idx_attr = 1 : i64}> : (!quantum.reg) -> !quantum.bit
+                // CHECK: [[q1:%.+]] = quantum.extract [[qreg]][1]
+                %6 = quantum.extract %2[1] : !quantum.reg -> !quantum.bit
 
                 // CHECK: [[compbasis1:%.+]] = quantum.compbasis qubits [[q1]] : !quantum.obs
                 %7 = quantum.compbasis qubits %6 : !quantum.obs
diff --git a/tests/test_operation.py b/tests/test_operation.py
index 790b160d2..d9e59879a 100644
--- a/tests/test_operation.py
+++ b/tests/test_operation.py
@@ -1578,95 +1578,6 @@ class TestCriteria:
     stiff_rot = qml.Rot(0.1, -0.7, 0.2, wires=0)
     exp = qml.expval(qml.PauliZ(0))
 
-    def test_docstring(self):
-        expected = "Returns ``True`` if an operator has a generator defined."
-        assert expected in qml.operation.has_gen.__doc__
-
-    def test_has_gen(self):
-        """Test has_gen criterion."""
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert qml.operation.has_gen(self.rx)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert not qml.operation.has_gen(self.cnot)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert not qml.operation.has_gen(self.rot)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert not qml.operation.has_gen(self.exp)
-
-    def test_has_grad_method(self):
-        """Test has_grad_method criterion."""
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert qml.operation.has_grad_method(self.rx)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert qml.operation.has_grad_method(self.rot)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert not qml.operation.has_grad_method(self.cnot)
-
-    def test_gen_is_multi_term_hamiltonian(self):
-        """Test gen_is_multi_term_hamiltonian criterion."""
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert qml.operation.gen_is_multi_term_hamiltonian(self.doubleExcitation)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert not qml.operation.gen_is_multi_term_hamiltonian(self.cnot)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert not qml.operation.gen_is_multi_term_hamiltonian(self.rot)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert not qml.operation.gen_is_multi_term_hamiltonian(self.exp)
-
-        class SProdGen(Operator):
-
-            def generator(self):
-                return 2.0 * (qml.X(0) + qml.Y(0))
-
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert qml.operation.gen_is_multi_term_hamiltonian(SProdGen(wires=0))
-
-        class SumGen(Operator):
-
-            def generator(self):
-                return qml.X(0) + qml.Y(1)
-
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert qml.operation.gen_is_multi_term_hamiltonian(SumGen(wires=0))
-
-    def test_has_multipar(self):
-        """Test has_multipar criterion."""
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert not qml.operation.has_multipar(self.rx)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert qml.operation.has_multipar(self.rot)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert not qml.operation.has_multipar(self.cnot)
-
-    def test_has_nopar(self):
-        """Test has_nopar criterion."""
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert not qml.operation.has_nopar(self.rx)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert not qml.operation.has_nopar(self.rot)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert qml.operation.has_nopar(self.cnot)
-
-    def test_has_unitary_gen(self):
-        """Test has_unitary_gen criterion."""
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert qml.operation.has_unitary_gen(self.rx)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert not qml.operation.has_unitary_gen(self.rot)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert not qml.operation.has_unitary_gen(self.cnot)
-
-    def test_is_measurement(self):
-        """Test is_measurement criterion."""
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert not qml.operation.is_measurement(self.rx)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert not qml.operation.is_measurement(self.rot)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert not qml.operation.is_measurement(self.cnot)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert qml.operation.is_measurement(self.exp)
-
     def test_is_trainable(self):
         """Test is_trainable criterion."""
         assert qml.operation.is_trainable(self.rx)
@@ -1675,31 +1586,6 @@ class TestCriteria:
         assert not qml.operation.is_trainable(self.stiff_rot)
         assert not qml.operation.is_trainable(self.cnot)
 
-    def test_composed(self):
-        """Test has_gen criterion."""
-        both = qml.operation.has_gen & qml.operation.is_trainable
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert both(self.rx)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert not both(self.cnot)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert not both(self.rot)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert not both(self.exp)
-
-    def test_not_tape(self):
-        """Test the not_tape criterion."""
-
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert qml.operation.not_tape(2)
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert not qml.operation.not_tape(qml.tape.QuantumScript())
-
-    def test_defines_diagonalizing_gates(self):
-        """Test the defines_diagonalizing_gates criterion."""
-        with pytest.warns(qml.exceptions.PennyLaneDeprecationWarning):
-            assert qml.operation.defines_diagonalizing_gates(qml.X(0))
-
 
 pairs_of_ops = [
     (qml.S(0), qml.T(0)),
diff --git a/tests/test_qnode.py b/tests/test_qnode.py
index 517b81add..d3e815f2a 100644
--- a/tests/test_qnode.py
+++ b/tests/test_qnode.py
@@ -2092,6 +2092,24 @@ class TestSetShots:
         result = circuit()
         assert len(result) == 50
 
+    def test_set_shots_direct_application_argshots(self):
+        """Test applying set_shots directly to an existing QNode."""
+        dev = qml.device("default.qubit", wires=1, shots=10)
+
+        @qml.qnode(dev)
+        def original_circuit():
+            qml.RX(1.0, wires=0)
+            return qml.sample(qml.PauliZ(0))
+
+        # Apply set_shots directly
+        new_circuit = set_shots(original_circuit, 75)
+
+        assert original_circuit._shots == qml.measurements.Shots(10)
+        assert new_circuit._shots == qml.measurements.Shots(75)
+
+        result = new_circuit()
+        assert len(result) == 75
+
     def test_set_shots_direct_application(self):
         """Test applying set_shots directly to an existing QNode."""
         dev = qml.device("default.qubit", wires=1, shots=10)
@@ -2123,6 +2141,7 @@ class TestSetShots:
         new_circuit = set_shots(circuit, shots=shot_vector)
 
         assert new_circuit._shots.total_shots == 40  # 10*3 + 5*2
+        assert new_circuit._shots.shot_vector == ((10, 3), (5, 2))
         result = new_circuit()
         assert isinstance(result, tuple)
         assert len(result) == 5  # 3 + 2 groups
@@ -2175,6 +2194,18 @@ class TestSetShots:
         with pytest.raises(ValueError, match=expected_error):
             set_shots(invalid_input, shots=100)
 
+    @pytest.mark.parametrize(
+        "args,kwargs,error_message",
+        [
+            ((), {}, "Invalid arguments to set_shots"),
+            ((100, 200), {}, "set_shots can only be applied to QNodes"),
+        ],
+    )
+    def test_set_shots_error_on_invalid_argument_patterns(self, args, kwargs, error_message):
+        """Test that set_shots raises ValueError for invalid argument patterns."""
+        with pytest.raises(ValueError, match=error_message):
+            set_shots(*args, **kwargs)
+
     def test_set_shots_with_measurements_requiring_shots(self):
         """Test set_shots works correctly with measurements that require shots."""
         dev = qml.device("default.qubit", wires=2)
@@ -2337,3 +2368,212 @@ class TestSetShots:
         ):
             result = circuit.update(diff_method="parameter-shift")(shots=50)
         assert len(result) == 100
+
+    def test_set_shots_positional_preserves_qnode_properties(self):
+        """Test that @set_shots(500) preserves QNode properties."""
+        dev = qml.device("default.qubit", wires=1)
+
+        @set_shots(100)
+        @qml.qnode(dev, diff_method="parameter-shift", interface="autograd")
+        def circuit(x):
+            qml.RX(x, wires=0)
+            return qml.expval(qml.PauliZ(0))
+
+        # Check that shots are set correctly
+        assert circuit._shots == qml.measurements.Shots(100)
+
+        # Check that other properties are preserved
+        assert circuit.diff_method == "parameter-shift"
+        assert circuit.interface == "autograd"
+        assert circuit.device is dev
+
+    def test_set_shots_positional_decorator_integer(self):
+        """Test @set_shots(500) positional decorator syntax with integer shots."""
+        dev = qml.device("default.qubit", wires=1)
+
+        @set_shots(500)
+        @qml.qnode(dev)
+        def circuit():
+            qml.RX(1.0, wires=0)
+            return qml.sample(qml.PauliZ(0))
+
+        assert circuit._shots == qml.measurements.Shots(500)
+        result = circuit()
+        assert len(result) == 500
+
+    def test_set_shots_positional_decorator_none(self):
+        """Test @set_shots(None) positional decorator syntax for analytic mode."""
+        dev = qml.device("default.qubit", wires=1)
+
+        @set_shots(None)
+        @qml.qnode(dev)
+        def circuit():
+            qml.RX(1.0, wires=0)
+            return qml.expval(qml.PauliZ(0))
+
+        assert circuit._shots is None or circuit._shots.total_shots is None
+        result = circuit()
+        assert isinstance(result, (float, np.floating))
+        # Analytic result should be cos(1.0)
+        assert qml.math.allclose(result, np.cos(1.0))
+
+    def test_set_shots_positional_decorator_shot_vector(self):
+        """Test @set_shots(shot_vector) positional decorator syntax with shot vectors."""
+        dev = qml.device("default.qubit", wires=1)
+
+        shot_vector = [(20, 2), (15, 3)]  # 20 shots × 2, 15 shots × 3
+
+        @set_shots(shot_vector)
+        @qml.qnode(dev)
+        def circuit():
+            qml.RX(1.0, wires=0)
+            return qml.sample(qml.PauliZ(0))
+
+        assert circuit._shots.total_shots == 85  # 20*2 + 15*3
+        assert circuit._shots.shot_vector == ((20, 2), (15, 3))
+        result = circuit()
+        assert isinstance(result, tuple)
+        assert len(result) == 5  # 2 + 3 groups
+
+    def test_set_shots_positional_vs_keyword_equivalence(self):
+        """Test that @set_shots(500) and @set_shots(shots=500) are equivalent."""
+        dev = qml.device("default.qubit", wires=1)
+
+        @set_shots(None)  # Use analytic mode for exact comparison
+        @qml.qnode(dev)
+        def positional_circuit():
+            qml.RX(1.0, wires=0)
+            return qml.expval(qml.PauliZ(0))
+
+        @set_shots(shots=None)  # Use analytic mode for exact comparison
+        @qml.qnode(dev)
+        def keyword_circuit():
+            qml.RX(1.0, wires=0)
+            return qml.expval(qml.PauliZ(0))
+
+        # Both should have analytic mode
+        assert positional_circuit._shots is None or positional_circuit._shots.total_shots is None
+        assert keyword_circuit._shots is None or keyword_circuit._shots.total_shots is None
+
+        # Both should produce the same results for analytic case
+        pos_result = positional_circuit()
+        kw_result = keyword_circuit()
+        assert qml.math.allclose(pos_result, kw_result)
+
+        # Should equal the expected analytic result
+        expected = np.cos(1.0)
+        assert qml.math.allclose(pos_result, expected)
+        assert qml.math.allclose(kw_result, expected)
+
+    def test_set_shots_positional_vs_direct_equivalence(self):
+        """Test that @set_shots(500) and set_shots(qnode, shots=500) are equivalent."""
+        dev = qml.device("default.qubit", wires=1)
+
+        @qml.qnode(dev)
+        def base_circuit():
+            qml.RX(1.0, wires=0)
+            return qml.expval(qml.PauliZ(0))
+
+        @set_shots(None)  # Use analytic mode for exact comparison
+        @qml.qnode(dev)
+        def positional_circuit():
+            qml.RX(1.0, wires=0)
+            return qml.expval(qml.PauliZ(0))
+
+        # Apply set_shots directly to base circuit
+        direct_circuit = set_shots(base_circuit, shots=None)
+
+        # Both should have analytic mode
+        assert positional_circuit._shots is None or positional_circuit._shots.total_shots is None
+        assert direct_circuit._shots is None or direct_circuit._shots.total_shots is None
+
+        # Both should produce the same results
+        pos_result = positional_circuit()
+        direct_result = direct_circuit()
+        assert qml.math.allclose(pos_result, direct_result)
+
+        # Should equal the expected analytic result
+        expected = np.cos(1.0)
+        assert qml.math.allclose(pos_result, expected)
+        assert qml.math.allclose(direct_result, expected)
+
+    def test_set_shots_positional_analytic_vs_shot_difference(self):
+        """Test that @set_shots(None) vs @set_shots(1000) produce different behaviors."""
+        dev = qml.device("default.qubit", wires=1)
+
+        @set_shots(None)
+        @qml.qnode(dev)
+        def analytic_circuit():
+            qml.Hadamard(wires=0)
+            return qml.expval(qml.PauliZ(0))
+
+        @set_shots(1000)
+        @qml.qnode(dev)
+        def shot_circuit():
+            qml.Hadamard(wires=0)
+            return qml.expval(qml.PauliZ(0))
+
+        # Analytic should be exactly 0.0
+        analytic_results = [analytic_circuit() for _ in range(10)]
+        assert all(result == 0.0 for result in analytic_results)
+
+        # Shot-based should have variance
+        shot_results = [shot_circuit() for _ in range(10)]
+        # Should be close to 0 but with some variance
+        assert abs(np.mean(shot_results)) < 0.1  # Should be close to 0
+        assert np.std(shot_results) > 0.001  # Should have some variance
+
+    def test_set_shots_positional_complex_shot_patterns(self):
+        """Test @set_shots with complex shot patterns."""
+        dev = qml.device("default.qubit", wires=2)
+
+        # Test with tuple (shots, copies) format
+        @set_shots([(10, 5), (20, 3), 50])
+        @qml.qnode(dev)
+        def circuit():
+            qml.Hadamard(wires=0)
+            qml.CNOT(wires=[0, 1])
+            return qml.sample()
+
+        # Total shots: 10*5 + 20*3 + 50 = 50 + 60 + 50 = 160
+        assert circuit._shots.total_shots == 160
+        result = circuit()
+        assert isinstance(result, tuple)
+        # Should have 5 + 3 + 1 = 9 result groups
+        assert len(result) == 9
+
+    def test_set_shots_positional_nested_decorators(self):
+        """Test @set_shots works with other decorators."""
+        dev = qml.device("default.qubit", wires=1)
+
+        @qml.transforms.cancel_inverses  # Another transform
+        @set_shots(250)
+        @qml.qnode(dev)
+        def circuit():
+            qml.RX(1.0, wires=0)
+            qml.RX(-1.0, wires=0)  # Should be cancelled
+            return qml.sample(qml.PauliZ(0))
+
+        assert circuit._shots == qml.measurements.Shots(250)
+        result = circuit()
+        assert len(result) == 250
+
+    def test_set_shots_positional_multiple_calls_consistent(self):
+        """Test that @set_shots(500) produces consistent behavior across multiple calls."""
+        dev = qml.device("default.qubit", wires=1)
+
+        @set_shots(None)  # Analytic mode
+        @qml.qnode(dev)
+        def circuit():
+            qml.RX(0.5, wires=0)
+            return qml.expval(qml.PauliZ(0))
+
+        # Multiple calls should give exactly the same result in analytic mode
+        results = [circuit() for _ in range(5)]
+        expected = np.cos(0.5)
+
+        for result in results:
+            assert qml.math.allclose(result, expected)
+
+        # All results should be identical
+        assert all(qml.math.allclose(results[0], r) for r in results[1:])