//===- UncheckedOptionalAccessModelTest.cpp -------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
// FIXME: Move this to clang/unittests/Analysis/FlowSensitive/Models.

#include "clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h"
#include "TestingSupport.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Analysis/FlowSensitive/TypeErasedDataflowAnalysis.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Error.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include <optional>
#include <string>
#include <utility>
#include <vector>

using namespace clang;
using namespace dataflow;
using namespace test;

using ::testing::ContainerEq;

// FIXME: Move header definitions in separate file(s).
static constexpr char CSDtdDefHeader[] = R"(
#ifndef CSTDDEF_H
#define CSTDDEF_H

namespace std {

typedef decltype(sizeof(char)) size_t;

using nullptr_t = decltype(nullptr);

} // namespace std

#endif // CSTDDEF_H
)";

static constexpr char StdTypeTraitsHeader[] = R"(
#ifndef STD_TYPE_TRAITS_H
#define STD_TYPE_TRAITS_H

#include "cstddef.h"

namespace std {

template <typename T, T V>
struct integral_constant {
  static constexpr T value = V;
};

using true_type = integral_constant<bool, true>;
using false_type = integral_constant<bool, false>;

template< class T > struct remove_reference      {typedef T type;};
template< class T > struct remove_reference<T&>  {typedef T type;};
template< class T > struct remove_reference<T&&> {typedef T type;};

template <class T>
  using remove_reference_t = typename remove_reference<T>::type;

template <class T>
struct remove_extent {
  typedef T type;
};

template <class T>
struct remove_extent<T[]> {
  typedef T type;
};

template <class T, size_t N>
struct remove_extent<T[N]> {
  typedef T type;
};

template <class T>
struct is_array : false_type {};

template <class T>
struct is_array<T[]> : true_type {};

template <class T, size_t N>
struct is_array<T[N]> : true_type {};

template <class>
struct is_function : false_type {};

template <class Ret, class... Args>
struct is_function<Ret(Args...)> : true_type {};

namespace detail {

template <class T>
struct type_identity {
  using type = T;
};  // or use type_identity (since C++20)

template <class T>
auto try_add_pointer(int) -> type_identity<typename remove_reference<T>::type*>;
template <class T>
auto try_add_pointer(...) -> type_identity<T>;

}  // namespace detail

template <class T>
struct add_pointer : decltype(detail::try_add_pointer<T>(0)) {};

template <bool B, class T, class F>
struct conditional {
  typedef T type;
};

template <class T, class F>
struct conditional<false, T, F> {
  typedef F type;
};

template <class T>
struct remove_cv {
  typedef T type;
};
template <class T>
struct remove_cv<const T> {
  typedef T type;
};
template <class T>
struct remove_cv<volatile T> {
  typedef T type;
};
template <class T>
struct remove_cv<const volatile T> {
  typedef T type;
};

template <class T>
using remove_cv_t = typename remove_cv<T>::type;

template <class T>
struct decay {
 private:
  typedef typename remove_reference<T>::type U;

 public:
  typedef typename conditional<
      is_array<U>::value, typename remove_extent<U>::type*,
      typename conditional<is_function<U>::value, typename add_pointer<U>::type,
                           typename remove_cv<U>::type>::type>::type type;
};

template <bool B, class T = void>
struct enable_if {};

template <class T>
struct enable_if<true, T> {
  typedef T type;
};

template <bool B, class T = void>
using enable_if_t = typename enable_if<B, T>::type;

template <class T, class U>
struct is_same : false_type {};

template <class T>
struct is_same<T, T> : true_type {};

template <class T>
struct is_void : is_same<void, typename remove_cv<T>::type> {};

namespace detail {

template <class T>
auto try_add_lvalue_reference(int) -> type_identity<T&>;
template <class T>
auto try_add_lvalue_reference(...) -> type_identity<T>;

template <class T>
auto try_add_rvalue_reference(int) -> type_identity<T&&>;
template <class T>
auto try_add_rvalue_reference(...) -> type_identity<T>;

}  // namespace detail

template <class T>
struct add_lvalue_reference : decltype(detail::try_add_lvalue_reference<T>(0)) {
};

template <class T>
struct add_rvalue_reference : decltype(detail::try_add_rvalue_reference<T>(0)) {
};

template <class T>
typename add_rvalue_reference<T>::type declval() noexcept;

namespace detail {

template <class T>
auto test_returnable(int)
    -> decltype(void(static_cast<T (*)()>(nullptr)), true_type{});
template <class>
auto test_returnable(...) -> false_type;

template <class From, class To>
auto test_implicitly_convertible(int)
    -> decltype(void(declval<void (&)(To)>()(declval<From>())), true_type{});
template <class, class>
auto test_implicitly_convertible(...) -> false_type;

}  // namespace detail

template <class From, class To>
struct is_convertible
    : integral_constant<bool,
                        (decltype(detail::test_returnable<To>(0))::value &&
                         decltype(detail::test_implicitly_convertible<From, To>(
                             0))::value) ||
                            (is_void<From>::value && is_void<To>::value)> {};

template <class From, class To>
inline constexpr bool is_convertible_v = is_convertible<From, To>::value;

template <class...>
using void_t = void;

template <class, class T, class... Args>
struct is_constructible_ : false_type {};

template <class T, class... Args>
struct is_constructible_<void_t<decltype(T(declval<Args>()...))>, T, Args...>
    : true_type {};

template <class T, class... Args>
using is_constructible = is_constructible_<void_t<>, T, Args...>;

template <class T, class... Args>
inline constexpr bool is_constructible_v = is_constructible<T, Args...>::value;

template <class _Tp>
struct __uncvref {
  typedef typename remove_cv<typename remove_reference<_Tp>::type>::type type;
};

template <class _Tp>
using __uncvref_t = typename __uncvref<_Tp>::type;

template <bool _Val>
using _BoolConstant = integral_constant<bool, _Val>;

template <class _Tp, class _Up>
using _IsSame = _BoolConstant<__is_same(_Tp, _Up)>;

template <class _Tp, class _Up>
using _IsNotSame = _BoolConstant<!__is_same(_Tp, _Up)>;

template <bool>
struct _MetaBase;
template <>
struct _MetaBase<true> {
  template <class _Tp, class _Up>
  using _SelectImpl = _Tp;
  template <template <class...> class _FirstFn, template <class...> class,
            class... _Args>
  using _SelectApplyImpl = _FirstFn<_Args...>;
  template <class _First, class...>
  using _FirstImpl = _First;
  template <class, class _Second, class...>
  using _SecondImpl = _Second;
  template <class _Result, class _First, class... _Rest>
  using _OrImpl =
      typename _MetaBase<_First::value != true && sizeof...(_Rest) != 0>::
          template _OrImpl<_First, _Rest...>;
};

template <>
struct _MetaBase<false> {
  template <class _Tp, class _Up>
  using _SelectImpl = _Up;
  template <template <class...> class, template <class...> class _SecondFn,
            class... _Args>
  using _SelectApplyImpl = _SecondFn<_Args...>;
  template <class _Result, class...>
  using _OrImpl = _Result;
};

template <bool _Cond, class _IfRes, class _ElseRes>
using _If = typename _MetaBase<_Cond>::template _SelectImpl<_IfRes, _ElseRes>;

template <class... _Rest>
using _Or = typename _MetaBase<sizeof...(_Rest) !=
                               0>::template _OrImpl<false_type, _Rest...>;

template <bool _Bp, class _Tp = void>
using __enable_if_t = typename enable_if<_Bp, _Tp>::type;

template <class...>
using __expand_to_true = true_type;
template <class... _Pred>
__expand_to_true<__enable_if_t<_Pred::value>...> __and_helper(int);
template <class...>
false_type __and_helper(...);
template <class... _Pred>
using _And = decltype(__and_helper<_Pred...>(0));

template <class _Pred>
struct _Not : _BoolConstant<!_Pred::value> {};

struct __check_tuple_constructor_fail {
  static constexpr bool __enable_explicit_default() { return false; }
  static constexpr bool __enable_implicit_default() { return false; }
  template <class...>
  static constexpr bool __enable_explicit() {
    return false;
  }
  template <class...>
  static constexpr bool __enable_implicit() {
    return false;
  }
};

template <typename, typename _Tp>
struct __select_2nd {
  typedef _Tp type;
};
template <class _Tp, class _Arg>
typename __select_2nd<decltype((declval<_Tp>() = declval<_Arg>())),
                      true_type>::type
__is_assignable_test(int);
template <class, class>
false_type __is_assignable_test(...);
template <class _Tp, class _Arg,
          bool = is_void<_Tp>::value || is_void<_Arg>::value>
struct __is_assignable_imp
    : public decltype((__is_assignable_test<_Tp, _Arg>(0))) {};
template <class _Tp, class _Arg>
struct __is_assignable_imp<_Tp, _Arg, true> : public false_type {};
template <class _Tp, class _Arg>
struct is_assignable : public __is_assignable_imp<_Tp, _Arg> {};

template <class _Tp>
struct __libcpp_is_integral : public false_type {};
template <>
struct __libcpp_is_integral<bool> : public true_type {};
template <>
struct __libcpp_is_integral<char> : public true_type {};
template <>
struct __libcpp_is_integral<signed char> : public true_type {};
template <>
struct __libcpp_is_integral<unsigned char> : public true_type {};
template <>
struct __libcpp_is_integral<wchar_t> : public true_type {};
template <>
struct __libcpp_is_integral<short> : public true_type {};  // NOLINT
template <>
struct __libcpp_is_integral<unsigned short> : public true_type {};  // NOLINT
template <>
struct __libcpp_is_integral<int> : public true_type {};
template <>
struct __libcpp_is_integral<unsigned int> : public true_type {};
template <>
struct __libcpp_is_integral<long> : public true_type {};  // NOLINT
template <>
struct __libcpp_is_integral<unsigned long> : public true_type {};  // NOLINT
template <>
struct __libcpp_is_integral<long long> : public true_type {};  // NOLINT
template <>                                                    // NOLINTNEXTLINE
struct __libcpp_is_integral<unsigned long long> : public true_type {};
template <class _Tp>
struct is_integral
    : public __libcpp_is_integral<typename remove_cv<_Tp>::type> {};

template <class _Tp>
struct __libcpp_is_floating_point : public false_type {};
template <>
struct __libcpp_is_floating_point<float> : public true_type {};
template <>
struct __libcpp_is_floating_point<double> : public true_type {};
template <>
struct __libcpp_is_floating_point<long double> : public true_type {};
template <class _Tp>
struct is_floating_point
    : public __libcpp_is_floating_point<typename remove_cv<_Tp>::type> {};

template <class _Tp>
struct is_arithmetic
    : public integral_constant<bool, is_integral<_Tp>::value ||
                                         is_floating_point<_Tp>::value> {};

template <class _Tp>
struct __libcpp_is_pointer : public false_type {};
template <class _Tp>
struct __libcpp_is_pointer<_Tp*> : public true_type {};
template <class _Tp>
struct is_pointer : public __libcpp_is_pointer<typename remove_cv<_Tp>::type> {
};

template <class _Tp>
struct __libcpp_is_member_pointer : public false_type {};
template <class _Tp, class _Up>
struct __libcpp_is_member_pointer<_Tp _Up::*> : public true_type {};
template <class _Tp>
struct is_member_pointer
    : public __libcpp_is_member_pointer<typename remove_cv<_Tp>::type> {};

template <class _Tp>
struct __libcpp_union : public false_type {};
template <class _Tp>
struct is_union : public __libcpp_union<typename remove_cv<_Tp>::type> {};

template <class T>
struct is_reference : false_type {};
template <class T>
struct is_reference<T&> : true_type {};
template <class T>
struct is_reference<T&&> : true_type {};

template <class T>
inline constexpr bool is_reference_v = is_reference<T>::value;

struct __two {
  char __lx[2];
};

namespace __is_class_imp {
template <class _Tp>
char __test(int _Tp::*);
template <class _Tp>
__two __test(...);
}  // namespace __is_class_imp
template <class _Tp>
struct is_class
    : public integral_constant<bool,
                               sizeof(__is_class_imp::__test<_Tp>(0)) == 1 &&
                                   !is_union<_Tp>::value> {};

template <class _Tp>
struct __is_nullptr_t_impl : public false_type {};
template <>
struct __is_nullptr_t_impl<nullptr_t> : public true_type {};
template <class _Tp>
struct __is_nullptr_t
    : public __is_nullptr_t_impl<typename remove_cv<_Tp>::type> {};
template <class _Tp>
struct is_null_pointer
    : public __is_nullptr_t_impl<typename remove_cv<_Tp>::type> {};

template <class _Tp>
struct is_enum
    : public integral_constant<
          bool, !is_void<_Tp>::value && !is_integral<_Tp>::value &&
                    !is_floating_point<_Tp>::value && !is_array<_Tp>::value &&
                    !is_pointer<_Tp>::value && !is_reference<_Tp>::value &&
                    !is_member_pointer<_Tp>::value && !is_union<_Tp>::value &&
                    !is_class<_Tp>::value && !is_function<_Tp>::value> {};

template <class _Tp>
struct is_scalar
    : public integral_constant<
          bool, is_arithmetic<_Tp>::value || is_member_pointer<_Tp>::value ||
                    is_pointer<_Tp>::value || __is_nullptr_t<_Tp>::value ||
                    is_enum<_Tp>::value> {};
template <>
struct is_scalar<nullptr_t> : public true_type {};

} // namespace std

#endif // STD_TYPE_TRAITS_H
)";

static constexpr char AbslTypeTraitsHeader[] = R"(
#ifndef ABSL_TYPE_TRAITS_H
#define ABSL_TYPE_TRAITS_H

#include "std_type_traits.h"

namespace absl {

template <typename... Ts>
struct conjunction : std::true_type {};

template <typename T, typename... Ts>
struct conjunction<T, Ts...>
    : std::conditional<T::value, conjunction<Ts...>, T>::type {};

template <typename T>
struct conjunction<T> : T {};

template <typename T>
struct negation : std::integral_constant<bool, !T::value> {};

template <bool B, typename T = void>
using enable_if_t = typename std::enable_if<B, T>::type;

} // namespace absl

#endif // ABSL_TYPE_TRAITS_H
)";

static constexpr char StdStringHeader[] = R"(
#ifndef STRING_H
#define STRING_H

namespace std {

struct string {
  string(const char*);
  ~string();
  bool empty();
};
bool operator!=(const string &LHS, const char *RHS);

} // namespace std

#endif // STRING_H
)";

static constexpr char StdUtilityHeader[] = R"(
#ifndef UTILITY_H
#define UTILITY_H

#include "std_type_traits.h"

namespace std {

template <typename T>
constexpr remove_reference_t<T>&& move(T&& x);

template <typename T>
void swap(T& a, T& b) noexcept;

} // namespace std

#endif // UTILITY_H
)";

static constexpr char StdInitializerListHeader[] = R"(
#ifndef INITIALIZER_LIST_H
#define INITIALIZER_LIST_H

namespace std {

template <typename T>
class initializer_list {
 public:
  initializer_list() noexcept;
};

} // namespace std

#endif // INITIALIZER_LIST_H
)";

static constexpr char StdOptionalHeader[] = R"(
#include "std_initializer_list.h"
#include "std_type_traits.h"
#include "std_utility.h"

namespace std {

struct in_place_t {};
constexpr in_place_t in_place;

struct nullopt_t {
  constexpr explicit nullopt_t() {}
};
constexpr nullopt_t nullopt;

template <class _Tp>
struct __optional_destruct_base {
  constexpr void reset() noexcept;
};

template <class _Tp>
struct __optional_storage_base : __optional_destruct_base<_Tp> {
  constexpr bool has_value() const noexcept;
};

template <typename _Tp>
class optional : private __optional_storage_base<_Tp> {
  using __base = __optional_storage_base<_Tp>;

 public:
  using value_type = _Tp;

 private:
  struct _CheckOptionalArgsConstructor {
    template <class _Up>
    static constexpr bool __enable_implicit() {
      return is_constructible_v<_Tp, _Up&&> && is_convertible_v<_Up&&, _Tp>;
    }

    template <class _Up>
    static constexpr bool __enable_explicit() {
      return is_constructible_v<_Tp, _Up&&> && !is_convertible_v<_Up&&, _Tp>;
    }
  };
  template <class _Up>
  using _CheckOptionalArgsCtor =
      _If<_IsNotSame<__uncvref_t<_Up>, in_place_t>::value &&
              _IsNotSame<__uncvref_t<_Up>, optional>::value,
          _CheckOptionalArgsConstructor, __check_tuple_constructor_fail>;
  template <class _QualUp>
  struct _CheckOptionalLikeConstructor {
    template <class _Up, class _Opt = optional<_Up>>
    using __check_constructible_from_opt =
        _Or<is_constructible<_Tp, _Opt&>, is_constructible<_Tp, _Opt const&>,
            is_constructible<_Tp, _Opt&&>, is_constructible<_Tp, _Opt const&&>,
            is_convertible<_Opt&, _Tp>, is_convertible<_Opt const&, _Tp>,
            is_convertible<_Opt&&, _Tp>, is_convertible<_Opt const&&, _Tp>>;
    template <class _Up, class _QUp = _QualUp>
    static constexpr bool __enable_implicit() {
      return is_convertible<_QUp, _Tp>::value &&
             !__check_constructible_from_opt<_Up>::value;
    }
    template <class _Up, class _QUp = _QualUp>
    static constexpr bool __enable_explicit() {
      return !is_convertible<_QUp, _Tp>::value &&
             !__check_constructible_from_opt<_Up>::value;
    }
  };

  template <class _Up, class _QualUp>
  using _CheckOptionalLikeCtor =
      _If<_And<_IsNotSame<_Up, _Tp>, is_constructible<_Tp, _QualUp>>::value,
          _CheckOptionalLikeConstructor<_QualUp>,
          __check_tuple_constructor_fail>;


  template <class _Up, class _QualUp>
  using _CheckOptionalLikeAssign = _If<
      _And<
          _IsNotSame<_Up, _Tp>,
          is_constructible<_Tp, _QualUp>,
          is_assignable<_Tp&, _QualUp>
      >::value,
      _CheckOptionalLikeConstructor<_QualUp>,
      __check_tuple_constructor_fail
    >;

 public:
  constexpr optional() noexcept {}
  constexpr optional(const optional&) = default;
  constexpr optional(optional&&) = default;
  constexpr optional(nullopt_t) noexcept {}

  template <
      class _InPlaceT, class... _Args,
      class = enable_if_t<_And<_IsSame<_InPlaceT, in_place_t>,
                             is_constructible<value_type, _Args...>>::value>>
  constexpr explicit optional(_InPlaceT, _Args&&... __args);

  template <class _Up, class... _Args,
            class = enable_if_t<is_constructible_v<
                value_type, initializer_list<_Up>&, _Args...>>>
  constexpr explicit optional(in_place_t, initializer_list<_Up> __il,
                              _Args&&... __args);

  template <
      class _Up = value_type,
      enable_if_t<_CheckOptionalArgsCtor<_Up>::template __enable_implicit<_Up>(),
                int> = 0>
  constexpr optional(_Up&& __v);

  template <
      class _Up,
      enable_if_t<_CheckOptionalArgsCtor<_Up>::template __enable_explicit<_Up>(),
                int> = 0>
  constexpr explicit optional(_Up&& __v);

  template <class _Up, enable_if_t<_CheckOptionalLikeCtor<_Up, _Up const&>::
                                     template __enable_implicit<_Up>(),
                                 int> = 0>
  constexpr optional(const optional<_Up>& __v);

  template <class _Up, enable_if_t<_CheckOptionalLikeCtor<_Up, _Up const&>::
                                     template __enable_explicit<_Up>(),
                                 int> = 0>
  constexpr explicit optional(const optional<_Up>& __v);

  template <class _Up, enable_if_t<_CheckOptionalLikeCtor<_Up, _Up&&>::
                                     template __enable_implicit<_Up>(),
                                 int> = 0>
  constexpr optional(optional<_Up>&& __v);

  template <class _Up, enable_if_t<_CheckOptionalLikeCtor<_Up, _Up&&>::
                                     template __enable_explicit<_Up>(),
                                 int> = 0>
  constexpr explicit optional(optional<_Up>&& __v);

  constexpr optional& operator=(nullopt_t) noexcept;

  optional& operator=(const optional&);

  optional& operator=(optional&&);

  template <class _Up = value_type,
            class = enable_if_t<_And<_IsNotSame<__uncvref_t<_Up>, optional>,
                                   _Or<_IsNotSame<__uncvref_t<_Up>, value_type>,
                                       _Not<is_scalar<value_type>>>,
                                   is_constructible<value_type, _Up>,
                                   is_assignable<value_type&, _Up>>::value>>
  constexpr optional& operator=(_Up&& __v);

  template <class _Up, enable_if_t<_CheckOptionalLikeAssign<_Up, _Up const&>::
                                     template __enable_assign<_Up>(),
                                 int> = 0>
  constexpr optional& operator=(const optional<_Up>& __v);

  template <class _Up, enable_if_t<_CheckOptionalLikeCtor<_Up, _Up&&>::
                                     template __enable_assign<_Up>(),
                                 int> = 0>
  constexpr optional& operator=(optional<_Up>&& __v);

  const _Tp& operator*() const&;
  _Tp& operator*() &;
  const _Tp&& operator*() const&&;
  _Tp&& operator*() &&;

  const _Tp* operator->() const;
  _Tp* operator->();

  const _Tp& value() const&;
  _Tp& value() &;
  const _Tp&& value() const&&;
  _Tp&& value() &&;

  template <typename U>
  constexpr _Tp value_or(U&& v) const&;
  template <typename U>
  _Tp value_or(U&& v) &&;

  template <typename... Args>
  _Tp& emplace(Args&&... args);

  template <typename U, typename... Args>
  _Tp& emplace(std::initializer_list<U> ilist, Args&&... args);

  using __base::reset;

  constexpr explicit operator bool() const noexcept;
  using __base::has_value;

  constexpr void swap(optional& __opt) noexcept;
};

template <typename T>
constexpr optional<typename std::decay<T>::type> make_optional(T&& v);

template <typename T, typename... Args>
constexpr optional<T> make_optional(Args&&... args);

template <typename T, typename U, typename... Args>
constexpr optional<T> make_optional(std::initializer_list<U> il,
                                    Args&&... args);

template <typename T, typename U>
constexpr bool operator==(const optional<T> &lhs, const optional<U> &rhs);
template <typename T, typename U>
constexpr bool operator!=(const optional<T> &lhs, const optional<U> &rhs);

template <typename T>
constexpr bool operator==(const optional<T> &opt, nullopt_t);
template <typename T>
constexpr bool operator==(nullopt_t, const optional<T> &opt);
template <typename T>
constexpr bool operator!=(const optional<T> &opt, nullopt_t);
template <typename T>
constexpr bool operator!=(nullopt_t, const optional<T> &opt);

template <typename T, typename U>
constexpr bool operator==(const optional<T> &opt, const U &value);
template <typename T, typename U>
constexpr bool operator==(const T &value, const optional<U> &opt);
template <typename T, typename U>
constexpr bool operator!=(const optional<T> &opt, const U &value);
template <typename T, typename U>
constexpr bool operator!=(const T &value, const optional<U> &opt);

} // namespace std
)";

static constexpr char AbslOptionalHeader[] = R"(
#include "absl_type_traits.h"
#include "std_initializer_list.h"
#include "std_type_traits.h"
#include "std_utility.h"

namespace absl {

struct nullopt_t {
  constexpr explicit nullopt_t() {}
};
constexpr nullopt_t nullopt;

struct in_place_t {};
constexpr in_place_t in_place;

template <typename T>
class optional;

namespace optional_internal {

template <typename T, typename U>
struct is_constructible_convertible_from_optional
    : std::integral_constant<
          bool, std::is_constructible<T, optional<U>&>::value ||
                    std::is_constructible<T, optional<U>&&>::value ||
                    std::is_constructible<T, const optional<U>&>::value ||
                    std::is_constructible<T, const optional<U>&&>::value ||
                    std::is_convertible<optional<U>&, T>::value ||
                    std::is_convertible<optional<U>&&, T>::value ||
                    std::is_convertible<const optional<U>&, T>::value ||
                    std::is_convertible<const optional<U>&&, T>::value> {};

template <typename T, typename U>
struct is_constructible_convertible_assignable_from_optional
    : std::integral_constant<
          bool, is_constructible_convertible_from_optional<T, U>::value ||
                    std::is_assignable<T&, optional<U>&>::value ||
                    std::is_assignable<T&, optional<U>&&>::value ||
                    std::is_assignable<T&, const optional<U>&>::value ||
                    std::is_assignable<T&, const optional<U>&&>::value> {};

}  // namespace optional_internal

template <typename T>
class optional {
 public:
  constexpr optional() noexcept;

  constexpr optional(nullopt_t) noexcept;

  optional(const optional&) = default;

  optional(optional&&) = default;

  template <typename InPlaceT, typename... Args,
            absl::enable_if_t<absl::conjunction<
                std::is_same<InPlaceT, in_place_t>,
                std::is_constructible<T, Args&&...>>::value>* = nullptr>
  constexpr explicit optional(InPlaceT, Args&&... args);

  template <typename U, typename... Args,
            typename = typename std::enable_if<std::is_constructible<
                T, std::initializer_list<U>&, Args&&...>::value>::type>
  constexpr explicit optional(in_place_t, std::initializer_list<U> il,
                              Args&&... args);

  template <
      typename U = T,
      typename std::enable_if<
          absl::conjunction<absl::negation<std::is_same<
                                in_place_t, typename std::decay<U>::type>>,
                            absl::negation<std::is_same<
                                optional<T>, typename std::decay<U>::type>>,
                            std::is_convertible<U&&, T>,
                            std::is_constructible<T, U&&>>::value,
          bool>::type = false>
  constexpr optional(U&& v);

  template <
      typename U = T,
      typename std::enable_if<
          absl::conjunction<absl::negation<std::is_same<
                                in_place_t, typename std::decay<U>::type>>,
                            absl::negation<std::is_same<
                                optional<T>, typename std::decay<U>::type>>,
                            absl::negation<std::is_convertible<U&&, T>>,
                            std::is_constructible<T, U&&>>::value,
          bool>::type = false>
  explicit constexpr optional(U&& v);

  template <typename U,
            typename std::enable_if<
                absl::conjunction<
                    absl::negation<std::is_same<T, U>>,
                    std::is_constructible<T, const U&>,
                    absl::negation<
                        optional_internal::
                            is_constructible_convertible_from_optional<T, U>>,
                    std::is_convertible<const U&, T>>::value,
                bool>::type = false>
  optional(const optional<U>& rhs);

  template <typename U,
            typename std::enable_if<
                absl::conjunction<
                    absl::negation<std::is_same<T, U>>,
                    std::is_constructible<T, const U&>,
                    absl::negation<
                        optional_internal::
                            is_constructible_convertible_from_optional<T, U>>,
                    absl::negation<std::is_convertible<const U&, T>>>::value,
                bool>::type = false>
  explicit optional(const optional<U>& rhs);

  template <
      typename U,
      typename std::enable_if<
          absl::conjunction<
              absl::negation<std::is_same<T, U>>, std::is_constructible<T, U&&>,
              absl::negation<
                  optional_internal::is_constructible_convertible_from_optional<
                      T, U>>,
              std::is_convertible<U&&, T>>::value,
          bool>::type = false>
  optional(optional<U>&& rhs);

  template <
      typename U,
      typename std::enable_if<
          absl::conjunction<
              absl::negation<std::is_same<T, U>>, std::is_constructible<T, U&&>,
              absl::negation<
                  optional_internal::is_constructible_convertible_from_optional<
                      T, U>>,
              absl::negation<std::is_convertible<U&&, T>>>::value,
          bool>::type = false>
  explicit optional(optional<U>&& rhs);

  optional& operator=(nullopt_t) noexcept;

  optional& operator=(const optional& src);

  optional& operator=(optional&& src);

  template <
      typename U = T,
      typename = typename std::enable_if<absl::conjunction<
          absl::negation<
              std::is_same<optional<T>, typename std::decay<U>::type>>,
          absl::negation<
              absl::conjunction<std::is_scalar<T>,
                                std::is_same<T, typename std::decay<U>::type>>>,
          std::is_constructible<T, U>, std::is_assignable<T&, U>>::value>::type>
  optional& operator=(U&& v);

  template <
      typename U,
      typename = typename std::enable_if<absl::conjunction<
          absl::negation<std::is_same<T, U>>,
          std::is_constructible<T, const U&>, std::is_assignable<T&, const U&>,
          absl::negation<
              optional_internal::
                  is_constructible_convertible_assignable_from_optional<
                      T, U>>>::value>::type>
  optional& operator=(const optional<U>& rhs);

  template <typename U,
            typename = typename std::enable_if<absl::conjunction<
                absl::negation<std::is_same<T, U>>, std::is_constructible<T, U>,
                std::is_assignable<T&, U>,
                absl::negation<
                    optional_internal::
                        is_constructible_convertible_assignable_from_optional<
                            T, U>>>::value>::type>
  optional& operator=(optional<U>&& rhs);

  const T& operator*() const&;
  T& operator*() &;
  const T&& operator*() const&&;
  T&& operator*() &&;

  const T* operator->() const;
  T* operator->();

  const T& value() const&;
  T& value() &;
  const T&& value() const&&;
  T&& value() &&;

  template <typename U>
  constexpr T value_or(U&& v) const&;
  template <typename U>
  T value_or(U&& v) &&;

  template <typename... Args>
  T& emplace(Args&&... args);

  template <typename U, typename... Args>
  T& emplace(std::initializer_list<U> ilist, Args&&... args);

  void reset() noexcept;

  constexpr explicit operator bool() const noexcept;
  constexpr bool has_value() const noexcept;

  void swap(optional& rhs) noexcept;
};

template <typename T>
constexpr optional<typename std::decay<T>::type> make_optional(T&& v);

template <typename T, typename... Args>
constexpr optional<T> make_optional(Args&&... args);

template <typename T, typename U, typename... Args>
constexpr optional<T> make_optional(std::initializer_list<U> il,
                                    Args&&... args);

template <typename T, typename U>
constexpr bool operator==(const optional<T> &lhs, const optional<U> &rhs);
template <typename T, typename U>
constexpr bool operator!=(const optional<T> &lhs, const optional<U> &rhs);

template <typename T>
constexpr bool operator==(const optional<T> &opt, nullopt_t);
template <typename T>
constexpr bool operator==(nullopt_t, const optional<T> &opt);
template <typename T>
constexpr bool operator!=(const optional<T> &opt, nullopt_t);
template <typename T>
constexpr bool operator!=(nullopt_t, const optional<T> &opt);

template <typename T, typename U>
constexpr bool operator==(const optional<T> &opt, const U &value);
template <typename T, typename U>
constexpr bool operator==(const T &value, const optional<U> &opt);
template <typename T, typename U>
constexpr bool operator!=(const optional<T> &opt, const U &value);
template <typename T, typename U>
constexpr bool operator!=(const T &value, const optional<U> &opt);

} // namespace absl
)";

static constexpr char BaseOptionalHeader[] = R"(
#include "std_initializer_list.h"
#include "std_type_traits.h"
#include "std_utility.h"

namespace base {

struct in_place_t {};
constexpr in_place_t in_place;

struct nullopt_t {
  constexpr explicit nullopt_t() {}
};
constexpr nullopt_t nullopt;

template <typename T>
class Optional;

namespace internal {

template <typename T>
using RemoveCvRefT = std::remove_cv_t<std::remove_reference_t<T>>;

template <typename T, typename U>
struct IsConvertibleFromOptional
    : std::integral_constant<
          bool, std::is_constructible<T, Optional<U>&>::value ||
                    std::is_constructible<T, const Optional<U>&>::value ||
                    std::is_constructible<T, Optional<U>&&>::value ||
                    std::is_constructible<T, const Optional<U>&&>::value ||
                    std::is_convertible<Optional<U>&, T>::value ||
                    std::is_convertible<const Optional<U>&, T>::value ||
                    std::is_convertible<Optional<U>&&, T>::value ||
                    std::is_convertible<const Optional<U>&&, T>::value> {};

template <typename T, typename U>
struct IsAssignableFromOptional
    : std::integral_constant<
          bool, IsConvertibleFromOptional<T, U>::value ||
                    std::is_assignable<T&, Optional<U>&>::value ||
                    std::is_assignable<T&, const Optional<U>&>::value ||
                    std::is_assignable<T&, Optional<U>&&>::value ||
                    std::is_assignable<T&, const Optional<U>&&>::value> {};

}  // namespace internal

template <typename T>
class Optional {
 public:
  using value_type = T;

  constexpr Optional() = default;
  constexpr Optional(const Optional& other) noexcept = default;
  constexpr Optional(Optional&& other) noexcept = default;

  constexpr Optional(nullopt_t);

  template <typename U,
            typename std::enable_if<
                std::is_constructible<T, const U&>::value &&
                    !internal::IsConvertibleFromOptional<T, U>::value &&
                    std::is_convertible<const U&, T>::value,
                bool>::type = false>
  Optional(const Optional<U>& other) noexcept;

  template <typename U,
            typename std::enable_if<
                std::is_constructible<T, const U&>::value &&
                    !internal::IsConvertibleFromOptional<T, U>::value &&
                    !std::is_convertible<const U&, T>::value,
                bool>::type = false>
  explicit Optional(const Optional<U>& other) noexcept;

  template <typename U,
            typename std::enable_if<
                std::is_constructible<T, U&&>::value &&
                    !internal::IsConvertibleFromOptional<T, U>::value &&
                    std::is_convertible<U&&, T>::value,
                bool>::type = false>
  Optional(Optional<U>&& other) noexcept;

  template <typename U,
            typename std::enable_if<
                std::is_constructible<T, U&&>::value &&
                    !internal::IsConvertibleFromOptional<T, U>::value &&
                    !std::is_convertible<U&&, T>::value,
                bool>::type = false>
  explicit Optional(Optional<U>&& other) noexcept;

  template <class... Args>
  constexpr explicit Optional(in_place_t, Args&&... args);

  template <class U, class... Args,
            class = typename std::enable_if<std::is_constructible<
                value_type, std::initializer_list<U>&, Args...>::value>::type>
  constexpr explicit Optional(in_place_t, std::initializer_list<U> il,
                              Args&&... args);

  template <
      typename U = value_type,
      typename std::enable_if<
          std::is_constructible<T, U&&>::value &&
              !std::is_same<internal::RemoveCvRefT<U>, in_place_t>::value &&
              !std::is_same<internal::RemoveCvRefT<U>, Optional<T>>::value &&
              std::is_convertible<U&&, T>::value,
          bool>::type = false>
  constexpr Optional(U&& value);

  template <
      typename U = value_type,
      typename std::enable_if<
          std::is_constructible<T, U&&>::value &&
              !std::is_same<internal::RemoveCvRefT<U>, in_place_t>::value &&
              !std::is_same<internal::RemoveCvRefT<U>, Optional<T>>::value &&
              !std::is_convertible<U&&, T>::value,
          bool>::type = false>
  constexpr explicit Optional(U&& value);

  Optional& operator=(const Optional& other) noexcept;

  Optional& operator=(Optional&& other) noexcept;

  Optional& operator=(nullopt_t);

  template <typename U>
  typename std::enable_if<
      !std::is_same<internal::RemoveCvRefT<U>, Optional<T>>::value &&
          std::is_constructible<T, U>::value &&
          std::is_assignable<T&, U>::value &&
          (!std::is_scalar<T>::value ||
           !std::is_same<typename std::decay<U>::type, T>::value),
      Optional&>::type
  operator=(U&& value) noexcept;

  template <typename U>
  typename std::enable_if<!internal::IsAssignableFromOptional<T, U>::value &&
                              std::is_constructible<T, const U&>::value &&
                              std::is_assignable<T&, const U&>::value,
                          Optional&>::type
  operator=(const Optional<U>& other) noexcept;

  template <typename U>
  typename std::enable_if<!internal::IsAssignableFromOptional<T, U>::value &&
                              std::is_constructible<T, U>::value &&
                              std::is_assignable<T&, U>::value,
                          Optional&>::type
  operator=(Optional<U>&& other) noexcept;

  const T& operator*() const&;
  T& operator*() &;
  const T&& operator*() const&&;
  T&& operator*() &&;

  const T* operator->() const;
  T* operator->();

  const T& value() const&;
  T& value() &;
  const T&& value() const&&;
  T&& value() &&;

  template <typename U>
  constexpr T value_or(U&& v) const&;
  template <typename U>
  T value_or(U&& v) &&;

  template <typename... Args>
  T& emplace(Args&&... args);

  template <typename U, typename... Args>
  T& emplace(std::initializer_list<U> ilist, Args&&... args);

  void reset() noexcept;

  constexpr explicit operator bool() const noexcept;
  constexpr bool has_value() const noexcept;

  void swap(Optional& other);
};

template <typename T>
constexpr Optional<typename std::decay<T>::type> make_optional(T&& v);

template <typename T, typename... Args>
constexpr Optional<T> make_optional(Args&&... args);

template <typename T, typename U, typename... Args>
constexpr Optional<T> make_optional(std::initializer_list<U> il,
                                    Args&&... args);

template <typename T, typename U>
constexpr bool operator==(const Optional<T> &lhs, const Optional<U> &rhs);
template <typename T, typename U>
constexpr bool operator!=(const Optional<T> &lhs, const Optional<U> &rhs);

template <typename T>
constexpr bool operator==(const Optional<T> &opt, nullopt_t);
template <typename T>
constexpr bool operator==(nullopt_t, const Optional<T> &opt);
template <typename T>
constexpr bool operator!=(const Optional<T> &opt, nullopt_t);
template <typename T>
constexpr bool operator!=(nullopt_t, const Optional<T> &opt);

template <typename T, typename U>
constexpr bool operator==(const Optional<T> &opt, const U &value);
template <typename T, typename U>
constexpr bool operator==(const T &value, const Optional<U> &opt);
template <typename T, typename U>
constexpr bool operator!=(const Optional<T> &opt, const U &value);
template <typename T, typename U>
constexpr bool operator!=(const T &value, const Optional<U> &opt);

} // namespace base
)";

/// Replaces all occurrences of `Pattern` in `S` with `Replacement`.
static void ReplaceAllOccurrences(std::string &S, const std::string &Pattern,
                                  const std::string &Replacement) {
  size_t Pos = 0;
  while (true) {
    Pos = S.find(Pattern, Pos);
    if (Pos == std::string::npos)
      break;
    S.replace(Pos, Pattern.size(), Replacement);
  }
}

struct OptionalTypeIdentifier {
  std::string NamespaceName;
  std::string TypeName;
};

class UncheckedOptionalAccessTest
    : public ::testing::TestWithParam<OptionalTypeIdentifier> {
protected:
  void ExpectDiagnosticsFor(std::string SourceCode) {
    ExpectDiagnosticsFor(SourceCode, ast_matchers::hasName("target"));
  }

  template <typename FuncDeclMatcher>
  void ExpectDiagnosticsFor(std::string SourceCode,
                            FuncDeclMatcher FuncMatcher) {
    ReplaceAllOccurrences(SourceCode, "$ns", GetParam().NamespaceName);
    ReplaceAllOccurrences(SourceCode, "$optional", GetParam().TypeName);

    std::vector<std::pair<std::string, std::string>> Headers;
    Headers.emplace_back("cstddef.h", CSDtdDefHeader);
    Headers.emplace_back("std_initializer_list.h", StdInitializerListHeader);
    Headers.emplace_back("std_string.h", StdStringHeader);
    Headers.emplace_back("std_type_traits.h", StdTypeTraitsHeader);
    Headers.emplace_back("std_utility.h", StdUtilityHeader);
    Headers.emplace_back("std_optional.h", StdOptionalHeader);
    Headers.emplace_back("absl_type_traits.h", AbslTypeTraitsHeader);
    Headers.emplace_back("absl_optional.h", AbslOptionalHeader);
    Headers.emplace_back("base_optional.h", BaseOptionalHeader);
    Headers.emplace_back("unchecked_optional_access_test.h", R"(
      #include "absl_optional.h"
      #include "base_optional.h"
      #include "std_initializer_list.h"
      #include "std_optional.h"
      #include "std_string.h"
      #include "std_utility.h"

      template <typename T>
      T Make();
    )");
    UncheckedOptionalAccessModelOptions Options{
        /*IgnoreSmartPointerDereference=*/true};
    std::vector<SourceLocation> Diagnostics;
    llvm::Error Error = checkDataflow<UncheckedOptionalAccessModel>(
        AnalysisInputs<UncheckedOptionalAccessModel>(
            SourceCode, std::move(FuncMatcher),
            [](ASTContext &Ctx, Environment &) {
              return UncheckedOptionalAccessModel(Ctx);
            })
            .withPostVisitCFG(
                [&Diagnostics,
                 Diagnoser = UncheckedOptionalAccessDiagnoser(Options)](
                    ASTContext &Ctx, const CFGElement &Elt,
                    const TransferStateForDiagnostics<NoopLattice>
                        &State) mutable {
                  auto EltDiagnostics =
                      Diagnoser.diagnose(Ctx, &Elt, State.Env);
                  llvm::move(EltDiagnostics, std::back_inserter(Diagnostics));
                })
            .withASTBuildArgs(
                {"-fsyntax-only", "-std=c++17", "-Wno-undefined-inline"})
            .withASTBuildVirtualMappedFiles(
                tooling::FileContentMappings(Headers.begin(), Headers.end())),
        /*VerifyResults=*/[&Diagnostics](
                              const llvm::DenseMap<unsigned, std::string>
                                  &Annotations,
                              const AnalysisOutputs &AO) {
          llvm::DenseSet<unsigned> AnnotationLines;
          for (const auto &[Line, _] : Annotations) {
            AnnotationLines.insert(Line);
          }
          auto &SrcMgr = AO.ASTCtx.getSourceManager();
          llvm::DenseSet<unsigned> DiagnosticLines;
          for (SourceLocation &Loc : Diagnostics) {
            DiagnosticLines.insert(SrcMgr.getPresumedLineNumber(Loc));
          }

          EXPECT_THAT(DiagnosticLines, ContainerEq(AnnotationLines));
        });
    if (Error)
      FAIL() << llvm::toString(std::move(Error));
  }
};

INSTANTIATE_TEST_SUITE_P(
    UncheckedOptionalUseTestInst, UncheckedOptionalAccessTest,
    ::testing::Values(OptionalTypeIdentifier{"std", "optional"},
                      OptionalTypeIdentifier{"absl", "optional"},
                      OptionalTypeIdentifier{"base", "Optional"}),
    [](const ::testing::TestParamInfo<OptionalTypeIdentifier> &Info) {
      return Info.param.NamespaceName;
    });

TEST_P(UncheckedOptionalAccessTest, EmptyFunctionBody) {
  ExpectDiagnosticsFor(R"(
    void target() {
      (void)0;
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, UnwrapUsingValueNoCheck) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target($ns::$optional<int> opt) {
      opt.value(); // [[unsafe]]
    }
  )");

  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target($ns::$optional<int> opt) {
      std::move(opt).value(); // [[unsafe]]
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, UnwrapUsingOperatorStarNoCheck) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target($ns::$optional<int> opt) {
      *opt; // [[unsafe]]
    }
  )");

  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target($ns::$optional<int> opt) {
      *std::move(opt); // [[unsafe]]
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, UnwrapUsingOperatorArrowNoCheck) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    struct Foo {
      void foo();
    };

    void target($ns::$optional<Foo> opt) {
      opt->foo(); // [[unsafe]]
    }
  )");

  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    struct Foo {
      void foo();
    };

    void target($ns::$optional<Foo> opt) {
      std::move(opt)->foo(); // [[unsafe]]
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, HasValueCheck) {
  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    void target($ns::$optional<int> opt) {
      if (opt.has_value()) {
        opt.value();
      }
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, OperatorBoolCheck) {
  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    void target($ns::$optional<int> opt) {
      if (opt) {
        opt.value();
      }
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, UnwrapFunctionCallResultNoCheck) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      Make<$ns::$optional<int>>().value(); // [[unsafe]]
      (void)0;
    }
  )");

  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target($ns::$optional<int> opt) {
      std::move(opt).value(); // [[unsafe]]
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, DefaultConstructor) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt;
      opt.value(); // [[unsafe]]
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, NulloptConstructor) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt($ns::nullopt);
      opt.value(); // [[unsafe]]
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, NulloptConstructorWithSugaredType) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"
    template <typename T>
    using wrapper = T;

    template <typename T>
    wrapper<T> wrap(T);

    void target() {
      $ns::$optional<int> opt(wrap($ns::nullopt));
      opt.value(); // [[unsafe]]
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, InPlaceConstructor) {
  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt($ns::in_place, 3);
      opt.value();
    }
  )");

  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    struct Foo {};

    void target() {
      $ns::$optional<Foo> opt($ns::in_place);
      opt.value();
    }
  )");

  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    struct Foo {
      explicit Foo(int, bool);
    };

    void target() {
      $ns::$optional<Foo> opt($ns::in_place, 3, false);
      opt.value();
    }
  )");

  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    struct Foo {
      explicit Foo(std::initializer_list<int>);
    };

    void target() {
      $ns::$optional<Foo> opt($ns::in_place, {3});
      opt.value();
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, ValueConstructor) {
  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt(21);
      opt.value();
    }
  )");

  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt = $ns::$optional<int>(21);
      opt.value();
    }
  )");
  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<$ns::$optional<int>> opt(Make<$ns::$optional<int>>());
      opt.value();
    }
  )");

  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    struct MyString {
      MyString(const char*);
    };

    void target() {
      $ns::$optional<MyString> opt("foo");
      opt.value();
    }
  )");

  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    struct Foo {};

    struct Bar {
      Bar(const Foo&);
    };

    void target() {
      $ns::$optional<Bar> opt(Make<Foo>());
      opt.value();
    }
  )");

  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    struct Foo {
      explicit Foo(int);
    };

    void target() {
      $ns::$optional<Foo> opt(3);
      opt.value();
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, ConvertibleOptionalConstructor) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    struct Foo {};

    struct Bar {
      Bar(const Foo&);
    };

    void target() {
      $ns::$optional<Bar> opt(Make<$ns::$optional<Foo>>());
      opt.value(); // [[unsafe]]
    }
  )");

  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    struct Foo {};

    struct Bar {
      explicit Bar(const Foo&);
    };

    void target() {
      $ns::$optional<Bar> opt(Make<$ns::$optional<Foo>>());
      opt.value(); // [[unsafe]]
    }
  )");

  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    struct Foo {};

    struct Bar {
      Bar(const Foo&);
    };

    void target() {
      $ns::$optional<Foo> opt1 = $ns::nullopt;
      $ns::$optional<Bar> opt2(opt1);
      opt2.value(); // [[unsafe]]
    }
  )");

  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    struct Foo {};

    struct Bar {
      Bar(const Foo&);
    };

    void target() {
      $ns::$optional<Foo> opt1(Make<Foo>());
      $ns::$optional<Bar> opt2(opt1);
      opt2.value();
    }
  )");

  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    struct Foo {};

    struct Bar {
      explicit Bar(const Foo&);
    };

    void target() {
      $ns::$optional<Foo> opt1(Make<Foo>());
      $ns::$optional<Bar> opt2(opt1);
      opt2.value();
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, MakeOptional) {
  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt = $ns::make_optional(0);
      opt.value();
    }
  )");

  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    struct Foo {
      Foo(int, int);
    };

    void target() {
      $ns::$optional<Foo> opt = $ns::make_optional<Foo>(21, 22);
      opt.value();
    }
  )");

  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    struct Foo {
      constexpr Foo(std::initializer_list<char>);
    };

    void target() {
      char a = 'a';
      $ns::$optional<Foo> opt = $ns::make_optional<Foo>({a});
      opt.value();
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, ValueOr) {
  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt;
      opt.value_or(0);
      (void)0;
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, ValueOrComparison) {
  // Pointers.
  ExpectDiagnosticsFor(
      R"code(
    #include "unchecked_optional_access_test.h"

    void target($ns::$optional<int*> opt) {
      if (opt.value_or(nullptr) != nullptr) {
        opt.value();
      } else {
        opt.value(); // [[unsafe]]
      }
    }
  )code");

  // Integers.
  ExpectDiagnosticsFor(
      R"code(
    #include "unchecked_optional_access_test.h"

    void target($ns::$optional<int> opt) {
      if (opt.value_or(0) != 0) {
        opt.value();
      } else {
        opt.value(); // [[unsafe]]
      }
    }
  )code");

  // Strings.
  ExpectDiagnosticsFor(
      R"code(
    #include "unchecked_optional_access_test.h"

    void target($ns::$optional<std::string> opt) {
      if (!opt.value_or("").empty()) {
        opt.value();
      } else {
        opt.value(); // [[unsafe]]
      }
    }
  )code");

  ExpectDiagnosticsFor(
      R"code(
    #include "unchecked_optional_access_test.h"

    void target($ns::$optional<std::string> opt) {
      if (opt.value_or("") != "") {
        opt.value();
      } else {
        opt.value(); // [[unsafe]]
      }
    }
  )code");

  // Pointer-to-optional.
  //
  // FIXME: make `opt` a parameter directly, once we ensure that all `optional`
  // values have a `has_value` property.
  ExpectDiagnosticsFor(
      R"code(
    #include "unchecked_optional_access_test.h"

    void target($ns::$optional<int> p) {
      $ns::$optional<int> *opt = &p;
      if (opt->value_or(0) != 0) {
        opt->value();
      } else {
        opt->value(); // [[unsafe]]
      }
    }
  )code");
}

TEST_P(UncheckedOptionalAccessTest, Emplace) {
  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt;
      opt.emplace(0);
      opt.value();
    }
  )");

  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    void target($ns::$optional<int> *opt) {
      opt->emplace(0);
      opt->value();
    }
  )");

  // FIXME: Add tests that call `emplace` in conditional branches:
  //  ExpectDiagnosticsFor(
  //      R"(
  //    #include "unchecked_optional_access_test.h"
  //
  //    void target($ns::$optional<int> opt, bool b) {
  //      if (b) {
  //        opt.emplace(0);
  //      }
  //      if (b) {
  //        opt.value();
  //      } else {
  //        opt.value(); // [[unsafe]]
  //      }
  //    }
  //  )");
}

TEST_P(UncheckedOptionalAccessTest, Reset) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt = $ns::make_optional(0);
      opt.reset();
      opt.value(); // [[unsafe]]
    }
  )");

  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target($ns::$optional<int> &opt) {
      if (opt.has_value()) {
        opt.reset();
        opt.value(); // [[unsafe]]
      }
    }
  )");

  // FIXME: Add tests that call `reset` in conditional branches:
  //  ExpectDiagnosticsFor(
  //      R"(
  //    #include "unchecked_optional_access_test.h"
  //
  //    void target(bool b) {
  //      $ns::$optional<int> opt = $ns::make_optional(0);
  //      if (b) {
  //        opt.reset();
  //      }
  //      if (b) {
  //        opt.value(); // [[unsafe]]
  //      } else {
  //        opt.value();
  //      }
  //    }
  //  )");
}

TEST_P(UncheckedOptionalAccessTest, ValueAssignment) {
  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    struct Foo {};

    void target() {
      $ns::$optional<Foo> opt;
      opt = Foo();
      opt.value();
    }
  )");

  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    struct Foo {};

    void target() {
      $ns::$optional<Foo> opt;
      (opt = Foo()).value();
      (void)0;
    }
  )");

  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    struct MyString {
      MyString(const char*);
    };

    void target() {
      $ns::$optional<MyString> opt;
      opt = "foo";
      opt.value();
    }
  )");

  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    struct MyString {
      MyString(const char*);
    };

    void target() {
      $ns::$optional<MyString> opt;
      (opt = "foo").value();
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, OptionalConversionAssignment) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    struct Foo {};

    struct Bar {
      Bar(const Foo&);
    };

    void target() {
      $ns::$optional<Foo> opt1 = Foo();
      $ns::$optional<Bar> opt2;
      opt2 = opt1;
      opt2.value();
    }
  )");

  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    struct Foo {};

    struct Bar {
      Bar(const Foo&);
    };

    void target() {
      $ns::$optional<Foo> opt1;
      $ns::$optional<Bar> opt2;
      if (opt2.has_value()) {
        opt2 = opt1;
        opt2.value(); // [[unsafe]]
      }
    }
  )");

  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    struct Foo {};

    struct Bar {
      Bar(const Foo&);
    };

    void target() {
      $ns::$optional<Foo> opt1 = Foo();
      $ns::$optional<Bar> opt2;
      (opt2 = opt1).value();
      (void)0;
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, NulloptAssignment) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt = 3;
      opt = $ns::nullopt;
      opt.value(); // [[unsafe]]
    }
  )");

  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt = 3;
      (opt = $ns::nullopt).value(); // [[unsafe]]
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, OptionalSwap) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt1 = $ns::nullopt;
      $ns::$optional<int> opt2 = 3;

      opt1.swap(opt2);

      opt1.value();

      opt2.value(); // [[unsafe]]
    }
  )");

  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt1 = $ns::nullopt;
      $ns::$optional<int> opt2 = 3;

      opt2.swap(opt1);

      opt1.value();

      opt2.value(); // [[unsafe]]
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, StdSwap) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt1 = $ns::nullopt;
      $ns::$optional<int> opt2 = 3;

      std::swap(opt1, opt2);

      opt1.value();

      opt2.value(); // [[unsafe]]
    }
  )");

  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt1 = $ns::nullopt;
      $ns::$optional<int> opt2 = 3;

      std::swap(opt2, opt1);

      opt1.value();

      opt2.value(); // [[unsafe]]
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, UniquePtrToOptional) {
  // We suppress diagnostics for optionals in smart pointers (other than
  // `optional` itself).
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    template <typename T>
    struct smart_ptr {
      T& operator*() &;
      T* operator->();
    };

    void target() {
      smart_ptr<$ns::$optional<bool>> foo;
      foo->value();
      (*foo).value();
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, UniquePtrToStructWithOptionalField) {
  // We suppress diagnostics for optional fields reachable from smart pointers
  // (other than `optional` itself) through (exactly) one member access.
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    template <typename T>
    struct smart_ptr {
      T& operator*() &;
      T* operator->();
    };

    struct Foo {
      $ns::$optional<int> opt;
    };

    void target() {
      smart_ptr<Foo> foo;
      *foo->opt;
      *(*foo).opt;
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, CallReturningOptional) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    $ns::$optional<int> MakeOpt();

    void target() {
      $ns::$optional<int> opt = 0;
      opt = MakeOpt();
      opt.value(); // [[unsafe]]
    }
  )");
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    const $ns::$optional<int>& MakeOpt();

    void target() {
      $ns::$optional<int> opt = 0;
      opt = MakeOpt();
      opt.value(); // [[unsafe]]
    }
  )");

  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    using IntOpt = $ns::$optional<int>;
    IntOpt MakeOpt();

    void target() {
      IntOpt opt = 0;
      opt = MakeOpt();
      opt.value(); // [[unsafe]]
    }
  )");

  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    using IntOpt = $ns::$optional<int>;
    const IntOpt& MakeOpt();

    void target() {
      IntOpt opt = 0;
      opt = MakeOpt();
      opt.value(); // [[unsafe]]
    }
  )");
}


TEST_P(UncheckedOptionalAccessTest, EqualityCheckLeftSet) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt1 = 3;
      $ns::$optional<int> opt2 = Make<$ns::$optional<int>>();

      if (opt1 == opt2) {
        opt2.value();
      } else {
        opt2.value(); // [[unsafe]]
      }
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, EqualityCheckRightSet) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt1 = 3;
      $ns::$optional<int> opt2 = Make<$ns::$optional<int>>();

      if (opt2 == opt1) {
        opt2.value();
      } else {
        opt2.value(); // [[unsafe]]
      }
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, EqualityCheckVerifySetAfterEq) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt1 = Make<$ns::$optional<int>>();
      $ns::$optional<int> opt2 = Make<$ns::$optional<int>>();

      if (opt1 == opt2) {
        if (opt1.has_value())
          opt2.value();
        if (opt2.has_value())
          opt1.value();
      }
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, EqualityCheckLeftUnset) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt1 = $ns::nullopt;
      $ns::$optional<int> opt2 = Make<$ns::$optional<int>>();

      if (opt1 == opt2) {
        opt2.value(); // [[unsafe]]
      } else {
        opt2.value();
      }
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, EqualityCheckRightUnset) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt1 = $ns::nullopt;
      $ns::$optional<int> opt2 = Make<$ns::$optional<int>>();

      if (opt2 == opt1) {
        opt2.value(); // [[unsafe]]
      } else {
        opt2.value();
      }
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, EqualityCheckRightNullopt) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt = Make<$ns::$optional<int>>();

      if (opt == $ns::nullopt) {
        opt.value(); // [[unsafe]]
      } else {
        opt.value();
      }
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, EqualityCheckLeftNullopt) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt = Make<$ns::$optional<int>>();

      if ($ns::nullopt == opt) {
        opt.value(); // [[unsafe]]
      } else {
        opt.value();
      }
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, EqualityCheckRightValue) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt = Make<$ns::$optional<int>>();

      if (opt == 3) {
        opt.value();
      } else {
        opt.value(); // [[unsafe]]
      }
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, EqualityCheckLeftValue) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt = Make<$ns::$optional<int>>();

      if (3 == opt) {
        opt.value();
      } else {
        opt.value(); // [[unsafe]]
      }
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, InequalityCheckLeftSet) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt1 = 3;
      $ns::$optional<int> opt2 = Make<$ns::$optional<int>>();

      if (opt1 != opt2) {
        opt2.value(); // [[unsafe]]
      } else {
        opt2.value();
      }
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, InequalityCheckRightSet) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt1 = 3;
      $ns::$optional<int> opt2 = Make<$ns::$optional<int>>();

      if (opt2 != opt1) {
        opt2.value(); // [[unsafe]]
      } else {
        opt2.value();
      }
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, InequalityCheckVerifySetAfterEq) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt1 = Make<$ns::$optional<int>>();
      $ns::$optional<int> opt2 = Make<$ns::$optional<int>>();

      if (opt1 != opt2) {
        if (opt1.has_value())
          opt2.value(); // [[unsafe]]
        if (opt2.has_value())
          opt1.value(); // [[unsafe]]
      }
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, InequalityCheckLeftUnset) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt1 = $ns::nullopt;
      $ns::$optional<int> opt2 = Make<$ns::$optional<int>>();

      if (opt1 != opt2) {
        opt2.value();
      } else {
        opt2.value(); // [[unsafe]]
      }
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, InequalityCheckRightUnset) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt1 = $ns::nullopt;
      $ns::$optional<int> opt2 = Make<$ns::$optional<int>>();

      if (opt2 != opt1) {
        opt2.value();
      } else {
        opt2.value(); // [[unsafe]]
      }
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, InequalityCheckRightNullopt) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt = Make<$ns::$optional<int>>();

      if (opt != $ns::nullopt) {
        opt.value();
      } else {
        opt.value(); // [[unsafe]]
      }
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, InequalityCheckLeftNullopt) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt = Make<$ns::$optional<int>>();

      if ($ns::nullopt != opt) {
        opt.value();
      } else {
        opt.value(); // [[unsafe]]
      }
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, InequalityCheckRightValue) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt = Make<$ns::$optional<int>>();

      if (opt != 3) {
        opt.value(); // [[unsafe]]
      } else {
        opt.value();
      }
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, InequalityCheckLeftValue) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt = Make<$ns::$optional<int>>();

      if (3 != opt) {
        opt.value(); // [[unsafe]]
      } else {
        opt.value();
      }
    }
  )");
}

// Verifies that the model sees through aliases.
TEST_P(UncheckedOptionalAccessTest, WithAlias) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    template <typename T>
    using MyOptional = $ns::$optional<T>;

    void target(MyOptional<int> opt) {
      opt.value(); // [[unsafe]]
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, OptionalValueOptional) {
  // Basic test that nested values are populated.  We nest an optional because
  // its easy to use in a test, but the type of the nested value shouldn't
  // matter.
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    using Foo = $ns::$optional<std::string>;

    void target($ns::$optional<Foo> foo) {
      if (foo && *foo) {
        foo->value();
      }
    }
  )");

  // Mutation is supported for nested values.
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    using Foo = $ns::$optional<std::string>;

    void target($ns::$optional<Foo> foo) {
      if (foo && *foo) {
        foo->reset();
        foo->value(); // [[unsafe]]
      }
    }
  )");
}

// Tests that structs can be nested. We use an optional field because its easy
// to use in a test, but the type of the field shouldn't matter.
TEST_P(UncheckedOptionalAccessTest, OptionalValueStruct) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    struct Foo {
      $ns::$optional<std::string> opt;
    };

    void target($ns::$optional<Foo> foo) {
      if (foo && foo->opt) {
        foo->opt.value();
      }
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, OptionalValueInitialization) {
  // FIXME: Fix when to initialize `value`. All unwrapping should be safe in
  // this example, but `value` initialization is done multiple times during the
  // fixpoint iterations and joining the environment won't correctly merge them.
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    using Foo = $ns::$optional<std::string>;

    void target($ns::$optional<Foo> foo, bool b) {
      if (!foo.has_value()) return;
      if (b) {
        if (!foo->has_value()) return;
        // We have created `foo.value()`.
        foo->value();
      } else {
        if (!foo->has_value()) return;
        // We have created `foo.value()` again, in a different environment.
        foo->value();
      }
      // Now we merge the two values. UncheckedOptionalAccessModel::merge() will
      // throw away the "value" property.
      foo->value(); // [[unsafe]]
    }
  )");
}

// This test is aimed at the core model, not the diagnostic. It is a regression
// test against a crash when using non-trivial smart pointers, like
// `std::unique_ptr`. As such, it doesn't test the access itself, which would be
// ignored regardless because of `IgnoreSmartPointerDereference = true`, above.
TEST_P(UncheckedOptionalAccessTest, AssignThroughLvalueReferencePtr) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    template <typename T>
    struct smart_ptr {
      typename std::add_lvalue_reference<T>::type operator*() &;
    };

    void target() {
      smart_ptr<$ns::$optional<int>> x;
      // Verify that this assignment does not crash.
      *x = 3;
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, CorrelatedBranches) {
  ExpectDiagnosticsFor(R"code(
    #include "unchecked_optional_access_test.h"

    void target(bool b, $ns::$optional<int> opt) {
      if (b || opt.has_value()) {
        if (!b) {
          opt.value();
        }
      }
    }
  )code");

  ExpectDiagnosticsFor(R"code(
    #include "unchecked_optional_access_test.h"

    void target(bool b, $ns::$optional<int> opt) {
      if (b && !opt.has_value()) return;
      if (b) {
        opt.value();
      }
    }
  )code");

  ExpectDiagnosticsFor(
      R"code(
    #include "unchecked_optional_access_test.h"

    void target(bool b, $ns::$optional<int> opt) {
      if (opt.has_value()) b = true;
      if (b) {
        opt.value(); // [[unsafe]]
      }
    }
  )code");

  ExpectDiagnosticsFor(R"code(
    #include "unchecked_optional_access_test.h"

    void target(bool b, $ns::$optional<int> opt) {
      if (b) return;
      if (opt.has_value()) b = true;
      if (b) {
        opt.value();
      }
    }
  )code");

  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    void target(bool b, $ns::$optional<int> opt) {
      if (opt.has_value() == b) {
        if (b) {
          opt.value();
        }
      }
    }
  )");

  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    void target(bool b, $ns::$optional<int> opt) {
      if (opt.has_value() != b) {
        if (!b) {
          opt.value();
        }
      }
    }
  )");

  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    void target(bool b) {
      $ns::$optional<int> opt1 = $ns::nullopt;
      $ns::$optional<int> opt2;
      if (b) {
        opt2 = $ns::nullopt;
      } else {
        opt2 = $ns::nullopt;
      }
      if (opt2.has_value()) {
        opt1.value();
      }
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, JoinDistinctValues) {
  ExpectDiagnosticsFor(
      R"code(
    #include "unchecked_optional_access_test.h"

    void target(bool b) {
      $ns::$optional<int> opt;
      if (b) {
        opt = Make<$ns::$optional<int>>();
      } else {
        opt = Make<$ns::$optional<int>>();
      }
      if (opt.has_value()) {
        opt.value();
      } else {
        opt.value(); // [[unsafe]]
      }
    }
  )code");

  ExpectDiagnosticsFor(R"code(
    #include "unchecked_optional_access_test.h"

    void target(bool b) {
      $ns::$optional<int> opt;
      if (b) {
        opt = Make<$ns::$optional<int>>();
        if (!opt.has_value()) return;
      } else {
        opt = Make<$ns::$optional<int>>();
        if (!opt.has_value()) return;
      }
      opt.value();
    }
  )code");

  ExpectDiagnosticsFor(
      R"code(
    #include "unchecked_optional_access_test.h"

    void target(bool b) {
      $ns::$optional<int> opt;
      if (b) {
        opt = Make<$ns::$optional<int>>();
        if (!opt.has_value()) return;
      } else {
        opt = Make<$ns::$optional<int>>();
      }
      opt.value(); // [[unsafe]]
    }
  )code");

  ExpectDiagnosticsFor(
      R"code(
    #include "unchecked_optional_access_test.h"

    void target(bool b) {
      $ns::$optional<int> opt;
      if (b) {
        opt = 1;
      } else {
        opt = 2;
      }
      opt.value();
    }
  )code");

  ExpectDiagnosticsFor(
      R"code(
    #include "unchecked_optional_access_test.h"

    void target(bool b) {
      $ns::$optional<int> opt;
      if (b) {
        opt = 1;
      } else {
        opt = Make<$ns::$optional<int>>();
      }
      opt.value(); // [[unsafe]]
    }
  )code");
}

TEST_P(UncheckedOptionalAccessTest, AccessValueInLoop) {
  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt = 3;
      while (Make<bool>()) {
        opt.value();
      }
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, ReassignValueInLoopWithCheckSafe) {
  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt = 3;
      while (Make<bool>()) {
        opt.value();

        opt = Make<$ns::$optional<int>>();
        if (!opt.has_value()) return;
      }
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, ReassignValueInLoopNoCheckUnsafe) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt = 3;
      while (Make<bool>()) {
        opt.value(); // [[unsafe]]

        opt = Make<$ns::$optional<int>>();
      }
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, ReassignValueInLoopToUnsetUnsafe) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt = 3;
      while (Make<bool>())
        opt = $ns::nullopt;
      $ns::$optional<int> opt2 = $ns::nullopt;
      if (opt.has_value())
        opt2 = $ns::$optional<int>(3);
      opt2.value(); // [[unsafe]]
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, ReassignValueInLoopToSetUnsafe) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt = $ns::nullopt;
      while (Make<bool>())
        opt = $ns::$optional<int>(3);
      $ns::$optional<int> opt2 = $ns::nullopt;
      if (!opt.has_value())
        opt2 = $ns::$optional<int>(3);
      opt2.value(); // [[unsafe]]
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, ReassignValueInLoopToUnknownUnsafe) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt = $ns::nullopt;
      while (Make<bool>())
        opt = Make<$ns::$optional<int>>();
      $ns::$optional<int> opt2 = $ns::nullopt;
      if (!opt.has_value())
        opt2 = $ns::$optional<int>(3);
      opt2.value(); // [[unsafe]]
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, ReassignValueInLoopBadConditionUnsafe) {
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    void target() {
      $ns::$optional<int> opt = 3;
      while (Make<bool>()) {
        opt.value(); // [[unsafe]]

        opt = Make<$ns::$optional<int>>();
        if (!opt.has_value()) continue;
      }
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, StructuredBindingsFromStruct) {
  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    struct kv { $ns::$optional<int> opt; int x; };
    int target() {
      auto [contents, x] = Make<kv>();
      return contents ? *contents : x;
    }
  )");

  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    template <typename T1, typename T2>
    struct pair { T1 fst;  T2 snd; };
    int target() {
      auto [contents, x] = Make<pair<$ns::$optional<int>, int>>();
      return contents ? *contents : x;
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, StructuredBindingsFromTupleLikeType) {
  ExpectDiagnosticsFor(R"(
    #include "unchecked_optional_access_test.h"

    namespace std {
    template <class> struct tuple_size;
    template <size_t, class> struct tuple_element;
    template <class...> class tuple;

    template <class... T>
    struct tuple_size<tuple<T...>> : integral_constant<size_t, sizeof...(T)> {};

    template <size_t I, class... T>
    struct tuple_element<I, tuple<T...>> {
      using type =  __type_pack_element<I, T...>;
    };

    template <class...> class tuple {};
    template <size_t I, class... T>
    typename tuple_element<I, tuple<T...>>::type get(tuple<T...>);
    } // namespace std

    std::tuple<$ns::$optional<const char *>, int> get_opt();
    void target() {
      auto [content, ck] = get_opt();
      content ? *content : "";
    }
  )");
}

TEST_P(UncheckedOptionalAccessTest, CtorInitializerNullopt) {
  using namespace ast_matchers;
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    struct Target {
      Target(): opt($ns::nullopt) {
        opt.value(); // [[unsafe]]
      }
      $ns::$optional<int> opt;
    };
  )",
      cxxConstructorDecl(ofClass(hasName("Target"))));
}

TEST_P(UncheckedOptionalAccessTest, CtorInitializerValue) {
  using namespace ast_matchers;
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"

    struct Target {
      Target(): opt(3) {
        opt.value();
      }
      $ns::$optional<int> opt;
    };
  )",
      cxxConstructorDecl(ofClass(hasName("Target"))));
}

// This is regression test, it shouldn't crash.
TEST_P(UncheckedOptionalAccessTest, Bitfield) {
  using namespace ast_matchers;
  ExpectDiagnosticsFor(
      R"(
    #include "unchecked_optional_access_test.h"
    struct Dst {
      unsigned int n : 1;
    };
    void target() {
      $ns::$optional<bool> v;
      Dst d;
      if (v.has_value())
        d.n = v.value();
    }
  )");
}
// FIXME: Add support for:
// - constructors (copy, move)
// - assignment operators (default, copy, move)
// - invalidation (passing optional by non-const reference/pointer)
