// file : libbuild2/variable.txx -*- C++ -*- // license : MIT; see accompanying LICENSE file #include namespace build2 { template bool lookup:: belongs (const T& x, bool t) const { if (vars == &x.vars) return true; if (t) { for (const auto& p1: x.target_vars) // variable_type_map { for (const auto& p2: p1.second) // variable_pattern_map { if (vars == &p2.second) return true; } } } return false; } [[noreturn]] LIBBUILD2_SYMEXPORT void convert_throw (const value_type* from, const value_type& to); template T convert (value&& v) { if (v) { if (v.type == nullptr) return convert (move (v).as ()); else if (v.type == &value_traits::value_type) return move (v).as (); } convert_throw (v ? v.type : nullptr, value_traits::value_type); } template T convert (const value& v) { if (v) { if (v.type == nullptr) return convert (names (v.as ())); else if (v.type == &value_traits::value_type) return v.as (); } convert_throw (v ? v.type : nullptr, value_traits::value_type); } template void default_dtor (value& v) { v.as ().~T (); } template void default_copy_ctor (value& l, const value& r, bool m) { if (m) new (&l.data_) T (move (const_cast (r).as ())); else new (&l.data_) T (r.as ()); } template void default_copy_assign (value& l, const value& r, bool m) { if (m) l.as () = move (const_cast (r).as ()); else l.as () = r.as (); } template bool default_empty (const value& v) { return value_traits::empty (v.as ()); } template void simple_assign (value& v, names&& ns, const variable* var) { size_t n (ns.size ()); diag_record dr; if (value_traits::empty_value ? n <= 1 : n == 1) { try { value_traits::assign ( v, (n == 0 ? T () : value_traits::convert (move (ns.front ()), nullptr))); } catch (const invalid_argument& e) { dr << fail << e; } } else dr << fail << "invalid " << value_traits::value_type.name << " value: " << (n == 0 ? "empty" : "multiple names"); if (!dr.empty ()) { if (var != nullptr) dr << " in variable " << var->name; dr << info << "while converting '" << ns << "'"; } } template void simple_append (value& v, names&& ns, const variable* var) { size_t n (ns.size ()); diag_record dr; if (value_traits::empty_value ? n <= 1 : n == 1) { try { value_traits::append ( v, (n == 0 ? T () : value_traits::convert (move (ns.front ()), nullptr))); } catch (const invalid_argument& e) { dr << fail << e; } } else dr << fail << "invalid " << value_traits::value_type.name << " value: " << (n == 0 ? "empty" : "multiple names"); if (!dr.empty ()) { if (var != nullptr) dr << " in variable " << var->name; dr << info << "while converting '" << ns << "'"; } } template void simple_prepend (value& v, names&& ns, const variable* var) { size_t n (ns.size ()); diag_record dr; if (value_traits::empty_value ? n <= 1 : n == 1) { try { value_traits::prepend ( v, (n == 0 ? T () : value_traits::convert (move (ns.front ()), nullptr))); } catch (const invalid_argument& e) { dr << fail << e; } } else dr << fail << "invalid " << value_traits::value_type.name << " value: " << (n == 0 ? "empty" : "multiple names"); if (!dr.empty ()) { if (var != nullptr) dr << " in variable " << var->name; dr << info << "while converting '" << ns << "'"; } } template names_view simple_reverse (const value& v, names& s, bool reduce) { const T& x (v.as ()); // Unless requested otherwise, represent an empty simple value as empty // name sequence rather than a single empty name. This way, for example, // during serialization we end up with a much saner looking: // // config.import.foo = // // Rather than: // // config.import.foo = {} // if (!value_traits::empty (x)) s.emplace_back (value_traits::reverse (x)); else if (!reduce) s.push_back (name ()); return s; } template int simple_compare (const value& l, const value& r) { return value_traits::compare (l.as (), r.as ()); } // pair value // template pair pair_value_traits:: convert (name&& l, name* r, const char* type, const char* what, const variable* var) { if (!l.pair) { diag_record dr (fail); dr << type << ' ' << what << (*what != '\0' ? " " : "") << "pair expected instead of '" << l << "'"; if (var != nullptr) dr << " in variable " << var->name; } if (l.pair != '@') { diag_record dr (fail); dr << "unexpected pair style for " << type << ' ' << what << (*what != '\0' ? " " : "") << "key-value pair '" << l << "'" << l.pair << "'" << *r << "'"; if (var != nullptr) dr << " in variable " << var->name; } try { F f (value_traits::convert (move (l), nullptr)); try { S s (value_traits::convert (move (*r), nullptr)); return pair (move (f), move (s)); } catch (const invalid_argument& e) { diag_record dr (fail); dr << e; if (var != nullptr) dr << " in variable " << var->name; dr << info << "while converting second have of pair '" << *r << "'" << endf; } } catch (const invalid_argument& e) { diag_record dr (fail); dr << e; if (var != nullptr) dr << " in variable " << var->name; dr << info << "while converting first have of pair '" << l << "'" << endf; } } template pair> pair_value_traits>:: convert (name&& l, name* r, const char* type, const char* what, const variable* var) { if (l.pair && l.pair != '@') { diag_record dr (fail); dr << "unexpected pair style for " << type << ' ' << what << (*what != '\0' ? " " : "") << "key-value pair '" << l << "'" << l.pair << "'" << *r << "'"; if (var != nullptr) dr << " in variable " << var->name; } try { F f (value_traits::convert (move (l), nullptr)); try { optional s; if (l.pair) s = value_traits::convert (move (*r), nullptr); return pair> (move (f), move (s)); } catch (const invalid_argument& e) { diag_record dr (fail); dr << e; if (var != nullptr) dr << " in variable " << var->name; dr << info << "while converting second have of pair '" << *r << "'" << endf; } } catch (const invalid_argument& e) { diag_record dr (fail); dr << e; if (var != nullptr) dr << " in variable " << var->name; dr << info << "while converting first have of pair '" << l << "'" << endf; } } template pair, S> pair_value_traits, S>:: convert (name&& l, name* r, const char* type, const char* what, const variable* var) { if (l.pair && l.pair != '@') { diag_record dr (fail); dr << "unexpected pair style for " << type << ' ' << what << (*what != '\0' ? " " : "") << "key-value pair '" << l << "'" << l.pair << "'" << *r << "'"; if (var != nullptr) dr << " in variable " << var->name; } try { optional f; if (l.pair) { f = value_traits::convert (move (l), nullptr); l = move (*r); // Shift. } try { S s (value_traits::convert (move (l), nullptr)); return pair, S> (move (f), move (s)); } catch (const invalid_argument& e) { diag_record dr (fail); dr << e; if (var != nullptr) dr << " in variable " << var->name; dr << info << "while converting second have of pair '" << *r << "'" << endf; } } catch (const invalid_argument& e) { diag_record dr (fail); dr << e; if (var != nullptr) dr << " in variable " << var->name; dr << info << "while converting first have of pair '" << l << "'" << endf; } } template void pair_value_traits:: reverse (const F& f, const S& s, names& ns) { ns.push_back (value_traits::reverse (f)); ns.back ().pair = '@'; ns.push_back (value_traits::reverse (s)); } template void pair_value_traits>:: reverse (const F& f, const optional& s, names& ns) { ns.push_back (value_traits::reverse (f)); if (s) { ns.back ().pair = '@'; ns.push_back (value_traits::reverse (*s)); } } template void pair_value_traits, S>:: reverse (const optional& f, const S& s, names& ns) { if (f) { ns.push_back (value_traits::reverse (*f)); ns.back ().pair = '@'; } ns.push_back (value_traits::reverse (s)); } // vector value // template vector value_traits>:: convert (names&& ns) { vector v; v.reserve (ns.size ()); // Normally there won't be any pairs. // Similar to vector_append() below except we throw instead of issuing // diagnostics. // for (auto i (ns.begin ()); i != ns.end (); ++i) { name& n (*i); name* r (nullptr); if (n.pair) { r = &*++i; if (n.pair != '@') throw invalid_argument ( string ("invalid pair character: '") + n.pair + '\''); } v.push_back (value_traits::convert (move (n), r)); } return v; } template void vector_append (value& v, names&& ns, const variable* var) { vector& p (v ? v.as> () : *new (&v.data_) vector ()); p.reserve (p.size () + ns.size ()); // Normally there won't be any pairs. // Convert each element to T while merging pairs. // for (auto i (ns.begin ()); i != ns.end (); ++i) { name& n (*i); name* r (nullptr); if (n.pair) { r = &*++i; if (n.pair != '@') { diag_record dr (fail); dr << "unexpected pair style for " << value_traits::value_type.name << " value " << "'" << n << "'" << n.pair << "'" << *r << "'"; if (var != nullptr) dr << " in variable " << var->name; } } try { p.push_back (value_traits::convert (move (n), r)); } catch (const invalid_argument& e) { diag_record dr (fail); dr << e; if (var != nullptr) dr << " in variable " << var->name; dr << info << "while converting "; if (n.pair) dr << " element pair '" << n << "'@'" << *r << "'"; else dr << " element '" << n << "'"; } } } template void vector_assign (value& v, names&& ns, const variable* var) { if (v) v.as> ().clear (); vector_append (v, move (ns), var); } template void vector_prepend (value& v, names&& ns, const variable* var) { // Reduce to append. // vector t; vector* p; if (v) { p = &v.as> (); p->swap (t); } else p = new (&v.data_) vector (); vector_append (v, move (ns), var); p->insert (p->end (), make_move_iterator (t.begin ()), make_move_iterator (t.end ())); } template names_view vector_reverse (const value& v, names& s, bool) { auto& vv (v.as> ()); s.reserve (vv.size ()); for (const T& x: vv) s.push_back (value_traits::reverse (x)); return s; } template int vector_compare (const value& l, const value& r) { auto& lv (l.as> ()); auto& rv (r.as> ()); auto li (lv.begin ()), le (lv.end ()); auto ri (rv.begin ()), re (rv.end ()); for (; li != le && ri != re; ++li, ++ri) if (int r = value_traits::compare (*li, *ri)) return r; if (li == le && ri != re) // l shorter than r. return -1; if (ri == re && li != le) // r shorter than l. return 1; return 0; } // Provide subscript for vector for efficiency. // template value vector_subscript (const value& val, value* val_data, value&& sub, const location& sloc, const location& bloc) { // Process subscript even if the value is null to make sure it is valid. // size_t i; try { i = static_cast (convert (move (sub))); } catch (const invalid_argument& e) { fail (sloc) << "invalid " << value_traits>::value_type.name << " value subscript: " << e << info (bloc) << "use the '\\[' escape sequence if this is a " << "wildcard pattern"; } value r; if (!val.null) { const auto& v (val.as> ()); if (i < v.size ()) { const T& e (v[i]); // Steal the value if possible. // r = &val == val_data ? T (move (const_cast (e))) : T (e); } } // Typify null values so that type-specific subscript (e.g., for // json_value) gets called for chained subscripts. // if (r.null) r.type = &value_traits::value_type; return r; } // Provide iterate for vector for efficiency. // template void vector_iterate (const value& val, const function& f) { const auto& v (val.as> ()); // Never NULL. for (auto b (v.begin ()), i (b), e (v.end ()); i != e; ++i) { f (value (*i), i == b); } } // Make sure these are static-initialized together. Failed that VC will make // sure it's done in the wrong order. // template struct vector_value_type: value_type { string type_name; vector_value_type (value_type&& v) : value_type (move (v)) { // Note: vector always has a convenience alias. // type_name = value_traits::type_name; type_name += 's'; name = type_name.c_str (); } }; template const vector value_traits>::empty_instance; template const vector_value_type value_traits>::value_type = build2::value_type // VC14 wants =. { nullptr, // Patched above. sizeof (vector), nullptr, // No base. true, // Container. &value_traits::value_type, // Element type. &default_dtor>, &default_copy_ctor>, &default_copy_assign>, &vector_assign, &vector_append, &vector_prepend, &vector_reverse, nullptr, // No cast (cast data_ directly). &vector_compare, &default_empty>, &vector_subscript, &vector_iterate }; // vector> value // template void pair_vector_append (value& v, names&& ns, const variable* var) { vector>& p (v ? v.as>> () : *new (&v.data_) vector> ()); // Verify we have a sequence of pairs and convert each lhs/rhs to K/V. // for (auto i (ns.begin ()); i != ns.end (); ++i) { name& l (*i); name* r (l.pair ? &*++i : nullptr); p.push_back (value_traits>::convert ( move (l), r, value_traits>>::value_type.name, "element", var)); } } template void pair_vector_assign (value& v, names&& ns, const variable* var) { if (v) v.as>> ().clear (); pair_vector_append (v, move (ns), var); } template names_view pair_vector_reverse (const value& v, names& s, bool) { auto& vv (v.as>> ()); s.reserve (2 * vv.size ()); for (const auto& p: vv) value_traits>::reverse (p.first, p.second, s); return s; } template int pair_vector_compare (const value& l, const value& r) { auto& lv (l.as>> ()); auto& rv (r.as>> ()); auto li (lv.begin ()), le (lv.end ()); auto ri (rv.begin ()), re (rv.end ()); for (; li != le && ri != re; ++li, ++ri) { if (int r = value_traits>::compare (*li, *ri)) return r; } if (li == le && ri != re) // l shorter than r. return -1; if (ri == re && li != le) // r shorter than l. return 1; return 0; } // Make sure these are static-initialized together. Failed that VC will make // sure it's done in the wrong order. // template struct pair_vector_value_type: value_type { string type_name; pair_vector_value_type (value_type&& v) : value_type (move (v)) { // vector> // type_name = "vector::type_name; type_name += ','; type_name += value_traits::type_name; type_name += ">>"; name = type_name.c_str (); } }; // This is beyond our static initialization order control skills, so we hack // it up for now. // template struct pair_vector_value_type>: value_type { string type_name; pair_vector_value_type (value_type&& v) : value_type (move (v)) { // vector>> // type_name = "vector::type_name; type_name += ",optional<"; type_name += value_traits::type_name; type_name += ">>>"; name = type_name.c_str (); } }; template struct pair_vector_value_type, V>: value_type { string type_name; pair_vector_value_type (value_type&& v) : value_type (move (v)) { // vector,V>> // type_name = "vector::type_name; type_name += ">,"; type_name += value_traits::type_name; type_name += ">>"; name = type_name.c_str (); } }; template const vector> value_traits>>::empty_instance; template const pair_vector_value_type value_traits>>::value_type = build2::value_type // VC14 wants = { nullptr, // Patched above. sizeof (vector>), nullptr, // No base. true, // Container. nullptr, // No element (not named). &default_dtor>>, &default_copy_ctor>>, &default_copy_assign>>, &pair_vector_assign, &pair_vector_append, &pair_vector_append, // Prepend is the same as append. &pair_vector_reverse, nullptr, // No cast (cast data_ directly). &pair_vector_compare, &default_empty>>, nullptr, // Subscript. nullptr // Iterate. }; // set value // template set value_traits>:: convert (names&& ns) { set s; // Similar to set_append() below except we throw instead of issuing // diagnostics. // for (auto i (ns.begin ()); i != ns.end (); ++i) { name& n (*i); name* r (nullptr); if (n.pair) { r = &*++i; if (n.pair != '@') throw invalid_argument ( string ("invalid pair character: '") + n.pair + '\''); } s.insert (value_traits::convert (move (n), r)); } return s; } template void set_append (value& v, names&& ns, const variable* var) { set& s (v ? v.as> () : *new (&v.data_) set ()); // Convert each element to T while merging pairs. // for (auto i (ns.begin ()); i != ns.end (); ++i) { name& n (*i); name* r (nullptr); if (n.pair) { r = &*++i; if (n.pair != '@') { diag_record dr (fail); dr << "unexpected pair style for " << value_traits::value_type.name << " value " << "'" << n << "'" << n.pair << "'" << *r << "'"; if (var != nullptr) dr << " in variable " << var->name; } } try { s.insert (value_traits::convert (move (n), r)); } catch (const invalid_argument& e) { diag_record dr (fail); dr << e; if (var != nullptr) dr << " in variable " << var->name; dr << info << "while converting "; if (n.pair) dr << " element pair '" << n << "'@'" << *r << "'"; else dr << " element '" << n << "'"; } } } template void set_assign (value& v, names&& ns, const variable* var) { if (v) v.as> ().clear (); set_append (v, move (ns), var); } template names_view set_reverse (const value& v, names& s, bool) { auto& sv (v.as> ()); s.reserve (sv.size ()); for (const T& x: sv) s.push_back (value_traits::reverse (x)); return s; } template int set_compare (const value& l, const value& r) { auto& ls (l.as> ()); auto& rs (r.as> ()); auto li (ls.begin ()), le (ls.end ()); auto ri (rs.begin ()), re (rs.end ()); for (; li != le && ri != re; ++li, ++ri) if (int r = value_traits::compare (*li, *ri)) return r; if (li == le && ri != re) // l shorter than r. return -1; if (ri == re && li != le) // r shorter than l. return 1; return 0; } // Map subscript to set::contains(). // template value set_subscript (const value& val, value*, value&& sub, const location& sloc, const location& bloc) { // Process subscript even if the value is null to make sure it is valid. // T k; try { k = convert (move (sub)); } catch (const invalid_argument& e) { fail (sloc) << "invalid " << value_traits>::value_type.name << " value subscript: " << e << info (bloc) << "use the '\\[' escape sequence if this is a " << "wildcard pattern"; } bool r (false); if (!val.null) { const auto& s (val.as> ()); r = s.find (k) != s.end (); } return value (r); } // Provide iterate for set for efficiency. // template void set_iterate (const value& val, const function& f) { const auto& v (val.as> ()); // Never NULL. for (auto b (v.begin ()), i (b), e (v.end ()); i != e; ++i) { f (value (*i), i == b); } } // Make sure these are static-initialized together. Failed that VC will make // sure it's done in the wrong order. // template struct set_value_type: value_type { string type_name; set_value_type (value_type&& v) : value_type (move (v)) { // set // type_name = "set<"; type_name += value_traits::type_name; type_name += '>'; name = type_name.c_str (); } }; // Convenience aliases for certain set cases. // template <> struct set_value_type: value_type { set_value_type (value_type&& v) : value_type (move (v)) { name = "string_set"; } }; template const set value_traits>::empty_instance; template const set_value_type value_traits>::value_type = build2::value_type // VC14 wants =. { nullptr, // Patched above. sizeof (set), nullptr, // No base. true, // Container. &value_traits::value_type, // Element type. &default_dtor>, &default_copy_ctor>, &default_copy_assign>, &set_assign, &set_append, &set_append, // Prepend the same as append. &set_reverse, nullptr, // No cast (cast data_ directly). &set_compare, &default_empty>, &set_subscript, &set_iterate }; // map value // template void map_append (value& v, names&& ns, const variable* var) { map& p (v ? v.as> () : *new (&v.data_) map ()); // Verify we have a sequence of pairs and convert each lhs/rhs to K/V. // for (auto i (ns.begin ()); i != ns.end (); ++i) { name& l (*i); name* r (l.pair ? &*++i : nullptr); pair v (value_traits>::convert ( move (l), r, value_traits>::value_type.name, "element", var)); // Poor man's emplace_or_assign(). // p.emplace (move (v.first), V ()).first->second = move (v.second); } } template void map_prepend (value& v, names&& ns, const variable* var) { map& p (v ? v.as> () : *new (&v.data_) map ()); // Verify we have a sequence of pairs and convert each lhs/rhs to K/V. // for (auto i (ns.begin ()); i != ns.end (); ++i) { name& l (*i); name* r (l.pair ? &*++i : nullptr); pair v (value_traits>::convert ( move (l), r, value_traits>::value_type.name, "element", var)); p.emplace (move (v.first), move (v.second)); } } template void map_assign (value& v, names&& ns, const variable* var) { if (v) v.as> ().clear (); map_append (v, move (ns), var); } template names_view map_reverse (const value& v, names& s, bool) { auto& vm (v.as> ()); s.reserve (2 * vm.size ()); for (const auto& p: vm) value_traits>::reverse (p.first, p.second, s); return s; } template int map_compare (const value& l, const value& r) { auto& lm (l.as> ()); auto& rm (r.as> ()); auto li (lm.begin ()), le (lm.end ()); auto ri (rm.begin ()), re (rm.end ()); for (; li != le && ri != re; ++li, ++ri) { if (int r = value_traits>::compare (*li, *ri)) return r; } if (li == le && ri != re) // l shorter than r. return -1; if (ri == re && li != le) // r shorter than l. return 1; return 0; } // Note that unlike json_value, we don't provide index support for maps. // There are two reasons for this: Firstly, consider map. // Secondly, even something like map may contain integers as // keys (in JSON, there is a strong convention for object member names not // to be integers). Instead, we provide the $keys() function which allows // one to implement an index-based access with a bit of overhead, if needed. // template value map_subscript (const value& val, value* val_data, value&& sub, const location& sloc, const location& bloc) { // Process subscript even if the value is null to make sure it is valid. // K k; try { k = convert (move (sub)); } catch (const invalid_argument& e) { fail (sloc) << "invalid " << value_traits>::value_type.name << " value subscript: " << e << info (bloc) << "use the '\\[' escape sequence if this is a " << "wildcard pattern"; } value r; if (!val.null) { const auto& m (val.as> ()); auto i (m.find (k)); if (i != m.end ()) { // Steal the value if possible. // r = (&val == val_data ? V (move (const_cast (i->second))) : V (i->second)); } } // Typify null values so that type-specific subscript (e.g., for // json_value) gets called for chained subscripts. // if (r.null) r.type = &value_traits::value_type; return r; } // Make sure these are static-initialized together. Failed that VC will make // sure it's done in the wrong order. // template struct map_value_type: value_type { string type_name; map_value_type (value_type&& v) : value_type (move (v)) { // map // type_name = "map<"; type_name += value_traits::type_name; type_name += ','; type_name += value_traits::type_name; type_name += '>'; name = type_name.c_str (); subscript = &map_subscript; } }; // This is beyond our static initialization order control skills, so we hack // it up for now. // template struct map_value_type>: value_type { string type_name; map_value_type (value_type&& v) : value_type (move (v)) { // map> // type_name = "map<"; type_name += value_traits::type_name; type_name += ",optional<"; type_name += value_traits::type_name; type_name += ">>"; name = type_name.c_str (); // @@ TODO: subscript } }; template struct map_value_type, V>: value_type { string type_name; map_value_type (value_type&& v) : value_type (move (v)) { // map,V> // type_name = "map::type_name; type_name += ">,"; type_name += value_traits::type_name; type_name += '>'; name = type_name.c_str (); // @@ TODO: subscript } }; // Convenience aliases for certain map cases. // template <> struct map_value_type: value_type { map_value_type (value_type&& v) : value_type (move (v)) { name = "string_map"; subscript = &map_subscript; } }; template const map value_traits>::empty_instance; // Note that custom iteration would be better (more efficient, return typed // value), but we don't yet have pair<> as value type so we let the generic // implementation return an untyped pair. // // BTW, one negative consequence of returning untyped pair is that // $first()/$second() don't return types values either, which is quite // unfortunate for something like json_map. // template const map_value_type value_traits>::value_type = build2::value_type // VC14 wants = { nullptr, // Patched above. sizeof (map), nullptr, // No base. true, // Container. nullptr, // No element (pair<> not a value type yet). &default_dtor>, &default_copy_ctor>, &default_copy_assign>, &map_assign, &map_append, &map_prepend, &map_reverse, nullptr, // No cast (cast data_ directly). &map_compare, &default_empty>, nullptr, // Subscript (patched in by map_value_type above). nullptr // Iterate. }; // variable_cache // template pair variable_cache:: insert (context& ctx, K k, const lookup& stem, size_t bver, const variable& var) { using value_data = variable_map::value_data; const variable_map* svars (stem.vars); // NULL if undefined. size_t sver (stem.defined () ? static_cast (stem.value)->version : 0); shared_mutex& m ( ctx.mutexes->variable_cache[ hash () (this) % ctx.mutexes->variable_cache_size]); slock sl (m); ulock ul (m, defer_lock); auto i (m_.find (k)); // Cache hit. // if (i != m_.end () && i->second.base_version == bver && i->second.stem_vars == svars && i->second.stem_version == sver && (var.type == nullptr || i->second.value.type == var.type)) return pair (i->second.value, move (ul)); // Relock for exclusive access. Note that it is entirely possible // that between unlock and lock someone else has updated the entry. // sl.unlock (); ul.lock (); // Note that the cache entries are never removed so we can reuse the // iterator. // pair p (i, i == m_.end ()); if (p.second) p = m_.emplace (move (k), entry_type {value_data (nullptr), bver, svars, sver}); entry_type& e (p.first->second); if (p.second) { // Cache miss. // e.value.version++; // New value. } else if (e.base_version != bver || e.stem_vars != svars || e.stem_version != sver) { // Cache invalidation. // assert (e.base_version <= bver); e.base_version = bver; if (e.stem_vars != svars) e.stem_vars = svars; else assert (e.stem_version <= sver); e.stem_version = sver; e.value.extra = 0; // For consistency (we don't really use it). e.value.version++; // Value changed. } else { // Cache hit. // if (var.type != nullptr && e.value.type != var.type) typify (e.value, *var.type, &var); ul.unlock (); } return pair (e.value, move (ul)); } }