Skip to content
File

Blob: src/workerd/jsg/web-idl.h

cpp336 lines
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 
17namespace 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).
23template <typename T>
24concept HasJsgKind = requires { T::JSG_KIND; };
25 
26// Helper to detect and unwrap Ref<T> types
27template <typename T>
28struct RefTraits_ {
29 static constexpr bool isRef = false;
30};
31template <typename T>
32struct RefTraits_<Ref<T>> {
33 static constexpr bool isRef = true;
34 using Type = T;
35};
36 
37template <typename T>
38concept IsRef = RefTraits_<T>::isRef;
39 
40template <IsRef T>
41using RefType = RefTraits_<T>::Type;
42 
43// =======================================================================================
44// Optional type detection
45 
46template <typename T>
47constexpr bool isOptional = false;
48template <typename T>
49constexpr bool isOptional<Optional<T>> = true;
50template <typename T>
51constexpr bool isOptional<LenientOptional<T>> = true;
52 
53template <typename T>
54concept 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
62template <typename... T>
63constexpr size_t nullableTypeCount = 0;
64 
65template <typename T, typename... U>
66constexpr size_t nullableTypeCount<T, U...> = nullableTypeCount<U...>;
67template <typename T, typename... U>
68constexpr size_t nullableTypeCount<kj::Maybe<T>, U...> =
69 1 + nullableTypeCount<T> + nullableTypeCount<U...>;
70template <typename... T, typename... U>
71constexpr 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).
86template <typename T>
87concept 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.
91template <typename T>
92concept NonCallbackInterfaceType_ = HasJsgKind<T> && (T::JSG_KIND == JsgKind::RESOURCE);
93 
94// Helper to check if Ref<T> wraps a resource type
95template <typename T>
96constexpr 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.
106template <typename T>
107concept NonCallbackInterfaceType = NonCallbackInterfaceType_<T> || isRefToResource_<T>();
108 
109template <typename T>
110concept 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
115template <typename T>
116struct IsRecordType_: std::false_type {};
117template <typename K, typename V>
118struct IsRecordType_<Dict<V, K>>: std::true_type {};
119 
120template <typename T>
121concept RecordType = IsRecordType_<T>::value;
122 
123template <typename T>
124concept BooleanType = StrictlyBool<T> || kj::isSameType<T, NonCoercible<bool>>();
125 
126template <typename T>
127concept 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 
132template <typename T>
133concept NumericType =
134 IntegerType<T> || kj::isSameType<T, double>() || kj::isSameType<T, NonCoercible<double>>();
135 
136template <typename T>
137concept 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 
143template <typename T>
144concept ObjectType =
145 kj::isSameType<T, v8::Local<v8::Object>>() || kj::isSameType<T, v8::Global<v8::Object>>();
146 
147template <typename T>
148concept SymbolType = false;
149// TODO(soon): kj::isSameType<T, v8::Local<v8::Symbol>>()?
150 
151// Helper for callback function type detection
152template <typename T>
153struct IsCallbackFunctionType_: std::false_type {};
154template <typename T>
155struct IsCallbackFunctionType_<kj::Function<T>>: std::true_type {};
156template <typename T>
157struct IsCallbackFunctionType_<Constructor<T>>: std::true_type {};
158 
159template <typename T>
160concept 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.
165template <typename T>
166concept 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.
170template <typename T>
171concept DictionaryLikeType = DictionaryType<T> || RecordType<T>;
172 
173// Helper for sequence-like type detection
174template <typename T>
175struct IsSequenceLikeType_: std::false_type {};
176template <typename T>
177struct IsSequenceLikeType_<kj::Array<T>>
178 : std::bool_constant<!kj::isSameType<T, kj::byte>() && !kj::isSameType<T, const kj::byte>()> {};
179template <typename T>
180struct IsSequenceLikeType_<Sequence<T>>: std::true_type {};
181 
182// TODO(soon): And frozen array types.
183template <typename T>
184concept 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.
188template <typename T>
189concept DistinguishableType =
190 BooleanType<T> || NumericType<T> || StringType<T> || ObjectType<T> || SymbolType<T> ||
191 InterfaceLikeType<T> || CallbackFunctionType<T> || DictionaryLikeType<T> || SequenceLikeType<T>;
192 
193template <typename T>
194concept 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 
202template <typename T>
203constexpr bool isNonCallbackInterfaceType = NonCallbackInterfaceType<T>;
204template <typename T>
205constexpr bool isRecordType = RecordType<T>;
206template <typename T>
207constexpr bool isBooleanType = BooleanType<T>;
208template <typename T>
209constexpr bool isNumericType = NumericType<T>;
210template <typename T>
211constexpr bool isStringType = StringType<T>;
212 
213// =======================================================================================
214// Type list utilities
215 
216template <typename... T>
217constexpr bool hasDuplicateTypes = false;
218template <typename T, typename U, typename... V>
219constexpr 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.
224template <typename... T>
225struct 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 
245template <typename Traits, typename... T>
246struct Flatten;
247template <typename Traits>
248struct Flatten<Traits>: Traits {};
249template <template <typename...> class Traits, typename... T, typename U, typename... V>
250struct Flatten<Traits<T...>, U, V...>: Flatten<Traits<T..., U>, V...> {};
251template <template <typename...> class Traits, typename... T, typename U, typename... V>
252struct Flatten<Traits<T...>, Ref<U>, V...>: Flatten<Traits<T...>, U, V...> {};
253template <template <typename...> class Traits, typename... T, typename U, typename... V>
254struct Flatten<Traits<T...>, kj::Maybe<U>, V...>: Flatten<Traits<T...>, U, V...> {};
255template <template <typename...> class Traits, typename... T, typename... U, typename... V>
256struct 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>>`.
266template <typename... T>
267using 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
271template <typename T>
272struct 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