File
Blob: src/workerd/jsg/web-idl.h
| 1 | // Copyright (c) 2017-2022 Cloudflare, Inc. |
| 2 | // Licensed under the Apache 2.0 license found in the LICENSE file or at: |
| 3 | // https://opensource.org/licenses/Apache-2.0 |
| 4 | |
| 5 | #pragma once |
| 6 | // INTERNAL IMPLEMENTATION FILE |
| 7 | // |
| 8 | // Type traits and concepts to help us map between C++ and Web IDL types. |
| 9 | |
| 10 | #include <workerd/jsg/jsg.h> |
| 11 | #include <workerd/jsg/meta.h> |
| 12 | |
| 13 | #include <kj/array.h> |
| 14 | #include <kj/common.h> |
| 15 | #include <kj/one-of.h> |
| 16 | |
| 17 | namespace workerd::jsg::webidl { |
| 18 | |
| 19 | // ======================================================================================= |
| 20 | // Base detection concepts and helpers |
| 21 | |
| 22 | // True if T has a JSG_KIND static member (i.e., is a JSG type). |
| 23 | template <typename T> |
| 24 | concept HasJsgKind = requires { T::JSG_KIND; }; |
| 25 | |
| 26 | // Helper to detect and unwrap Ref<T> types |
| 27 | template <typename T> |
| 28 | struct RefTraits_ { |
| 29 | static constexpr bool isRef = false; |
| 30 | }; |
| 31 | template <typename T> |
| 32 | struct RefTraits_<Ref<T>> { |
| 33 | static constexpr bool isRef = true; |
| 34 | using Type = T; |
| 35 | }; |
| 36 | |
| 37 | template <typename T> |
| 38 | concept IsRef = RefTraits_<T>::isRef; |
| 39 | |
| 40 | template <IsRef T> |
| 41 | using RefType = RefTraits_<T>::Type; |
| 42 | |
| 43 | // ======================================================================================= |
| 44 | // Optional type detection |
| 45 | |
| 46 | template <typename T> |
| 47 | constexpr bool isOptional = false; |
| 48 | template <typename T> |
| 49 | constexpr bool isOptional<Optional<T>> = true; |
| 50 | template <typename T> |
| 51 | constexpr bool isOptional<LenientOptional<T>> = true; |
| 52 | |
| 53 | template <typename T> |
| 54 | concept OptionalType = isOptional<T>; |
| 55 | |
| 56 | // Counts the number of Web IDL nullable types (modeled with kj::Maybe in JSG) that exist in |
| 57 | // `T...`. This variable template is designed to accept unflattened OneOfs -- it will recurse |
| 58 | // manually through the OneOfs, meaning `nullableTypeCount<Maybe<OneOf<Maybe<U>>>> == 2`. |
| 59 | // |
| 60 | // Implements the "number of nullable member types" algorithm defined here: |
| 61 | // https://heycam.github.io/webidl/#dfn-number-of-nullable-member-types |
| 62 | template <typename... T> |
| 63 | constexpr size_t nullableTypeCount = 0; |
| 64 | |
| 65 | template <typename T, typename... U> |
| 66 | constexpr size_t nullableTypeCount<T, U...> = nullableTypeCount<U...>; |
| 67 | template <typename T, typename... U> |
| 68 | constexpr size_t nullableTypeCount<kj::Maybe<T>, U...> = |
| 69 | 1 + nullableTypeCount<T> + nullableTypeCount<U...>; |
| 70 | template <typename... T, typename... U> |
| 71 | constexpr size_t nullableTypeCount<kj::OneOf<T...>, U...> = |
| 72 | nullableTypeCount<T...> + nullableTypeCount<U...>; |
| 73 | // TODO(soon): What to do with Optional? Unwrap? Hard error? It's not nullable. |
| 74 | |
| 75 | // ======================================================================================= |
| 76 | // Distinguishable type categories |
| 77 | // |
| 78 | // Web IDL defines nine different categories of distinguishable types, which are used to validate |
| 79 | // union types. For a basic example, consider `kj::OneOf<double, int>`. From Web IDL's perspective, |
| 80 | // these are both numeric types, thus the union is invalid. |
| 81 | // |
| 82 | // Note that these categories do not cover all Web IDL types, like Promises. Such types are not |
| 83 | // allowed in unions under any circumstances. |
| 84 | |
| 85 | // True if T is a Web IDL dictionary type (modeled with JSG_STRUCT). |
| 86 | template <typename T> |
| 87 | concept DictionaryType = HasJsgKind<T> && (T::JSG_KIND == JsgKind::STRUCT); |
| 88 | |
| 89 | // Note: This covers Web IDL exception types as well. This doesn't seem to be a problem in practice, |
| 90 | // but it's worth knowing that the Web IDL spec considers the two categories distinct. |
| 91 | template <typename T> |
| 92 | concept NonCallbackInterfaceType_ = HasJsgKind<T> && (T::JSG_KIND == JsgKind::RESOURCE); |
| 93 | |
| 94 | // Helper to check if Ref<T> wraps a resource type |
| 95 | template <typename T> |
| 96 | constexpr bool isRefToResource_() { |
| 97 | if constexpr (IsRef<T>) { |
| 98 | return NonCallbackInterfaceType_<RefType<T>>; |
| 99 | } else { |
| 100 | return false; |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | // True if T is a Web IDL non-callback interface type (modeled with JSG_RESOURCE). |
| 105 | // Handles both T and Ref<T> cases. |
| 106 | template <typename T> |
| 107 | concept NonCallbackInterfaceType = NonCallbackInterfaceType_<T> || isRefToResource_<T>(); |
| 108 | |
| 109 | template <typename T> |
| 110 | concept BufferSourceType = kj::isSameType<T, kj::Array<kj::byte>>() || |
| 111 | kj::isSameType<T, kj::ArrayPtr<kj::byte>>() || kj::isSameType<T, kj::Array<const kj::byte>>() || |
| 112 | kj::isSameType<T, kj::ArrayPtr<const kj::byte>>() || kj::isSameType<T, jsg::BufferSource>(); |
| 113 | |
| 114 | // Helper for record type detection |
| 115 | template <typename T> |
| 116 | struct IsRecordType_: std::false_type {}; |
| 117 | template <typename K, typename V> |
| 118 | struct IsRecordType_<Dict<V, K>>: std::true_type {}; |
| 119 | |
| 120 | template <typename T> |
| 121 | concept RecordType = IsRecordType_<T>::value; |
| 122 | |
| 123 | template <typename T> |
| 124 | concept BooleanType = StrictlyBool<T> || kj::isSameType<T, NonCoercible<bool>>(); |
| 125 | |
| 126 | template <typename T> |
| 127 | concept IntegerType = kj::isSameType<T, int8_t>() || kj::isSameType<T, int16_t>() || |
| 128 | kj::isSameType<T, int>() || kj::isSameType<T, int64_t>() || kj::isSameType<T, uint8_t>() || |
| 129 | kj::isSameType<T, uint16_t>() || kj::isSameType<T, uint32_t>() || |
| 130 | kj::isSameType<T, uint64_t>() || kj::isSameType<T, v8::Local<v8::BigInt>>(); |
| 131 | |
| 132 | template <typename T> |
| 133 | concept NumericType = |
| 134 | IntegerType<T> || kj::isSameType<T, double>() || kj::isSameType<T, NonCoercible<double>>(); |
| 135 | |
| 136 | template <typename T> |
| 137 | concept StringType = kj::isSameType<T, kj::String>() || kj::isSameType<T, USVString>() || |
| 138 | kj::isSameType<T, DOMString>() || kj::isSameType<T, v8::Local<v8::String>>() || |
| 139 | kj::isSameType<T, jsg::V8Ref<v8::String>>() || kj::isSameType<T, NonCoercible<kj::String>>() || |
| 140 | kj::isSameType<T, NonCoercible<USVString>>() || kj::isSameType<T, NonCoercible<DOMString>>() || |
| 141 | kj::isSameType<T, jsg::JsString>(); |
| 142 | |
| 143 | template <typename T> |
| 144 | concept ObjectType = |
| 145 | kj::isSameType<T, v8::Local<v8::Object>>() || kj::isSameType<T, v8::Global<v8::Object>>(); |
| 146 | |
| 147 | template <typename T> |
| 148 | concept SymbolType = false; |
| 149 | // TODO(soon): kj::isSameType<T, v8::Local<v8::Symbol>>()? |
| 150 | |
| 151 | // Helper for callback function type detection |
| 152 | template <typename T> |
| 153 | struct IsCallbackFunctionType_: std::false_type {}; |
| 154 | template <typename T> |
| 155 | struct IsCallbackFunctionType_<kj::Function<T>>: std::true_type {}; |
| 156 | template <typename T> |
| 157 | struct IsCallbackFunctionType_<Constructor<T>>: std::true_type {}; |
| 158 | |
| 159 | template <typename T> |
| 160 | concept CallbackFunctionType = IsCallbackFunctionType_<T>::value; |
| 161 | |
| 162 | // True if T is a Web IDL buffer source type, exception type, or non-callback interface type. The |
| 163 | // latter two cases are both modeled with JSG_RESOURCE_TYPE, which is why this trait only has two |
| 164 | // predicates, rather than three. |
| 165 | template <typename T> |
| 166 | concept InterfaceLikeType = BufferSourceType<T> || NonCallbackInterfaceType<T>; |
| 167 | |
| 168 | // TODO(someday): Or callback interface types. Callback interface types seem to be going the way of |
| 169 | // the dodo -- fingers crossed that we won't have to implement them. |
| 170 | template <typename T> |
| 171 | concept DictionaryLikeType = DictionaryType<T> || RecordType<T>; |
| 172 | |
| 173 | // Helper for sequence-like type detection |
| 174 | template <typename T> |
| 175 | struct IsSequenceLikeType_: std::false_type {}; |
| 176 | template <typename T> |
| 177 | struct IsSequenceLikeType_<kj::Array<T>> |
| 178 | : std::bool_constant<!kj::isSameType<T, kj::byte>() && !kj::isSameType<T, const kj::byte>()> {}; |
| 179 | template <typename T> |
| 180 | struct IsSequenceLikeType_<Sequence<T>>: std::true_type {}; |
| 181 | |
| 182 | // TODO(soon): And frozen array types. |
| 183 | template <typename T> |
| 184 | concept SequenceLikeType = IsSequenceLikeType_<T>::value; |
| 185 | |
| 186 | // True if T is listed in the table in Web IDL's distinguishable type algorithm: |
| 187 | // https://heycam.github.io/webidl/#dfn-distinguishable, step 4. |
| 188 | template <typename T> |
| 189 | concept DistinguishableType = |
| 190 | BooleanType<T> || NumericType<T> || StringType<T> || ObjectType<T> || SymbolType<T> || |
| 191 | InterfaceLikeType<T> || CallbackFunctionType<T> || DictionaryLikeType<T> || SequenceLikeType<T>; |
| 192 | |
| 193 | template <typename T> |
| 194 | concept IndistinguishableType = !DistinguishableType<T>; |
| 195 | |
| 196 | // ======================================================================================= |
| 197 | // Backward-compatible variable templates |
| 198 | // |
| 199 | // These provide backward compatibility with code that uses the old constexpr bool style |
| 200 | // that cannot use the concepts directly. |
| 201 | |
| 202 | template <typename T> |
| 203 | constexpr bool isNonCallbackInterfaceType = NonCallbackInterfaceType<T>; |
| 204 | template <typename T> |
| 205 | constexpr bool isRecordType = RecordType<T>; |
| 206 | template <typename T> |
| 207 | constexpr bool isBooleanType = BooleanType<T>; |
| 208 | template <typename T> |
| 209 | constexpr bool isNumericType = NumericType<T>; |
| 210 | template <typename T> |
| 211 | constexpr bool isStringType = StringType<T>; |
| 212 | |
| 213 | // ======================================================================================= |
| 214 | // Type list utilities |
| 215 | |
| 216 | template <typename... T> |
| 217 | constexpr bool hasDuplicateTypes = false; |
| 218 | template <typename T, typename U, typename... V> |
| 219 | constexpr bool hasDuplicateTypes<T, U, V...> = |
| 220 | kj::isSameType<T, U>() || hasDuplicateTypes<T, V...> || hasDuplicateTypes<U, V...>; |
| 221 | |
| 222 | // Traits computed over a flattened type list. Used for Web IDL union validation. |
| 223 | // Uses the concept-based variable templates for counting. |
| 224 | template <typename... T> |
| 225 | struct FlattenedTypeTraits_ { |
| 226 | static constexpr size_t dictionaryTypeCount = (static_cast<size_t>(DictionaryType<T>) + ...); |
| 227 | static constexpr size_t booleanTypeCount = (static_cast<size_t>(BooleanType<T>) + ...); |
| 228 | static constexpr size_t numericTypeCount = (static_cast<size_t>(NumericType<T>) + ...); |
| 229 | static constexpr size_t stringTypeCount = (static_cast<size_t>(StringType<T>) + ...); |
| 230 | static constexpr size_t objectTypeCount = (static_cast<size_t>(ObjectType<T>) + ...); |
| 231 | static constexpr size_t symbolTypeCount = (static_cast<size_t>(SymbolType<T>) + ...); |
| 232 | static constexpr size_t interfaceLikeTypeCount = |
| 233 | (static_cast<size_t>(InterfaceLikeType<T>) + ...); |
| 234 | static constexpr size_t callbackFunctionTypeCount = |
| 235 | (static_cast<size_t>(CallbackFunctionType<T>) + ...); |
| 236 | static constexpr size_t dictionaryLikeTypeCount = |
| 237 | (static_cast<size_t>(DictionaryLikeType<T>) + ...); |
| 238 | static constexpr size_t sequenceLikeTypeCount = (static_cast<size_t>(SequenceLikeType<T>) + ...); |
| 239 | |
| 240 | static constexpr bool hasDuplicateTypes = webidl::hasDuplicateTypes<T...>; |
| 241 | static constexpr bool hasIndistinguishableTypes = (IndistinguishableType<T> || ...); |
| 242 | static constexpr bool hasOptionalTypes = (OptionalType<T> || ...); |
| 243 | }; |
| 244 | |
| 245 | template <typename Traits, typename... T> |
| 246 | struct Flatten; |
| 247 | template <typename Traits> |
| 248 | struct Flatten<Traits>: Traits {}; |
| 249 | template <template <typename...> class Traits, typename... T, typename U, typename... V> |
| 250 | struct Flatten<Traits<T...>, U, V...>: Flatten<Traits<T..., U>, V...> {}; |
| 251 | template <template <typename...> class Traits, typename... T, typename U, typename... V> |
| 252 | struct Flatten<Traits<T...>, Ref<U>, V...>: Flatten<Traits<T...>, U, V...> {}; |
| 253 | template <template <typename...> class Traits, typename... T, typename U, typename... V> |
| 254 | struct Flatten<Traits<T...>, kj::Maybe<U>, V...>: Flatten<Traits<T...>, U, V...> {}; |
| 255 | template <template <typename...> class Traits, typename... T, typename... U, typename... V> |
| 256 | struct Flatten<Traits<T...>, kj::OneOf<U...>, V...>: Flatten<Traits<T...>, U..., V...> {}; |
| 257 | |
| 258 | // Flattens a list of types (recursively unwraps Maybes and OneOfs) and exposes some data about |
| 259 | // those types: number of dictionary types, whether or not there are duplicate types, presence of |
| 260 | // indistinguishable types, etc. |
| 261 | // |
| 262 | // Note: Web IDL dictates that we flatten nullables (Maybe) and unions (OneOf). We add one more |
| 263 | // flattening: Ref<T> -> T. We do this because JSG has two models for non-callback interface |
| 264 | // types: Ref<T> (unwrapped by reference) and T (unwrapped by copy/move). We need to be able to |
| 265 | // catch ambiguous OneOfs like `kj::OneOf<Interface, Ref<Interface>>`. |
| 266 | template <typename... T> |
| 267 | using FlattenedTypeTraits = Flatten<FlattenedTypeTraits_<>, T...>; |
| 268 | |
| 269 | // Instantiate to check that the type T satisfies the constraints on union types prescribed by |
| 270 | // Web IDL spec: https://heycam.github.io/webidl/#idl-union |
| 271 | template <typename T> |
| 272 | struct UnionTypeValidator { |
| 273 | using Traits = FlattenedTypeTraits<T>; |
| 274 | |
| 275 | static_assert(nullableTypeCount<T> + Traits::dictionaryTypeCount <= 1, |
| 276 | "A Web IDL union (OneOf) may contain at most one nullable or dictionary type."); |
| 277 | |
| 278 | static_assert(Traits::booleanTypeCount <= 1, |
| 279 | "A Web IDL union (OneOf) may contain at most one boolean type."); |
| 280 | static_assert(Traits::numericTypeCount <= 1, |
| 281 | "A Web IDL union (OneOf) may contain at most one numeric type."); |
| 282 | static_assert( |
| 283 | Traits::stringTypeCount <= 1, "A Web IDL union (OneOf) may contain at most one string type."); |
| 284 | static_assert( |
| 285 | Traits::objectTypeCount <= 1, "A Web IDL union (OneOf) may contain at most one object type."); |
| 286 | static_assert(Traits::objectTypeCount == 0 || |
| 287 | Traits::interfaceLikeTypeCount + Traits::callbackFunctionTypeCount + |
| 288 | Traits::dictionaryLikeTypeCount + Traits::sequenceLikeTypeCount == |
| 289 | 0, |
| 290 | "A Web IDL union (OneOf) may contain an object type only if it also contains no " |
| 291 | "interface-like, callback function, dictionary-like, or sequence-like types."); |
| 292 | static_assert( |
| 293 | Traits::symbolTypeCount <= 1, "A Web IDL union (OneOf) may contain at most one symbol type."); |
| 294 | static_assert(Traits::callbackFunctionTypeCount <= 1, |
| 295 | "A Web IDL union (OneOf) may contain at most one callback function type."); |
| 296 | // TODO(cleanup): This next check made it impossible to define a type for named top-level module |
| 297 | // exports, which are allowed to be objects or classes. I don't understand why this restriction |
| 298 | // existed since it's definitely possible to distinguish a function from a non-function. Do |
| 299 | // we really need to be enforcing WebIDL rules to the letter even when our type system is more |
| 300 | // expressive? |
| 301 | // static_assert(Traits::callbackFunctionTypeCount == 0 || Traits::dictionaryLikeTypeCount == 0, |
| 302 | // "A Web IDL union (OneOf) may contain a callback function type only if it also contains no " |
| 303 | // "dictionary-like types."); |
| 304 | static_assert(Traits::dictionaryLikeTypeCount <= 1, |
| 305 | "A Web IDL union (OneOf) may contain at most one dictionary-like type."); |
| 306 | static_assert(Traits::sequenceLikeTypeCount <= 1, |
| 307 | "A Web IDL union (OneOf) may contain at most one sequence-like type."); |
| 308 | |
| 309 | // There is no `Traits::interfaceLikeTypeCount <= 1` check because Web IDL unions can have |
| 310 | // multiple interface-like types as long as: |
| 311 | // |
| 312 | // 1. They are not the same type. |
| 313 | // 2. No single platform object implements more than one of the interfaces in question. |
| 314 | // |
| 315 | // Condition (1) will be taken care of with the `hasDuplicateTypes` check below (and is why |
| 316 | // `FlattenedTypeTraits` unwraps `Ref`s). Condition (2) is difficult to guarantee, but unless |
| 317 | // we start using multiple-inheritance in our API implementation types, we should be safe. |
| 318 | |
| 319 | static_assert( |
| 320 | !Traits::hasDuplicateTypes, "A Web IDL union (OneOf) may not contain duplicate types."); |
| 321 | // TODO(cleanup): This rule is incompatible with addEventListener(), whose second argument is |
| 322 | // allowed to be either a function or an object with a `handleEvent()` method. If such a |
| 323 | // fundamental web interface violates this rule, should we really be enforcing it? |
| 324 | // static_assert(!Traits::hasIndistinguishableTypes, |
| 325 | // "A Web IDL union (OneOf) may only contain distinguishable types, i.e., types which fall " |
| 326 | // "into one of the following categories: boolean, numeric, string, object, symbol, " |
| 327 | // "interface-like, callback function, dictionary-like, or sequence-like. See the definition " |
| 328 | // "of 'distinguishable' in the Web IDL spec for details."); |
| 329 | static_assert(!Traits::hasOptionalTypes, |
| 330 | "A Web IDL union (OneOf) may not contain any Optional<T> types. Optional<T> must only be " |
| 331 | "used to mark optional function/method parameters and non-required members of a " |
| 332 | "dictionary. Use Maybe<T> to represent nullable types."); |
| 333 | }; |
| 334 | |
| 335 | } // namespace workerd::jsg::webidl |