Skip to content

Commit 94a20dc

Browse files
vassilmladenovfacebook-github-bot
authored andcommitted
Make trait constants behave like interface constants
Summary: Trait constants were added to the language recently to obviate a workaround where traits themselves could not declare constants, but could bring in constants via interfaces. However, the semantics on conflict were to drop the conflicting constant; this is in contrast to interface conflicts, where you get a fatal at class loading time. ``` class A { const int X = 3; } interface I { const string X = "hello"; } class C extends A implements I {} // Fatal runtime error trait TI implements I {} class D extends A { use TI; } // no fatal // by extension trait T { const bool X = false; } class E extends A { use T; } // also no fatal ``` This flag allows the above cases to fatal, and it also fixes the behavior of defaults + makes HHVM align with Hack on member resolution order. ``` abstract class A { abstract const type T = arraykey; } trait T { const type T = int; } class C extends A { use T; } // C::T = int, was arraykey previously. ``` Equivalently, coeffect unsoundness given trait conflicts is resolved ``` abstract class A { abstract const ctx C = []; } trait T { const ctx C = [defaults]; public function f()[this::C]: void { echo "impure"; } } class C extends A { use T; } // runtime used to think (new C())->f() is pure, now it's correctly defaults. ``` The flag also changes HHBBC to insert trait constants before inserting locally declared constants of a class, which resolves a bug with printing the conflict error message and a static analysis failure where the trait constant was expecting to conflict with a shallowly declared constant (see trait_slot_conflict_repo.php). Reviewed By: oulgen Differential Revision: D28887745 fbshipit-source-id: a698e1818925ba17c2608ce34d50dc1400cfabd0
1 parent bed8173 commit 94a20dc

24 files changed

Lines changed: 225 additions & 39 deletions

hphp/compiler/analysis/emitter.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,8 @@ RepoGlobalData getGlobalData() {
183183
RuntimeOption::EvalNoticeOnCoerceForStrConcat;
184184
gd.NoticeOnCoerceForBitOp =
185185
RuntimeOption::EvalNoticeOnCoerceForBitOp;
186+
gd.TraitConstantInterfaceBehavior =
187+
RuntimeOption::EvalTraitConstantInterfaceBehavior;
186188

187189
for (auto const& elm : RuntimeOption::ConstantFunctions) {
188190
auto const s = internal_serialize(tvAsCVarRef(elm.second));

hphp/hhbbc/index.cpp

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1708,19 +1708,24 @@ bool build_class_constants(ClassInfo* cinfo, ClsPreResolveUpdates& updates) {
17081708
}
17091709
}
17101710

1711-
// Constants from traits silently lose
1712-
if (fromTrait) {
1713-
removeNoOverride(cns);
1714-
return true;
1711+
if (!RO::EvalTraitConstantInterfaceBehavior) {
1712+
// Constants from traits silently lose
1713+
if (fromTrait) {
1714+
removeNoOverride(cns);
1715+
return true;
1716+
}
17151717
}
17161718

1717-
if ((cns->cls->attrs & (AttrInterface)) && existing->isAbstract) {
1719+
if ((cns->cls->attrs & AttrInterface ||
1720+
(RO::EvalTraitConstantInterfaceBehavior && (cns->cls->attrs & AttrTrait))) &&
1721+
existing->isAbstract) {
17181722
// because existing has val, this covers the case where it is abstract with default
17191723
// allow incoming to win
17201724
} else {
17211725
// A constant from an interface or from an included enum collides
17221726
// with an existing constant.
1723-
if (cns->cls->attrs & (AttrInterface | AttrEnum | AttrEnumClass)) {
1727+
if (cns->cls->attrs & (AttrInterface | AttrEnum | AttrEnumClass) ||
1728+
(RO::EvalTraitConstantInterfaceBehavior && (cns->cls->attrs & AttrTrait))) {
17241729
ITRACE(
17251730
2,
17261731
"build_class_constants failed for `{}' because "
@@ -1754,16 +1759,32 @@ bool build_class_constants(ClassInfo* cinfo, ClsPreResolveUpdates& updates) {
17541759
}
17551760
}
17561761

1757-
for (uint32_t idx = 0; idx < cinfo->cls->constants.size(); ++idx) {
1758-
auto const cns = ClassInfo::ConstIndex { cinfo->cls, idx };
1759-
if (cinfo->cls->attrs & AttrTrait) removeNoOverride(cns);
1760-
if (!add(cns, false)) return false;
1761-
}
1762+
auto const addShallowConstants = [&]() {
1763+
for (uint32_t idx = 0; idx < cinfo->cls->constants.size(); ++idx) {
1764+
auto const cns = ClassInfo::ConstIndex { cinfo->cls, idx };
1765+
if (cinfo->cls->attrs & AttrTrait) removeNoOverride(cns);
1766+
if (!add(cns, false)) return false;
1767+
}
1768+
return true;
1769+
};
17621770

1763-
for (auto const trait : cinfo->usedTraits) {
1764-
for (auto const& cns : trait->clsConstants) {
1765-
if (!add(cns.second, true)) return false;
1771+
auto const addTraitConstants = [&]() {
1772+
for (auto const trait : cinfo->usedTraits) {
1773+
for (auto const& cns : trait->clsConstants) {
1774+
if (!add(cns.second, true)) return false;
1775+
}
17661776
}
1777+
return true;
1778+
};
1779+
1780+
if (RO::EvalTraitConstantInterfaceBehavior) {
1781+
// trait constants must be inserted before constants shallowly declared on the class
1782+
// to match the interface semantics
1783+
if (!addTraitConstants()) return false;
1784+
if (!addShallowConstants()) return false;
1785+
} else {
1786+
if (!addShallowConstants()) return false;
1787+
if (!addTraitConstants()) return false;
17671788
}
17681789

17691790
for (auto const ienum : cinfo->includedEnums) {

hphp/hhbbc/main.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,8 @@ RepoGlobalData get_global_data() {
339339
RuntimeOption::EvalNoticeOnCoerceForStrConcat;
340340
gd.NoticeOnCoerceForBitOp =
341341
RuntimeOption::EvalNoticeOnCoerceForBitOp;
342+
gd.TraitConstantInterfaceBehavior =
343+
RuntimeOption::EvalTraitConstantInterfaceBehavior;
342344

343345
for (auto const& elm : RuntimeOption::ConstantFunctions) {
344346
auto const s = internal_serialize(tvAsCVarRef(elm.second));
@@ -524,6 +526,7 @@ int main(int argc, char** argv) try {
524526
RO::EvalHackArrDVArrs = true; // TODO(kshaunak): Clean it up.
525527
RO::EvalArrayProvenance = false; // TODO(kshaunak): Clean it up.
526528
RO::EvalEnforceGenericsUB = gd.HardGenericsUB ? 2 : 1;
529+
RO::EvalTraitConstantInterfaceBehavior = gd.TraitConstantInterfaceBehavior;
527530

528531
if (print_bytecode_stats_and_exit) {
529532
print_repo_bytecode_stats();

hphp/runtime/base/runtime-option.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1417,6 +1417,7 @@ struct RuntimeOption {
14171417
F(bool, EnableAbstractContextConstants, true) \
14181418
F(bool, TypeconstAbstractDefaultReflectionIsAbstract, false) \
14191419
F(bool, AbstractContextConstantUninitAccess, false) \
1420+
F(bool, TraitConstantInterfaceBehavior, false) \
14201421
/* */
14211422

14221423
private:

hphp/runtime/base/unit-cache.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1365,6 +1365,7 @@ std::string mangleUnitSha1(const std::string& fileSha1,
13651365
+ (RuntimeOption::EvalFoldLazyClassKeys ? '1' : '0')
13661366
+ (RuntimeOption::EvalHackCompilerUseCompilerPool ? '1' : '0')
13671367
+ (RuntimeOption::EvalEnableAbstractContextConstants ? '1': '0')
1368+
+ (RuntimeOption::EvalTraitConstantInterfaceBehavior ? '1' : '0')
13681369
+ RuntimeOption::EvalUnitCacheBreaker + '\0'
13691370
+ CoeffectsConfig::mangle()
13701371
+ opts.cacheKeySha1().toString()

hphp/runtime/vm/class.cpp

Lines changed: 47 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2281,28 +2281,54 @@ void Class::importTraitConsts(ConstMap::Builder& builder) {
22812281
return;
22822282
}
22832283

2284-
// Constants in interfaces implemented by traits don't fatal with constants
2285-
// in declInterfaces
2286-
if (isFromInterface) { return; }
2287-
2288-
// Type and Context constants in interfaces can be overriden.
2289-
if (tConst.kind() == ConstModifiers::Kind::Type ||
2290-
tConst.kind() == ConstModifiers::Kind::Context) {
2291-
return;
2292-
}
2293-
if (existingConst.cls != tConst.cls) {
2284+
if (RO::EvalTraitConstantInterfaceBehavior) {
2285+
if (existingConst.isAbstract()) {
2286+
// the case where the incoming constant is abstract without a default is covered above
2287+
// there are two remaining cases:
2288+
// - the incoming constant is abstract with a default
2289+
// - the incoming constant is concrete
2290+
// In both situations, the incoming constant should win, and separate bookkeeping will
2291+
// cover situations where there are multiple competing defaults.
2292+
existingConst.cls = tConst.cls;
2293+
existingConst.val = tConst.val;
2294+
return;
2295+
} else { // existing is concrete
2296+
// the existing constant will win over any incoming abstracts and retain a fatal when two
2297+
// concrete constants collide
2298+
if (!tConst.isAbstract() && existingConst.cls != tConst.cls) {
2299+
raise_error("%s cannot inherit the %s %s from %s, because "
2300+
"it was previously inherited from %s",
2301+
m_preClass->name()->data(),
2302+
ConstModifiers::show(tConst.kind()),
2303+
tConst.name->data(),
2304+
tConst.cls->name()->data(),
2305+
existingConst.cls->name()->data());
2306+
}
2307+
}
2308+
} else {
2309+
// Constants in interfaces implemented by traits don't fatal with constants
2310+
// in declInterfaces
2311+
if (isFromInterface) { return; }
22942312

2295-
// Constants in traits conflict with constants in declared interfaces
2296-
if (existingConst.cls->attrs() & AttrInterface) {
2297-
for (auto const& interface : m_declInterfaces) {
2298-
auto iface = existingConst.cls;
2299-
if (interface.get() == iface) {
2300-
raise_error("%s cannot inherit the %s %s, because "
2301-
"it was previously inherited from %s",
2302-
m_preClass->name()->data(),
2303-
ConstModifiers::show(tConst.kind()),
2304-
tConst.name->data(),
2305-
existingConst.cls->name()->data());
2313+
// Type and Context constants in interfaces can be overriden.
2314+
if (tConst.kind() == ConstModifiers::Kind::Type ||
2315+
tConst.kind() == ConstModifiers::Kind::Context) {
2316+
return;
2317+
}
2318+
if (existingConst.cls != tConst.cls) {
2319+
2320+
// Constants in traits conflict with constants in declared interfaces
2321+
if (existingConst.cls->attrs() & AttrInterface) {
2322+
for (auto const& interface : m_declInterfaces) {
2323+
auto iface = existingConst.cls;
2324+
if (interface.get() == iface) {
2325+
raise_error("%s cannot inherit the %s %s, because "
2326+
"it was previously inherited from %s",
2327+
m_preClass->name()->data(),
2328+
ConstModifiers::show(tConst.kind()),
2329+
tConst.name->data(),
2330+
existingConst.cls->name()->data());
2331+
}
23062332
}
23072333
}
23082334
}

hphp/runtime/vm/repo-global-data.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ void RepoGlobalData::load(bool loadConstantFuncs) const {
4747
RO::EvalNoticeOnCoerceForStrConcat = NoticeOnCoerceForStrConcat;
4848
RO::EvalNoticeOnCoerceForBitOp = NoticeOnCoerceForBitOp;
4949
RO::EvalHackArrDVArrs = true; // TODO(kshaunak): Clean up.
50+
RO::EvalTraitConstantInterfaceBehavior = TraitConstantInterfaceBehavior;
5051

5152
if (HardGenericsUB) RO::EvalEnforceGenericsUB = 2;
5253

@@ -105,7 +106,7 @@ std::string show(const RepoGlobalData& gd) {
105106
SHOW(StrictArrayFillKeys);
106107
SHOW(NoticeOnCoerceForStrConcat);
107108
SHOW(NoticeOnCoerceForBitOp);
108-
SHOW(TypeconstInterfaceInheritanceDefaults);
109+
SHOW(TraitConstantInterfaceBehavior);
109110
#undef SHOW
110111
return out;
111112
}

hphp/runtime/vm/repo-global-data.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,8 @@ struct RepoGlobalData {
161161
/* Whether implicit coercions for bit ops trigger logs/exceptions */
162162
int32_t NoticeOnCoerceForBitOp = 0;
163163

164-
/* New behavior for inheritance of abstract type constants with defaults */
165-
bool TypeconstInterfaceInheritanceDefaults = false;
164+
/* Constants from traits behave like constants from interfaces (error on conflict) */
165+
bool TraitConstantInterfaceBehavior = false;
166166

167167
/*
168168
* The Hack.Lang.StrictArrayFillKeys option the repo was compiled with.
@@ -215,7 +215,7 @@ struct RepoGlobalData {
215215
(StrictArrayFillKeys)
216216
(NoticeOnCoerceForStrConcat)
217217
(NoticeOnCoerceForBitOp)
218-
(TypeconstInterfaceInheritanceDefaults)
218+
(TraitConstantInterfaceBehavior)
219219
(ConstantFunctions)
220220
;
221221
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
hhvm.enable_abstract_context_constants=1
22
hhvm.typeconst_abstract_default_reflection_is_abstract=1
33
hhvm.abstract_context_constant_uninit_access=1
4+
hhvm.trait_constant_interface_behavior=1
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
hhvm.enable_abstract_context_constants=1
22
hhvm.typeconst_abstract_default_reflection_is_abstract=1
33
hhvm.abstract_context_constant_uninit_access=1
4+
hhvm.trait_constant_interface_behavior=1

0 commit comments

Comments
 (0)