Skip to content

Commit b05c47e

Browse files
Andrew Kennedymeta-codesync[bot]
authored andcommitted
Extend type structure representation to support class refinements
Summary: [Human here: this was produced by Claude, with another Claude reviewing it and suggesting fixes. It's a prerequisite to supporting aliases and type constants whose right-hand-side is a type refinement.] Add runtime support for type refinements (`I with { type T = ... }`) in TypeStructures. Refinements are represented by adding an optional `with_refinements` dict field to class kinds (T_class, T_interface, T_trait, T_enum, T_unresolved) rather than introducing a new TypeStructureKind. Each entry in `with_refinements` is keyed by the type/ctx constant name and contains: - `is_ctx`: whether this is a context constant refinement - `equals`: for exact refinements (`type T = X`) - `as`: for upper-bounded refinements (`type T as X`) - `super`: for lower-bounded refinements (`type T super X`) Changes: - Emitter (`emit_type_constant.rs`): emit `with_refinements` as a sibling field of `classname`/`kind` when processing `Hrefinement` hints, and allow refinements in type aliases and type constants - Runtime (`type-structure.cpp`): resolve `with_refinements` during T_unresolved resolution, generate display names via `refinementTypeName()`, and coerce refinement member types in `coerceToTypeStructure` - Helpers (`type-structure-helpers.cpp/defs.h`): add `get_ts_refinement_types_opt()` accessor and string constants for refinement fields - HHBBC (`type-structure.cpp`): resolve refinement types at compile time and track references within refinement members for dependency analysis - Handle `with_refinements` presence in event-hook.cpp, irgen-types.cpp, interp.cpp, and type-system.cpp switch cases - Add tests covering exact, upper-bounded, lower-bounded, and ctx refinements for both type aliases and type constants Reviewed By: dlreeves Differential Revision: D94346411 fbshipit-source-id: c82eb43a3129a400c9bab45c616f3b9edaa25320
1 parent b5e753a commit b05c47e

20 files changed

Lines changed: 466 additions & 28 deletions

hphp/hack/src/hackc/emitter/emit_class.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ fn from_type_constant<'a>(
190190
&[],
191191
&BTreeMap::new(),
192192
init,
193-
TypeRefinementInHint::Disallowed,
193+
TypeRefinementInHint::Allowed,
194194
)?)
195195
}
196196
};

hphp/hack/src/hackc/emitter/emit_type_constant.rs

Lines changed: 133 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@ use naming_special_names_rust::classes;
1515
use naming_special_names_rust::typehints;
1616
use options::Options;
1717
use oxidized::aast;
18+
use oxidized::aast_defs::CtxRefinement;
1819
use oxidized::aast_defs::Hint;
1920
use oxidized::aast_defs::Hint_;
2021
use oxidized::aast_defs::NastShapeInfo;
22+
use oxidized::aast_defs::Refinement;
2123
use oxidized::aast_defs::ShapeFieldInfo;
2224
use oxidized::aast_defs::TupleExtra;
2325
use oxidized::aast_defs::TupleExtraInfo;
2426
use oxidized::aast_defs::TupleInfo;
27+
use oxidized::aast_defs::TypeRefinement;
2528
use oxidized::ast;
2629
use oxidized::ast_defs;
2730
use oxidized::ast_defs::ShapeFieldName;
@@ -259,6 +262,110 @@ fn get_typevars(tparams: &[&str]) -> Vec<DictEntry> {
259262
}
260263
}
261264

265+
fn refinement_to_entry(
266+
opts: &Options,
267+
tparams: &[&str],
268+
targ_map: &BTreeMap<&str, i64>,
269+
refinement: &Refinement,
270+
) -> Result<DictEntry> {
271+
match refinement {
272+
Refinement::Rtype(ast_defs::Id(_, name), tr) => {
273+
let mut member = vec![];
274+
member.push(encode_entry("is_ctx", TypedValue::Bool(false)));
275+
276+
match tr {
277+
TypeRefinement::TRexact(hint) => {
278+
member.push(encode_entry(
279+
"equals",
280+
hint_to_type_constant(
281+
opts,
282+
tparams,
283+
targ_map,
284+
hint,
285+
TypeRefinementInHint::Allowed,
286+
)?,
287+
));
288+
}
289+
TypeRefinement::TRloose(bounds) => {
290+
if !bounds.upper.is_empty() {
291+
member.push(encode_entry(
292+
"as",
293+
hints_to_type_constant(
294+
opts,
295+
tparams,
296+
targ_map,
297+
TypeRefinementInHint::Allowed,
298+
&bounds.upper,
299+
)?,
300+
));
301+
}
302+
if !bounds.lower.is_empty() {
303+
member.push(encode_entry(
304+
"super",
305+
hints_to_type_constant(
306+
opts,
307+
tparams,
308+
targ_map,
309+
TypeRefinementInHint::Allowed,
310+
&bounds.lower,
311+
)?,
312+
));
313+
}
314+
}
315+
}
316+
317+
Ok(encode_entry(name, TypedValue::dict(member)))
318+
}
319+
Refinement::Rctx(ast_defs::Id(_, name), cr) => {
320+
let mut member = vec![];
321+
member.push(encode_entry("is_ctx", TypedValue::Bool(true)));
322+
323+
match cr {
324+
CtxRefinement::CRexact(hint) => {
325+
member.push(encode_entry(
326+
"equals",
327+
hint_to_type_constant(
328+
opts,
329+
tparams,
330+
targ_map,
331+
hint,
332+
TypeRefinementInHint::Allowed,
333+
)?,
334+
));
335+
}
336+
CtxRefinement::CRloose(bounds) => {
337+
if let Some(upper) = &bounds.upper {
338+
member.push(encode_entry(
339+
"as",
340+
hints_to_type_constant(
341+
opts,
342+
tparams,
343+
targ_map,
344+
TypeRefinementInHint::Allowed,
345+
&[upper.clone()],
346+
)?,
347+
));
348+
}
349+
if let Some(lower) = &bounds.lower {
350+
member.push(encode_entry(
351+
"super",
352+
hints_to_type_constant(
353+
opts,
354+
tparams,
355+
targ_map,
356+
TypeRefinementInHint::Allowed,
357+
&[lower.clone()],
358+
)?,
359+
));
360+
}
361+
}
362+
}
363+
364+
Ok(encode_entry(name, TypedValue::dict(member)))
365+
}
366+
}
367+
}
368+
262369
fn hint_to_type_constant_list(
263370
opts: &Options,
264371
tparams: &[&str],
@@ -469,24 +576,32 @@ fn hint_to_type_constant_list(
469576
)?);
470577
r
471578
}
472-
Hint_::Hrefinement(h, _) => {
473-
match type_refinement_in_hint {
474-
TypeRefinementInHint::Disallowed => {
475-
let aast::Hint(pos, _) = h;
476-
return Err(Error::fatal_parse(pos, "Refinement in type structure"));
477-
}
478-
TypeRefinementInHint::Allowed => {
479-
// check recursively (e.g.: Class<T1, T2> with { ... })
480-
hint_to_type_constant_list(
481-
opts,
482-
tparams,
483-
targ_map,
484-
TypeRefinementInHint::Allowed,
485-
h,
486-
)?
487-
}
579+
Hint_::Hrefinement(h, refinements) => match type_refinement_in_hint {
580+
TypeRefinementInHint::Disallowed => {
581+
let aast::Hint(pos, _) = h;
582+
return Err(Error::fatal_parse(pos, "Refinement in type structure"));
488583
}
489-
}
584+
TypeRefinementInHint::Allowed => {
585+
let mut r = hint_to_type_constant_list(
586+
opts,
587+
tparams,
588+
targ_map,
589+
TypeRefinementInHint::Allowed,
590+
h,
591+
)?;
592+
593+
let refinement_entries = refinements
594+
.iter()
595+
.map(|refinement| refinement_to_entry(opts, tparams, targ_map, refinement))
596+
.collect::<Result<Vec<_>>>()?;
597+
r.push(encode_entry(
598+
"with_refinements",
599+
TypedValue::dict(refinement_entries),
600+
));
601+
602+
r
603+
}
604+
},
490605
Hint_::Habstr(_)
491606
| Hint_::Hdynamic
492607
| Hint_::HfunContext(_)
@@ -524,7 +639,7 @@ pub(crate) fn typedef_to_type_structure(
524639
opts,
525640
tparams,
526641
&BTreeMap::new(),
527-
TypeRefinementInHint::Disallowed, // Note: only called by `emit_typedef
642+
TypeRefinementInHint::Allowed, // Refinements are allowed in type aliases
528643
kind,
529644
)?;
530645
tconsts.append(&mut get_typevars(tparams));

hphp/hhbbc/type-structure.cpp

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,52 @@ Resolution resolve_type_access(ResolveCtx& ctx, SArray ts) {
800800
.finish();
801801
}
802802

803+
Resolution resolve_refinement_types(ResolveCtx& ctx, SArray ts) {
804+
auto const refinements = get_ts_refinement_types_opt(ts);
805+
if (!refinements) return Resolution{ TBottom, false };
806+
assertx(refinements->isStatic());
807+
assertx(refinements->isDictType());
808+
auto const size = refinements->size();
809+
auto membersBuilder = Builder::dict();
810+
for (size_t i = 0; i < size; ++i) {
811+
auto const k = refinements->nvGetKey(i);
812+
auto const v = refinements->nvGetVal(i);
813+
assertx(tvIsString(k));
814+
assertx(tvIsDict(v));
815+
auto const memberArr = val(v).parr;
816+
auto memberBuilder = Builder::dict();
817+
818+
auto const isCtx = memberArr->get(s_is_ctx.get());
819+
if (isCtx.is_init()) {
820+
memberBuilder.set(s_is_ctx, isCtx);
821+
}
822+
823+
auto const exact = memberArr->get(s_equals.get());
824+
if (exact.is_init()) {
825+
assertx(tvIsDict(exact));
826+
auto exactRes = resolve_bespoke(ctx, val(exact).parr);
827+
memberBuilder.set(s_equals, exactRes);
828+
}
829+
830+
auto const upper = memberArr->get(s_as.get());
831+
if (upper.is_init()) {
832+
assertx(tvIsVec(upper));
833+
auto upperRes = resolve_list(ctx, val(upper).parr);
834+
memberBuilder.set(s_as, upperRes);
835+
}
836+
837+
auto const lower = memberArr->get(s_super.get());
838+
if (lower.is_init()) {
839+
assertx(tvIsVec(lower));
840+
auto lowerRes = resolve_list(ctx, val(lower).parr);
841+
memberBuilder.set(s_super, lowerRes);
842+
}
843+
844+
membersBuilder.set(sval(val(k).pstr), memberBuilder.finish());
845+
}
846+
return membersBuilder.finish();
847+
}
848+
803849
Resolution resolve_unresolved(ResolveCtx& ctx, SArray ts) {
804850
auto b = Builder::copy(ts, TS::Kind::T_unresolved);
805851

@@ -829,9 +875,14 @@ Resolution resolve_unresolved(ResolveCtx& ctx, SArray ts) {
829875

830876
auto b = setKindAndName(kind, name);
831877
if (setExact) b.set(s_exact, make_tv<KindOfBoolean>(true));
878+
b.resolve(s_generic_types, get_ts_generic_types_opt(ts),
879+
ctx, resolve_list);
880+
auto refinementRes = resolve_refinement_types(ctx, ts);
881+
if (!refinementRes.type.is(BBottom)) {
882+
b.set(s_with_refinements, refinementRes);
883+
}
832884
return
833-
b.resolve(s_generic_types, get_ts_generic_types_opt(ts),
834-
ctx, resolve_list)
885+
std::move(b)
835886
.optCopy(s_typevars, ts)
836887
.optCopy(s_alias, ts)
837888
.optCopy(s_case_type, ts)
@@ -1247,6 +1298,32 @@ void type_structure_references(SArray ts, SStringSet& names) {
12471298
}
12481299
};
12491300

1301+
auto const onRefinements = [&names, &onList] (SArray ts) {
1302+
auto const refinements = get_ts_refinement_types_opt(ts);
1303+
if (!refinements) return;
1304+
auto const size = refinements->size();
1305+
for (size_t i = 0; i < size; ++i) {
1306+
auto const v = refinements->nvGetVal(i);
1307+
assertx(tvIsDict(v));
1308+
auto const memberArr = val(v).parr;
1309+
auto const exact = memberArr->get(s_equals.get());
1310+
if (exact.is_init()) {
1311+
assertx(tvIsDict(exact));
1312+
type_structure_references(val(exact).parr, names);
1313+
}
1314+
auto const upper = memberArr->get(s_as.get());
1315+
if (upper.is_init()) {
1316+
assertx(tvIsVec(upper));
1317+
onList(val(upper).parr);
1318+
}
1319+
auto const lower = memberArr->get(s_super.get());
1320+
if (lower.is_init()) {
1321+
assertx(tvIsVec(lower));
1322+
onList(val(lower).parr);
1323+
}
1324+
}
1325+
};
1326+
12501327
switch (get_ts_kind(ts)) {
12511328
case TS::Kind::T_enum:
12521329
case TS::Kind::T_trait:
@@ -1256,6 +1333,7 @@ void type_structure_references(SArray ts, SStringSet& names) {
12561333
case TS::Kind::T_xhp:
12571334
names.emplace(get_ts_classname(ts));
12581335
onList(get_ts_generic_types_opt(ts));
1336+
onRefinements(ts);
12591337
break;
12601338
case TS::Kind::T_fun:
12611339
type_structure_references(get_ts_return_type(ts), names);

hphp/runtime/base/type-structure-helpers-defs.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ extern const StaticString s_typevar_types;
4949
extern const StaticString s_union_types;
5050
extern const StaticString s_hh_this;
5151
extern const StaticString s_type_structure_non_existant_class;
52+
extern const StaticString s_with_refinements;
53+
extern const StaticString s_equals;
54+
extern const StaticString s_as;
55+
extern const StaticString s_super;
56+
extern const StaticString s_is_ctx;
5257

5358
// Fixed error messages
5459
extern const StaticString s_reified_type_must_be_ts;
@@ -185,6 +190,14 @@ ALWAYS_INLINE const ArrayData* get_ts_union_types_opt(const ArrayData* ts) {
185190
return detail::get_ts_varray_opt(ts, s_union_types);
186191
}
187192

193+
ALWAYS_INLINE const ArrayData* get_ts_refinement_types(const ArrayData* ts) {
194+
return detail::get_ts_darray(ts, s_with_refinements);
195+
}
196+
197+
ALWAYS_INLINE const ArrayData* get_ts_refinement_types_opt(const ArrayData* ts) {
198+
return detail::get_ts_darray_opt(ts, s_with_refinements);
199+
}
200+
188201
ALWAYS_INLINE const StringData* get_ts_classname(const ArrayData* ts) {
189202
return detail::get_ts_string(ts, s_classname);
190203
}

hphp/runtime/base/type-structure-helpers.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ const StaticString s_union_types("union_types");
7171
const StaticString s_hh_this(annotTypeName(AnnotType::This));
7272
const StaticString s_type_structure_non_existant_class(
7373
"HH\\__internal\\type_structure_non_existant_class");
74+
const StaticString s_with_refinements("with_refinements");
75+
const StaticString s_equals("equals");
76+
const StaticString s_as("as");
77+
const StaticString s_super("super");
78+
const StaticString s_is_ctx("is_ctx");
7479

7580
// Fixed error messages
7681
const StaticString s_reified_type_must_be_ts(
@@ -1064,6 +1069,9 @@ bool errorOnIsAsExpressionInvalidTypes(const Array& ts, bool dryrun,
10641069
case TypeStructure::Kind::T_enum:
10651070
case TypeStructure::Kind::T_class:
10661071
case TypeStructure::Kind::T_interface: {
1072+
if (ts.exists(s_with_refinements)) {
1073+
return err("a type with refinements");
1074+
}
10671075
auto tv = ts.lookup(s_generic_types);
10681076
return tv.is_init() ?
10691077
errorOnIsAsExpressionInvalidTypesList(tv.val().parr, dryrun, true) :

0 commit comments

Comments
 (0)