Repository: PrefectHQ/prefect

Issue:
Warning on `ifelse`/`merge` use
## Description

I can't seem to find any combination of `ifelse`/`merge` usage that doesn't give me the `UserWarning: Tasks were created but not added to the flow` warning.  

## Expected Behavior

No warning

## Reproduction

```python
from prefect import Flow, task
from prefect.tasks.control_flow.conditional import ifelse, merge

@task
def a(x):
    return x + 1

@task
def b(x):
    return x * 2

with Flow('f') as flow:
    x = 2
    t1 = a(x)
    t2 = b(x)
    ifelse(x > 1, t1, t2)
    y = merge(t1, t2)
flow.run()
# UserWarning: Tasks were created but not added to the flow
```

Is there something going on in the example above I may be misunderstanding?


Discussion:

Resolving patch:
diff --git a/src/prefect/core/flow.py b/src/prefect/core/flow.py
--- a/src/prefect/core/flow.py
+++ b/src/prefect/core/flow.py
@@ -327,25 +327,20 @@ def _flow_context(self) -> Iterator["Flow"]:
         """
         When entering a flow context, the Prefect context is modified to include:
             - `flow`: the flow itself
-            - `_new_task_tracker`: a set of all tasks created while the context is
+            - `_unused_task_tracker`: a set of all tasks created while the context is
                 open, in order to provide user friendly warnings if they aren't added
                 to the flow itself. This is purely for user experience.
         """
-        new_task_tracker = set()  # type: Set[Task]
+        unused_task_tracker = set()  # type: Set[Task]
 
-        with prefect.context(flow=self, _new_task_tracker=new_task_tracker):
+        with prefect.context(flow=self, _unused_task_tracker=unused_task_tracker):
             yield self
 
         # constants are not tracked at the flow level
-        new_tasks = {
-            t
-            for t in new_task_tracker
-            if not isinstance(t, prefect.tasks.core.constants.Constant)
-        }
-        if new_tasks.difference(self.tasks):
+        if unused_task_tracker.difference(self.tasks):
             warnings.warn(
                 "Tasks were created but not added to the flow: "
-                f"{new_task_tracker.difference(self.tasks)}. This can occur "
+                f"{unused_task_tracker.difference(self.tasks)}. This can occur "
                 "when `Task` classes, including `Parameters`, are instantiated "
                 "inside a `with flow:` block but not added to the flow either "
                 "explicitly or as the input to another task. For more information, "
diff --git a/src/prefect/core/task.py b/src/prefect/core/task.py
--- a/src/prefect/core/task.py
+++ b/src/prefect/core/task.py
@@ -301,8 +301,9 @@ def __init__(
         # if new task creations are being tracked, add this task
         # this makes it possible to give guidance to users that forget
         # to add tasks to a flow
-        if "_new_task_tracker" in prefect.context:
-            prefect.context._new_task_tracker.add(self)
+        if "_unused_task_tracker" in prefect.context:
+            if not isinstance(self, prefect.tasks.core.constants.Constant):
+                prefect.context._unused_task_tracker.add(self)
 
     def __repr__(self) -> str:
         return "<Task: {self.name}>".format(self=self)
@@ -403,9 +404,14 @@ def copy(self, **task_args: Any) -> "Task":
 
         # if new task creations are being tracked, add this task
         # this makes it possible to give guidance to users that forget
-        # to add tasks to a flow
-        if "_new_task_tracker" in prefect.context:
-            prefect.context._new_task_tracker.add(new)
+        # to add tasks to a flow. We also remove the original task,
+        # as it has been "interacted" with and don't want spurious
+        # warnings
+        if "_unused_task_tracker" in prefect.context:
+            if self in prefect.context._unused_task_tracker:
+                prefect.context._unused_task_tracker.remove(self)
+            if not isinstance(new, prefect.tasks.core.constants.Constant):
+                prefect.context._unused_task_tracker.add(new)
 
         return new
 


Test patch:
diff --git a/tests/core/test_flow.py b/tests/core/test_flow.py
--- a/tests/core/test_flow.py
+++ b/tests/core/test_flow.py
@@ -695,21 +695,21 @@ def test_key_states_raises_error_if_not_iterable():
 def test_warning_raised_if_tasks_are_created_but_not_added_to_flow():
     with pytest.warns(UserWarning, match="Tasks were created but not added"):
         with Flow(name="test"):
-            tracker = prefect.context._new_task_tracker
+            tracker = prefect.context._unused_task_tracker
             assert len(tracker) == 0
             x = Parameter("x")
             assert len(tracker) == 1
             assert x in tracker
-        assert "_new_task_tracker" not in prefect.context
+        assert "_unused_task_tracker" not in prefect.context
 
 
 def test_warning_raised_if_tasks_are_created_but_not_added_to_nested_flow():
     # only one warning for nested flows
     with pytest.warns(None) as record:
         with Flow(name="test"):
-            tracker_1 = prefect.context._new_task_tracker
+            tracker_1 = prefect.context._unused_task_tracker
             with Flow(name="test2"):
-                tracker_2 = prefect.context._new_task_tracker
+                tracker_2 = prefect.context._unused_task_tracker
                 x = Parameter("x")
                 assert x in tracker_2
                 assert x not in tracker_1
@@ -727,7 +727,7 @@ def test_warning_not_raised_if_tasks_are_created_and_added_to_flow():
     assert len(record) == 0
 
 
-def test_warning_not_raised_for_constant_tasks():
+def test_warning_not_raised_for_constant_tasks_as_indices():
     with pytest.warns(None) as record:
         with Flow(name="test") as f:
             tt = Task()[0]
@@ -739,6 +739,23 @@ def test_warning_not_raised_for_constant_tasks():
     assert len(record) == 0
 
 
+def test_warning_not_raised_for_constant_tasks_as_inputs():
+    @task
+    def add_one(x):
+        return x + 1
+
+    with pytest.warns(None) as record:
+        with Flow(name="test") as f:
+            tt = add_one(10)
+
+    # confirm tasks were added
+    assert len(f.tasks) == 1
+    assert f.constants[tt]["x"] == 10
+
+    # no warnings
+    assert len(record) == 0
+
+
 def test_warning_raised_if_tasks_are_copied_but_not_added_to_flow():
     x = Parameter("x")
     with pytest.warns(UserWarning, match="Tasks were created but not added"):
@@ -746,6 +763,61 @@ def test_warning_raised_if_tasks_are_copied_but_not_added_to_flow():
             x.copy("x2")
 
 
+def test_warning_not_raised_for_tasks_defined_in_flow_context():
+    # https://github.com/PrefectHQ/prefect/issues/2677
+
+    with pytest.warns(None) as record:
+        with Flow(name="test") as flow:
+
+            @task
+            def ten():
+                return 10
+
+            @task
+            def add(x, y):
+                return x + y
+
+            x = ten()
+            result = add(x(), 1)
+
+    # no warnings
+    assert len(record) == 0
+
+
+def test_warning_raised_for_tasks_defined_in_flow_context_and_unused():
+    # https://github.com/PrefectHQ/prefect/issues/2677
+
+    with pytest.warns(UserWarning, match="Tasks were created but not added"):
+        with Flow(name="test") as flow:
+
+            @task
+            def ten():
+                return 10
+
+            @task
+            def add(x, y):
+                return x + y
+
+
+def test_warning_not_raised_for_lambda_tasks_defined_in_flow_context():
+    # https://github.com/PrefectHQ/prefect/issues/2677
+
+    with pytest.warns(None) as record:
+        with Flow(name="test") as flow:
+            x = task(lambda: 10)
+            result = task(lambda x, y, z: x + y + z)(x, x(), 1)
+
+    # no warnings
+    assert len(record) == 0
+
+
+def test_warning_raised_for_lambda_tasks_defined_in_flow_context_and_unused():
+    # https://github.com/PrefectHQ/prefect/issues/2677
+    with pytest.warns(UserWarning, match="Tasks were created but not added"):
+        with Flow(name="test") as flow:
+            x = task(lambda: 10)
+
+
 def test_context_is_scoped_to_flow_context():
     with Flow(name="f"):
         prefect.context.name = "f"