AST fuzzer: wrap unary operator

Add a mutation that wraps an expression in a unary operator.
Valid unary operators depend on the type of the expression.

Fixes: tint:1111
Change-Id: If5a63c5da7e3c212acbec4e838d6542303e59481
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/62000
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ryan Harrison <rharrison@chromium.org>
Commit-Queue: Alastair Donaldson <afdx@google.com>
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn b/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn
index 70941ac..b6d205a 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn
+++ b/src/tint/fuzzers/tint_ast_fuzzer/BUILD.gn
@@ -50,10 +50,14 @@
       "mutation_finders/change_binary_operators.h",
       "mutation_finders/replace_identifiers.cc",
       "mutation_finders/replace_identifiers.h",
+      "mutation_finders/wrap_unary_operators.cc",
+      "mutation_finders/wrap_unary_operators.h",
       "mutations/change_binary_operator.cc",
       "mutations/change_binary_operator.h",
       "mutations/replace_identifier.cc",
       "mutations/replace_identifier.h",
+      "mutations/wrap_unary_operator.cc",
+      "mutations/wrap_unary_operator.h",
       "mutator.cc",
       "mutator.h",
       "node_id_map.cc",
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt b/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt
index a31d411..25dd3fc 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt
+++ b/src/tint/fuzzers/tint_ast_fuzzer/CMakeLists.txt
@@ -42,8 +42,10 @@
         mutation_finder.h
         mutation_finders/change_binary_operators.h
         mutation_finders/replace_identifiers.h
+        mutation_finders/wrap_unary_operators.h
         mutations/change_binary_operator.h
         mutations/replace_identifier.h
+        mutations/wrap_unary_operator.h
         mutator.h
         node_id_map.h
         probability_context.h
@@ -59,8 +61,10 @@
         mutation_finder.cc
         mutation_finders/change_binary_operators.cc
         mutation_finders/replace_identifiers.cc
+        mutation_finders/wrap_unary_operators.cc
         mutations/change_binary_operator.cc
         mutations/replace_identifier.cc
+        mutations/wrap_unary_operator.cc
         mutator.cc
         node_id_map.cc
         probability_context.cc
@@ -97,7 +101,8 @@
 if (${TINT_BUILD_TESTS})
     set(TEST_SOURCES
             mutations/change_binary_operator_test.cc
-            mutations/replace_identifier_test.cc)
+            mutations/replace_identifier_test.cc
+	    mutations/wrap_unary_operator_test.cc)
 
     add_executable(tint_ast_fuzzer_unittests ${TEST_SOURCES})
 
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutation.cc
index 1c4fb2e..c3c89a6 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/mutation.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation.cc
@@ -18,6 +18,7 @@
 
 #include "src/tint/fuzzers/tint_ast_fuzzer/mutations/change_binary_operator.h"
 #include "src/tint/fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h"
 
 namespace tint {
 namespace fuzzers {
@@ -34,6 +35,9 @@
     case protobufs::Mutation::kChangeBinaryOperator:
       return std::make_unique<MutationChangeBinaryOperator>(
           message.change_binary_operator());
+    case protobufs::Mutation::kWrapUnaryOperator:
+      return std::make_unique<MutationWrapUnaryOperator>(
+          message.wrap_unary_operator());
     case protobufs::Mutation::MUTATION_NOT_SET:
       assert(false && "Mutation is not set");
       break;
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.cc
new file mode 100644
index 0000000..da026f1
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.cc
@@ -0,0 +1,81 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.h"
+
+#include <memory>
+#include <vector>
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/util.h"
+#include "src/tint/sem/expression.h"
+#include "src/tint/sem/statement.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+
+MutationList MutationFinderWrapUnaryOperators::FindMutations(
+    const tint::Program& program,
+    NodeIdMap* node_id_map,
+    ProbabilityContext* probability_context) const {
+  MutationList result;
+
+  // Iterate through all ast nodes and for each expression node, try to wrap
+  // the inside a valid unary operator based on the type of the expression.
+  for (const auto* node : program.ASTNodes().Objects()) {
+    const auto* expr_ast_node = tint::As<ast::Expression>(node);
+
+    // Transformation applies only when the node represents a valid expression.
+    if (!expr_ast_node) {
+      continue;
+    }
+
+    const auto* expr_sem_node =
+        tint::As<sem::Expression>(program.Sem().Get(expr_ast_node));
+
+    // Transformation applies only when the semantic node for the given
+    // expression is present.
+    if (!expr_sem_node) {
+      continue;
+    }
+
+    std::vector<ast::UnaryOp> valid_operators =
+        MutationWrapUnaryOperator::GetValidUnaryWrapper(*expr_sem_node);
+
+    // Transformation only applies when there are available unary operators
+    // for the given expression.
+    if (valid_operators.empty()) {
+      continue;
+    }
+
+    ast::UnaryOp unary_op_wrapper =
+        valid_operators[probability_context->GetRandomIndex(valid_operators)];
+
+    result.push_back(std::make_unique<MutationWrapUnaryOperator>(
+        node_id_map->GetId(expr_ast_node), node_id_map->TakeFreshId(),
+        unary_op_wrapper));
+  }
+
+  return result;
+}
+
+uint32_t MutationFinderWrapUnaryOperators::GetChanceOfApplyingMutation(
+    ProbabilityContext* probability_context) const {
+  return probability_context->GetChanceOfWrappingUnaryOperators();
+}
+
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.h b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.h
new file mode 100644
index 0000000..02538fc
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.h
@@ -0,0 +1,43 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_WRAP_UNARY_OPERATORS_H_
+#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_WRAP_UNARY_OPERATORS_H_
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finder.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+
+/// Looks for opportunities to apply
+/// `MutationFinderWrapUnaryOperators`.
+///
+/// For each expression in the module, try to wrap it within
+/// a unary operator.
+class MutationFinderWrapUnaryOperators : public MutationFinder {
+ public:
+  MutationList FindMutations(
+      const tint::Program& program,
+      NodeIdMap* node_id_map,
+      ProbabilityContext* probability_context) const override;
+  uint32_t GetChanceOfApplyingMutation(
+      ProbabilityContext* probability_context) const override;
+};
+
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_WRAP_UNARY_OPERATORS_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.cc
new file mode 100644
index 0000000..1f5d5ae
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.cc
@@ -0,0 +1,127 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h"
+
+#include <utility>
+#include <vector>
+
+#include "src/tint/program_builder.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+
+MutationWrapUnaryOperator::MutationWrapUnaryOperator(
+    protobufs::MutationWrapUnaryOperator message)
+    : message_(std::move(message)) {}
+
+MutationWrapUnaryOperator::MutationWrapUnaryOperator(
+    uint32_t expression_id,
+    uint32_t fresh_id,
+    ast::UnaryOp unary_op_wrapper) {
+  message_.set_expression_id(expression_id);
+  message_.set_fresh_id(fresh_id);
+  message_.set_unary_op_wrapper(static_cast<uint32_t>(unary_op_wrapper));
+}
+
+bool MutationWrapUnaryOperator::IsApplicable(
+    const tint::Program& program,
+    const NodeIdMap& node_id_map) const {
+  // Check if id that will be assigned is fresh.
+  if (!node_id_map.IdIsFreshAndValid(message_.fresh_id())) {
+    return false;
+  }
+
+  const auto* expression_ast_node =
+      tint::As<ast::Expression>(node_id_map.GetNode(message_.expression_id()));
+
+  if (!expression_ast_node) {
+    // Either the node is not present with the given id or
+    // the node is not a valid expression type.
+    return false;
+  }
+
+  const auto* expression_sem_node =
+      tint::As<sem::Expression>(program.Sem().Get(expression_ast_node));
+
+  if (!expression_sem_node) {
+    // Semantic information for the expression ast node is not present
+    // or the semantic node is not a valid expression type node.
+    return false;
+  }
+
+  ast::UnaryOp unary_op_wrapper =
+      static_cast<ast::UnaryOp>(message_.unary_op_wrapper());
+
+  std::vector<ast::UnaryOp> valid_ops =
+      GetValidUnaryWrapper(*expression_sem_node);
+
+  // There is no available unary operator or |unary_op_wrapper| is a
+  // type that is not allowed for the given expression.
+  if (std::find(valid_ops.begin(), valid_ops.end(), unary_op_wrapper) ==
+      valid_ops.end()) {
+    return false;
+  }
+
+  return true;
+}
+
+void MutationWrapUnaryOperator::Apply(const NodeIdMap& node_id_map,
+                                      tint::CloneContext* clone_context,
+                                      NodeIdMap* new_node_id_map) const {
+  auto* expression_node =
+      tint::As<ast::Expression>(node_id_map.GetNode(message_.expression_id()));
+
+  auto* replacement_expression_node =
+      clone_context->dst->create<ast::UnaryOpExpression>(
+          static_cast<ast::UnaryOp>(message_.unary_op_wrapper()),
+          clone_context->Clone(expression_node));
+
+  clone_context->Replace(expression_node, replacement_expression_node);
+
+  new_node_id_map->Add(replacement_expression_node, message_.fresh_id());
+}
+
+protobufs::Mutation MutationWrapUnaryOperator::ToMessage() const {
+  protobufs::Mutation mutation;
+  *mutation.mutable_wrap_unary_operator() = message_;
+  return mutation;
+}
+
+std::vector<ast::UnaryOp> MutationWrapUnaryOperator::GetValidUnaryWrapper(
+    const sem::Expression& expr) {
+  const auto* expr_type = expr.Type();
+  if (expr_type->is_bool_scalar_or_vector()) {
+    return {ast::UnaryOp::kNot};
+  }
+
+  if (expr_type->is_signed_scalar_or_vector()) {
+    return {ast::UnaryOp::kNegation, ast::UnaryOp::kComplement};
+  }
+
+  if (expr_type->is_unsigned_scalar_or_vector()) {
+    return {ast::UnaryOp::kComplement};
+  }
+
+  if (expr_type->is_float_scalar_or_vector()) {
+    return {ast::UnaryOp::kNegation};
+  }
+
+  return {};
+}
+
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h
new file mode 100644
index 0000000..25fec00
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h
@@ -0,0 +1,85 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_WRAP_UNARY_OPERATOR_H_
+#define SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_WRAP_UNARY_OPERATOR_H_
+
+#include <vector>
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutation.h"
+
+#include "src/tint/sem/variable.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+
+/// @see MutationWrapUnaryOperator::Apply
+class MutationWrapUnaryOperator : public Mutation {
+ public:
+  /// @brief Constructs an instance of this mutation from a protobuf message.
+  /// @param message - protobuf message
+  explicit MutationWrapUnaryOperator(
+      protobufs::MutationWrapUnaryOperator message);
+
+  /// @brief Constructor.
+  /// @param expression_id - the id of an expression.
+  /// @param fresh_id - a fresh id for the created expression node with
+  /// unary operator wrapper.
+  /// @param unary_op_wrapper - a `ast::UnaryOp` instance.
+  MutationWrapUnaryOperator(uint32_t expression_id,
+                            uint32_t fresh_id,
+                            ast::UnaryOp unary_op_wrapper);
+
+  /// @copybrief Mutation::IsApplicable
+  ///
+  /// The mutation is applicable iff:
+  /// - `expression_id` must refer to a valid expression that can be wrapped
+  ///    with unary operator.
+  /// - `fresh_id` must be fresh.
+  /// - `unary_op_wrapper` is a unary expression that is valid based on the
+  ///   type of the given expression.
+  ///
+  /// @copydetails Mutation::IsApplicable
+  bool IsApplicable(const tint::Program& program,
+                    const NodeIdMap& node_id_map) const override;
+
+  /// @copybrief Mutation::Apply
+  ///
+  /// Wrap an expression in a unary operator that is valid based on
+  /// the type of the expression.
+  ///
+  /// @copydetails Mutation::Apply
+  void Apply(const NodeIdMap& node_id_map,
+             tint::CloneContext* clone_context,
+             NodeIdMap* new_node_id_map) const override;
+
+  protobufs::Mutation ToMessage() const override;
+
+  /// Return list of unary operator wrappers allowed for the given
+  /// expression.
+  /// @param expr - an `ast::Expression` instance from node id map.
+  /// @return a list of unary operators.
+  static std::vector<ast::UnaryOp> GetValidUnaryWrapper(
+      const sem::Expression& expr);
+
+ private:
+  protobufs::MutationWrapUnaryOperator message_;
+};
+
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
+
+#endif  // SRC_TINT_FUZZERS_TINT_AST_FUZZER_MUTATIONS_WRAP_UNARY_OPERATOR_H_
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator_test.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator_test.cc
new file mode 100644
index 0000000..4b8435e
--- /dev/null
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator_test.cc
@@ -0,0 +1,548 @@
+// Copyright 2021 The Tint Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string>
+
+#include "gtest/gtest.h"
+
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutations/wrap_unary_operator.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutator.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/probability_context.h"
+#include "src/tint/program_builder.h"
+#include "src/tint/reader/wgsl/parser.h"
+#include "src/tint/writer/wgsl/generator.h"
+
+namespace tint {
+namespace fuzzers {
+namespace ast_fuzzer {
+namespace {
+
+TEST(WrapUnaryOperatorTest, Applicable1) {
+  std::string content = R"(
+    fn main() {
+      var a = 5;
+      if (a < 5) {
+        a = 6;
+      }
+    }
+  )";
+  Source::File file("test.wgsl", content);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  const auto& main_fn_statements =
+      program.AST().Functions()[0]->body->statements;
+
+  auto expression_id = node_id_map.GetId(
+      main_fn_statements[1]->As<ast::IfStatement>()->condition);
+  ASSERT_NE(expression_id, 0);
+
+  ASSERT_TRUE(MaybeApplyMutation(
+      program,
+      MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
+                                ast::UnaryOp::kNot),
+      node_id_map, &program, &node_id_map, nullptr));
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  writer::wgsl::Options options;
+  auto result = writer::wgsl::Generate(&program, options);
+  ASSERT_TRUE(result.success) << result.error;
+
+  std::string expected_shader = R"(fn main() {
+  var a = 5;
+  if (!((a < 5))) {
+    a = 6;
+  }
+}
+)";
+  ASSERT_EQ(expected_shader, result.wgsl);
+}
+
+TEST(WrapUnaryOperatorTest, Applicable2) {
+  std::string content = R"(
+    fn main() {
+      let a = vec3<bool>(true, false, true);
+    }
+  )";
+  Source::File file("test.wgsl", content);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  const auto& main_fn_statements =
+      program.AST().Functions()[0]->body->statements;
+
+  const auto* expr = main_fn_statements[0]
+                         ->As<ast::VariableDeclStatement>()
+                         ->variable->constructor->As<ast::Expression>();
+
+  const auto expression_id = node_id_map.GetId(expr);
+  ASSERT_NE(expression_id, 0);
+
+  ASSERT_TRUE(MaybeApplyMutation(
+      program,
+      MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
+                                ast::UnaryOp::kNot),
+      node_id_map, &program, &node_id_map, nullptr));
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  writer::wgsl::Options options;
+  auto result = writer::wgsl::Generate(&program, options);
+  ASSERT_TRUE(result.success) << result.error;
+
+  std::string expected_shader = R"(fn main() {
+  let a = !(vec3<bool>(true, false, true));
+}
+)";
+  ASSERT_EQ(expected_shader, result.wgsl);
+}
+
+TEST(WrapUnaryOperatorTest, Applicable3) {
+  std::string content = R"(
+    fn main() {
+      var a : u32;
+      a = 6u;
+    }
+  )";
+  Source::File file("test.wgsl", content);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  const auto& main_fn_statements =
+      program.AST().Functions()[0]->body->statements;
+
+  const auto* expr = main_fn_statements[1]->As<ast::AssignmentStatement>()->rhs;
+
+  const auto expression_id = node_id_map.GetId(expr);
+  ASSERT_NE(expression_id, 0);
+
+  ASSERT_TRUE(MaybeApplyMutation(
+      program,
+      MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
+                                ast::UnaryOp::kComplement),
+      node_id_map, &program, &node_id_map, nullptr));
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  writer::wgsl::Options options;
+  auto result = writer::wgsl::Generate(&program, options);
+  ASSERT_TRUE(result.success) << result.error;
+
+  std::string expected_shader = R"(fn main() {
+  var a : u32;
+  a = ~(6u);
+}
+)";
+  ASSERT_EQ(expected_shader, result.wgsl);
+}
+
+TEST(WrapUnaryOperatorTest, Applicable4) {
+  std::string content = R"(
+    fn main() -> vec2<bool> {
+      var a = (vec2<u32> (1u, 2u) == vec2<u32> (1u, 2u));
+      return a;
+    }
+  )";
+  Source::File file("test.wgsl", content);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  const auto& main_fn_statements =
+      program.AST().Functions()[0]->body->statements;
+
+  const auto* expr = main_fn_statements[0]
+                         ->As<ast::VariableDeclStatement>()
+                         ->variable->constructor->As<ast::BinaryExpression>()
+                         ->lhs;
+
+  const auto expression_id = node_id_map.GetId(expr);
+  ASSERT_NE(expression_id, 0);
+
+  ASSERT_TRUE(MaybeApplyMutation(
+      program,
+      MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
+                                ast::UnaryOp::kComplement),
+      node_id_map, &program, &node_id_map, nullptr));
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  writer::wgsl::Options options;
+  auto result = writer::wgsl::Generate(&program, options);
+  ASSERT_TRUE(result.success) << result.error;
+
+  std::string expected_shader = R"(fn main() -> vec2<bool> {
+  var a = (~(vec2<u32>(1u, 2u)) == vec2<u32>(1u, 2u));
+  return a;
+}
+)";
+  ASSERT_EQ(expected_shader, result.wgsl);
+}
+
+TEST(WrapUnaryOperatorTest, Applicable5) {
+  std::string content = R"(
+    fn main() {
+      let a : f32 = -(1.0);
+    }
+  )";
+  Source::File file("test.wgsl", content);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  const auto& main_fn_statements =
+      program.AST().Functions()[0]->body->statements;
+
+  const auto* expr = main_fn_statements[0]
+                         ->As<ast::VariableDeclStatement>()
+                         ->variable->constructor->As<ast::UnaryOpExpression>()
+                         ->expr;
+
+  const auto expression_id = node_id_map.GetId(expr);
+  ASSERT_NE(expression_id, 0);
+
+  ASSERT_TRUE(MaybeApplyMutation(
+      program,
+      MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
+                                ast::UnaryOp::kNegation),
+      node_id_map, &program, &node_id_map, nullptr));
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  writer::wgsl::Options options;
+  auto result = writer::wgsl::Generate(&program, options);
+  ASSERT_TRUE(result.success) << result.error;
+
+  std::string expected_shader = R"(fn main() {
+  let a : f32 = -(-(1.0));
+}
+)";
+  ASSERT_EQ(expected_shader, result.wgsl);
+}
+
+TEST(WrapUnaryOperatorTest, Applicable6) {
+  std::string content = R"(
+    fn main() {
+      var a : vec4<f32> = vec4<f32>(-1.0, -1.0, -1.0, -1.0);
+    }
+  )";
+  Source::File file("test.wgsl", content);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  const auto& main_fn_statements =
+      program.AST().Functions()[0]->body->statements;
+
+  const auto* expr = main_fn_statements[0]
+                         ->As<ast::VariableDeclStatement>()
+                         ->variable->constructor->As<ast::Expression>();
+
+  const auto expression_id = node_id_map.GetId(expr);
+  ASSERT_NE(expression_id, 0);
+
+  ASSERT_TRUE(MaybeApplyMutation(
+      program,
+      MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
+                                ast::UnaryOp::kNegation),
+      node_id_map, &program, &node_id_map, nullptr));
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  writer::wgsl::Options options;
+  auto result = writer::wgsl::Generate(&program, options);
+  ASSERT_TRUE(result.success) << result.error;
+
+  std::string expected_shader = R"(fn main() {
+  var a : vec4<f32> = -(vec4<f32>(-1.0, -1.0, -1.0, -1.0));
+}
+)";
+  ASSERT_EQ(expected_shader, result.wgsl);
+}
+
+TEST(WrapUnaryOperatorTest, Applicable7) {
+  std::string content = R"(
+    fn main() {
+      var a = 1;
+      for(var i : i32 = 1; i < 5; i = i + 1) {
+        a = a + 1;
+      }
+    }
+  )";
+  Source::File file("test.wgsl", content);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  const auto& main_fn_statements =
+      program.AST().Functions()[0]->body->statements;
+
+  const auto* expr = main_fn_statements[1]
+                         ->As<ast::ForLoopStatement>()
+                         ->initializer->As<ast::VariableDeclStatement>()
+                         ->variable->constructor->As<ast::Expression>();
+
+  const auto expression_id = node_id_map.GetId(expr);
+  ASSERT_NE(expression_id, 0);
+
+  ASSERT_TRUE(MaybeApplyMutation(
+      program,
+      MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
+                                ast::UnaryOp::kNegation),
+      node_id_map, &program, &node_id_map, nullptr));
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  writer::wgsl::Options options;
+  auto result = writer::wgsl::Generate(&program, options);
+  ASSERT_TRUE(result.success) << result.error;
+
+  std::string expected_shader = R"(fn main() {
+  var a = 1;
+  for(var i : i32 = -(1); (i < 5); i = (i + 1)) {
+    a = (a + 1);
+  }
+}
+)";
+  ASSERT_EQ(expected_shader, result.wgsl);
+}
+
+TEST(WrapUnaryOperatorTest, Applicable8) {
+  std::string content = R"(
+    fn main() {
+      var a : vec4<i32> = vec4<i32>(1, 0, -1, 0);
+    }
+  )";
+  Source::File file("test.wgsl", content);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  const auto& main_fn_statements =
+      program.AST().Functions()[0]->body->statements;
+
+  const auto* expr = main_fn_statements[0]
+                         ->As<ast::VariableDeclStatement>()
+                         ->variable->constructor->As<ast::Expression>();
+
+  const auto expression_id = node_id_map.GetId(expr);
+  ASSERT_NE(expression_id, 0);
+
+  ASSERT_TRUE(MaybeApplyMutation(
+      program,
+      MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
+                                ast::UnaryOp::kComplement),
+      node_id_map, &program, &node_id_map, nullptr));
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  writer::wgsl::Options options;
+  auto result = writer::wgsl::Generate(&program, options);
+  ASSERT_TRUE(result.success) << result.error;
+
+  std::string expected_shader = R"(fn main() {
+  var a : vec4<i32> = ~(vec4<i32>(1, 0, -1, 0));
+}
+)";
+  ASSERT_EQ(expected_shader, result.wgsl);
+}
+
+TEST(WrapUnaryOperatorTest, NotApplicable1) {
+  std::string content = R"(
+    fn main() {
+      let a = mat2x3<f32>(vec3<f32>(1.,0.,1.), vec3<f32>(0.,1.,0.));
+    }
+  )";
+  Source::File file("test.wgsl", content);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  const auto& main_fn_statements =
+      program.AST().Functions()[0]->body->statements;
+
+  const auto* expr = main_fn_statements[0]
+                         ->As<ast::VariableDeclStatement>()
+                         ->variable->constructor->As<ast::Expression>();
+
+  const auto expression_id = node_id_map.GetId(expr);
+  ASSERT_NE(expression_id, 0);
+
+  // There is no unary operator that can be applied to matrix type.
+  ASSERT_FALSE(MaybeApplyMutation(
+      program,
+      MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
+                                ast::UnaryOp::kNegation),
+      node_id_map, &program, &node_id_map, nullptr));
+}
+
+TEST(WrapUnaryOperatorTest, NotApplicable2) {
+  std::string content = R"(
+    fn main() {
+      let a = 1;
+    }
+  )";
+  Source::File file("test.wgsl", content);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  const auto& main_fn_statements =
+      program.AST().Functions()[0]->body->statements;
+
+  const auto* expr = main_fn_statements[0]
+                         ->As<ast::VariableDeclStatement>()
+                         ->variable->constructor->As<ast::Expression>();
+
+  const auto expression_id = node_id_map.GetId(expr);
+  ASSERT_NE(expression_id, 0);
+
+  // Not cannot be applied to integer types.
+  ASSERT_FALSE(MaybeApplyMutation(
+      program,
+      MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
+                                ast::UnaryOp::kNot),
+      node_id_map, &program, &node_id_map, nullptr));
+}
+
+TEST(WrapUnaryOperatorTest, NotApplicable3) {
+  std::string content = R"(
+    fn main() {
+      let a = vec2<u32>(1u, 2u);
+    }
+  )";
+  Source::File file("test.wgsl", content);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  const auto& main_fn_statements =
+      program.AST().Functions()[0]->body->statements;
+
+  const auto* expr = main_fn_statements[0]
+                         ->As<ast::VariableDeclStatement>()
+                         ->variable->constructor->As<ast::Expression>();
+
+  const auto expression_id = node_id_map.GetId(expr);
+  ASSERT_NE(expression_id, 0);
+
+  // Negation cannot be applied to unsigned integer scalar or vectors.
+  ASSERT_FALSE(MaybeApplyMutation(
+      program,
+      MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
+                                ast::UnaryOp::kNegation),
+      node_id_map, &program, &node_id_map, nullptr));
+}
+
+TEST(WrapUnaryOperatorTest, NotApplicable4) {
+  std::string content = R"(
+    fn main() {
+      let a = 1.5;
+    }
+  )";
+  Source::File file("test.wgsl", content);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  const auto& main_fn_statements =
+      program.AST().Functions()[0]->body->statements;
+
+  const auto* expr = main_fn_statements[0]
+                         ->As<ast::VariableDeclStatement>()
+                         ->variable->constructor->As<ast::Expression>();
+
+  const auto expression_id = node_id_map.GetId(expr);
+  ASSERT_NE(expression_id, 0);
+
+  // Cannot wrap float types with complement operator.
+  ASSERT_FALSE(MaybeApplyMutation(
+      program,
+      MutationWrapUnaryOperator(expression_id, node_id_map.TakeFreshId(),
+                                ast::UnaryOp::kComplement),
+      node_id_map, &program, &node_id_map, nullptr));
+}
+
+TEST(WrapUnaryOperatorTest, NotApplicable5) {
+  std::string content = R"(
+    fn main() {
+      let a = 1.5;
+    }
+  )";
+  Source::File file("test.wgsl", content);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  const auto& main_fn_statements =
+      program.AST().Functions()[0]->body->statements;
+
+  const auto* expr = main_fn_statements[0]
+                         ->As<ast::VariableDeclStatement>()
+                         ->variable->constructor->As<ast::Expression>();
+
+  const auto expression_id = node_id_map.GetId(expr);
+  ASSERT_NE(expression_id, 0);
+
+  // Id for the replacement expression is not fresh.
+  ASSERT_FALSE(
+      MaybeApplyMutation(program,
+                         MutationWrapUnaryOperator(expression_id, expression_id,
+                                                   ast::UnaryOp::kNegation),
+                         node_id_map, &program, &node_id_map, nullptr));
+}
+
+TEST(WrapUnaryOperatorTest, NotApplicable6) {
+  std::string content = R"(
+    fn main() {
+      let a = 1.5;
+    }
+  )";
+  Source::File file("test.wgsl", content);
+  auto program = reader::wgsl::Parse(&file);
+  ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
+
+  NodeIdMap node_id_map(program);
+
+  const auto& main_fn_statements =
+      program.AST().Functions()[0]->body->statements;
+
+  const auto* statement =
+      main_fn_statements[0]->As<ast::VariableDeclStatement>();
+
+  const auto statement_id = node_id_map.GetId(statement);
+  ASSERT_NE(statement_id, 0);
+
+  // The id provided for the expression is not a valid expression type.
+  ASSERT_FALSE(MaybeApplyMutation(
+      program,
+      MutationWrapUnaryOperator(statement_id, node_id_map.TakeFreshId(),
+                                ast::UnaryOp::kNegation),
+      node_id_map, &program, &node_id_map, nullptr));
+}
+
+}  // namespace
+}  // namespace ast_fuzzer
+}  // namespace fuzzers
+}  // namespace tint
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc b/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc
index 63d62e2..ab73a3a 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/mutator.cc
@@ -22,8 +22,8 @@
 
 #include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/change_binary_operators.h"
 #include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.h"
+#include "src/tint/fuzzers/tint_ast_fuzzer/mutation_finders/wrap_unary_operators.h"
 #include "src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h"
-
 #include "src/tint/program_builder.h"
 
 namespace tint {
@@ -50,6 +50,8 @@
         enable_all_mutations, probability_context, &result);
     MaybeAddFinder<MutationFinderReplaceIdentifiers>(
         enable_all_mutations, probability_context, &result);
+    MaybeAddFinder<MutationFinderWrapUnaryOperators>(
+        enable_all_mutations, probability_context, &result);
   } while (result.empty());
   return result;
 }
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.cc b/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.cc
index 543a57b..117fd2d 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.cc
@@ -51,7 +51,7 @@
   }
 }
 
-bool NodeIdMap::IdIsFreshAndValid(IdType id) {
+bool NodeIdMap::IdIsFreshAndValid(IdType id) const {
   return id && !id_to_node_.count(id);
 }
 
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h b/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h
index 18dd96a..1aae93f 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h
+++ b/src/tint/fuzzers/tint_ast_fuzzer/node_id_map.h
@@ -72,7 +72,7 @@
   /// @param id - an id that is used to check in the map.
   /// @return true the given id is fresh and valid (non-zero).
   /// @return false otherwise.
-  bool IdIsFreshAndValid(IdType id);
+  bool IdIsFreshAndValid(IdType id) const;
 
   /// @brief Returns an id that is guaranteed to be unoccupied in this map.
   ///
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc
index f41f5b9..1d9461f 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.cc
@@ -23,6 +23,7 @@
 
 const std::pair<uint32_t, uint32_t> kChanceOfChangingBinaryOperators = {30, 90};
 const std::pair<uint32_t, uint32_t> kChanceOfReplacingIdentifiers = {30, 70};
+const std::pair<uint32_t, uint32_t> kChanceOfWrappingUnaryOperators = {30, 70};
 
 }  // namespace
 
@@ -31,7 +32,9 @@
       chance_of_changing_binary_operators_(
           RandomFromRange(kChanceOfChangingBinaryOperators)),
       chance_of_replacing_identifiers_(
-          RandomFromRange(kChanceOfReplacingIdentifiers)) {
+          RandomFromRange(kChanceOfReplacingIdentifiers)),
+      chance_of_wrapping_unary_operators_(
+          RandomFromRange(kChanceOfWrappingUnaryOperators)) {
   assert(generator != nullptr && "generator must not be nullptr");
 }
 
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/probability_context.h b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.h
index b890aba..eacc1bd 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/probability_context.h
+++ b/src/tint/fuzzers/tint_ast_fuzzer/probability_context.h
@@ -65,6 +65,11 @@
     return chance_of_replacing_identifiers_;
   }
 
+  /// @return the probability of wrapping an expression in a unary operator.
+  uint32_t GetChanceOfWrappingUnaryOperators() const {
+    return chance_of_wrapping_unary_operators_;
+  }
+
  private:
   /// @param range - a pair of integers `a` and `b` s.t. `a <= b`.
   /// @return an random number in the range `[a; b]`.
@@ -74,6 +79,7 @@
 
   uint32_t chance_of_changing_binary_operators_;
   uint32_t chance_of_replacing_identifiers_;
+  uint32_t chance_of_wrapping_unary_operators_;
 };
 
 }  // namespace ast_fuzzer
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.proto b/src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.proto
index e60e35d..0a5951f 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.proto
+++ b/src/tint/fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.proto
@@ -20,6 +20,7 @@
   oneof mutation {
     MutationReplaceIdentifier replace_identifier = 1;
     MutationChangeBinaryOperator change_binary_operator = 2;
+    MutationWrapUnaryOperator wrap_unary_operator = 3;
   };
 }
 
@@ -59,3 +60,17 @@
   // A BinaryOp representing the new binary operator.
   uint32 new_operator = 2;
 }
+
+message MutationWrapUnaryOperator {
+  // This transformation wraps an expression with a allowed unary
+  // expression operator.
+
+  // The id of the expression.
+  uint32 expression_id = 1;
+
+  // A fresh id for the created unary expression.
+  uint32 fresh_id = 2;
+
+  // The unary operator to wrap the expression with.
+  uint32 unary_op_wrapper = 3;
+}