Codemod asserts to assertxs in the runtime
[hiphop-php.git] / hphp / runtime / vm / type-constraint.cpp
blob443d1bf8d425fbdecf58fedcaf35371e3d97b4c9
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/vm/type-constraint.h"
19 #include <folly/Format.h>
20 #include <folly/MapUtil.h>
22 #include "hphp/util/trace.h"
24 #include "hphp/runtime/base/autoload-handler.h"
25 #include "hphp/runtime/base/execution-context.h"
26 #include "hphp/runtime/base/runtime-error.h"
27 #include "hphp/runtime/vm/act-rec.h"
28 #include "hphp/runtime/vm/class.h"
29 #include "hphp/runtime/vm/func.h"
30 #include "hphp/runtime/vm/hhbc.h"
31 #include "hphp/runtime/vm/jit/translator-inline.h"
32 #include "hphp/runtime/vm/repo-global-data.h"
33 #include "hphp/runtime/vm/repo.h"
34 #include "hphp/runtime/vm/runtime.h"
35 #include "hphp/runtime/vm/unit.h"
36 #include "hphp/runtime/vm/vm-regs.h"
38 #include "hphp/runtime/ext/std/ext_std_function.h"
40 namespace HPHP {
42 TRACE_SET_MOD(runtime);
44 //////////////////////////////////////////////////////////////////////
46 const StaticString s___invoke("__invoke"),
47 s_array{"array"},
48 s_varray{"HH\\varray"},
49 s_darray{"HH\\darray"},
50 s_varray_or_darray{"HH\\varray_or_darray"},
51 s_vec{"HH\\vec"},
52 s_dict{"HH\\dict"},
53 s_vec_or_dict{"HH\\vec_or_dict"};
55 void TypeConstraint::init() {
56 if (m_typeName == nullptr || isTypeVar() || isTypeConstant()) {
57 m_type = Type::Mixed;
58 return;
60 TRACE(5, "TypeConstraint: this %p type %s, nullable %d\n",
61 this, m_typeName->data(), isNullable());
62 auto const mptr = nameToAnnotType(m_typeName);
63 if (mptr) {
64 m_type = *mptr;
65 assertx(getAnnotDataType(m_type) != KindOfPersistentString);
66 if (RuntimeOption::EvalHackArrDVArrs) {
67 assertx(m_type != AnnotType::VArray &&
68 m_type != AnnotType::DArray &&
69 m_type != AnnotType::VArrOrDArr);
70 if (m_typeName->isame(s_varray.get())) {
71 m_typeName = s_vec.get();
72 } else if (m_typeName->isame(s_darray.get())) {
73 m_typeName = s_dict.get();
74 } else if (m_typeName->isame(s_varray_or_darray.get())) {
75 m_typeName = s_vec_or_dict.get();
78 return;
80 if (m_flags & Flags::Resolved) {
81 TRACE(5, "TypeConstraint: this %p pre-resolved type %s, treating as %s\n",
82 this, m_typeName->data(), tname(getAnnotDataType(m_type)).c_str());
83 } else {
84 TRACE(5, "TypeConstraint: this %p no such type %s, treating as object\n",
85 this, m_typeName->data());
86 m_type = Type::Object;
88 m_namedEntity = NamedEntity::get(m_typeName);
89 TRACE(5, "TypeConstraint: this %p NamedEntity: %p\n",
90 this, m_namedEntity.get());
93 std::string TypeConstraint::displayName(const Func* func /*= nullptr*/,
94 bool extra /* = false */) const {
95 const StringData* tn = typeName();
96 std::string name;
97 if (isSoft()) {
98 name += '@';
100 if (isNullable() && isExtended()) {
101 name += '?';
103 if (func && isSelf()) {
104 selfToTypeName(func, &tn);
105 name += tn->data();
106 } else if (func && isParent()) {
107 parentToTypeName(func, &tn);
108 name += tn->data();
109 } else {
110 const char* str = tn->data();
111 auto len = tn->size();
112 if (len > 3 && tolower(str[0]) == 'h' && tolower(str[1]) == 'h' &&
113 str[2] == '\\') {
114 bool strip = false;
115 const char* stripped = str + 3;
116 switch (len - 3) {
117 case 3:
118 strip = (!strcasecmp(stripped, "int") ||
119 !strcasecmp(stripped, "num"));
120 break;
121 case 4:
122 strip = (!strcasecmp(stripped, "bool") ||
123 !strcasecmp(stripped, "this"));
124 break;
125 case 5: strip = !strcasecmp(stripped, "float"); break;
126 case 6: strip = !strcasecmp(stripped, "string"); break;
127 case 8:
128 strip = (!strcasecmp(stripped, "resource") ||
129 !strcasecmp(stripped, "noreturn") ||
130 !strcasecmp(stripped, "arraykey"));
131 break;
132 default:
133 break;
135 if (strip) {
136 str = stripped;
139 name += str;
141 if (extra && m_flags & Flags::Resolved && m_type != AnnotType::Object) {
142 const char* str = nullptr;
143 switch (m_type) {
144 case AnnotType::Uninit: str = "uninit"; break;
145 case AnnotType::Null: str = "null"; break;
146 case AnnotType::Bool: str = "bool"; break;
147 case AnnotType::Int: str = "int"; break;
148 case AnnotType::Float: str = "float"; break;
149 case AnnotType::String: str = "string"; break;
150 case AnnotType::Array: str = "array"; break;
151 case AnnotType::Resource: str = "resource"; break;
152 case AnnotType::Dict: str = "dict"; break;
153 case AnnotType::Vec: str = "vec"; break;
154 case AnnotType::Keyset: str = "keyset"; break;
155 case AnnotType::Number: str = "num"; break;
156 case AnnotType::ArrayKey: str = "arraykey"; break;
157 case AnnotType::VArray: str = "varray"; break;
158 case AnnotType::DArray: str = "darray"; break;
159 case AnnotType::VArrOrDArr: str = "varray_or_darray"; break;
160 case AnnotType::VecOrDict: str = "vec_or_dict"; break;
161 case AnnotType::Self:
162 case AnnotType::This:
163 case AnnotType::Parent:
164 case AnnotType::Object:
165 case AnnotType::Mixed:
166 case AnnotType::Callable:
167 break;
169 if (str) folly::format(&name, " ({})", str);
171 return name;
174 bool TypeConstraint::compat(const TypeConstraint& other) const {
175 if (other.isExtended() || isExtended()) {
177 * Rely on the ahead of time typechecker---checking here can
178 * make it harder to convert a base class or interface to <?hh,
179 * because derived classes that are still <?php would all need
180 * to be modified.
182 return true;
185 if (m_typeName == other.m_typeName) {
186 return true;
189 if (m_typeName && other.m_typeName) {
190 if (m_typeName->isame(other.m_typeName)) {
191 return true;
194 auto const is_array = [](const StringData* str) {
195 return str->isame(s_array.get()) ||
196 str->isame(s_varray.get()) ||
197 str->isame(s_darray.get()) ||
198 str->isame(s_varray_or_darray.get());
200 if (is_array(m_typeName) && is_array(other.m_typeName)) {
201 return true;
204 const Class* cls = Unit::lookupClass(m_typeName);
205 const Class* otherCls = Unit::lookupClass(other.m_typeName);
207 return cls && otherCls && cls == otherCls;
210 return false;
213 namespace {
216 * Look up a TypeAliasReq for the supplied NamedEntity (which must be the
217 * NamedEntity for `name'), invoking autoload if necessary for types but not
218 * for classes.
220 * We don't need to autoload classes because it is impossible to have an
221 * instance of a class if it's not defined. However, we need to autoload
222 * typedefs because they can affect whether VerifyParamType would succeed.
224 const TypeAliasReq* getTypeAliasWithAutoload(const NamedEntity* ne,
225 const StringData* name) {
226 auto def = ne->getCachedTypeAlias();
227 if (!def) {
228 VMRegAnchor _;
229 String nameStr(const_cast<StringData*>(name));
230 if (!AutoloadHandler::s_instance->autoloadType(nameStr)) {
231 return nullptr;
233 def = ne->getCachedTypeAlias();
235 return def;
239 * Look up a TypeAliasReq or a Class for the supplied NamedEntity
240 * (which must be the NamedEntity for `name'), invoking autoload if
241 * necessary.
243 * This is useful when looking up a type annotation that could be either a
244 * type alias or an enum class; enum classes are strange in that it
245 * *is* possible to have an instance of them even if they are not defined.
247 static
248 std::pair<const TypeAliasReq*, Class*> getTypeAliasOrClassWithAutoload(
249 const NamedEntity* ne,
250 const StringData* name) {
252 auto def = ne->getCachedTypeAlias();
253 Class *klass = nullptr;
254 if (!def) {
255 klass = Unit::lookupClass(ne);
256 // We don't have the class or the typedef, so autoload.
257 if (!klass) {
258 String nameStr(const_cast<StringData*>(name));
259 if (AutoloadHandler::s_instance->autoloadClassOrType(nameStr)) {
260 // Autoload succeeded, try to grab a typedef and if that doesn't work,
261 // a class.
262 def = ne->getCachedTypeAlias();
263 if (!def) {
264 klass = Unit::lookupClass(ne);
270 assertx(!def || !klass);
271 return std::make_pair(def, klass);
276 MaybeDataType TypeConstraint::underlyingDataTypeResolved() const {
277 assertx(!isSelf() && !isParent() && !isCallable());
278 assertx(IMPLIES(
279 !hasConstraint() || isTypeVar() || isTypeConstant(),
280 isMixed()));
282 if (!isPrecise()) {
283 if (isVArray() || isDArray() || isVArrayOrDArray()) return KindOfArray;
284 return folly::none;
287 auto t = underlyingDataType();
288 assertx(t);
290 // If we aren't a class or type alias, nothing special to do.
291 if (!isObject()) return t;
293 assertx(t == KindOfObject);
294 auto p = getTypeAliasOrClassWithAutoload(m_namedEntity, m_typeName);
295 auto td = p.first;
296 auto c = p.second;
298 // See if this is a type alias.
299 if (td) {
300 if (td->type != Type::Object) {
301 auto const metatype = getAnnotMetaType(td->type);
302 if (metatype == MetaType::Precise) {
303 t = getAnnotDataType(td->type);
304 } else if (metatype == MetaType::VArray || metatype == MetaType::DArray ||
305 metatype == MetaType::VArrOrDArr) {
306 t = KindOfArray;
307 } else {
308 t = folly::none;
310 } else {
311 c = td->klass;
315 // If the underlying type is a class, see if it is an enum and get that.
316 if (c && isEnum(c)) {
317 t = c->enumBaseTy();
320 return t;
323 bool TypeConstraint::checkTypeAliasNonObj(const TypedValue* tv) const {
324 assertx(tv->m_type != KindOfObject);
325 assertx(isObject());
327 auto p = getTypeAliasOrClassWithAutoload(m_namedEntity, m_typeName);
328 auto td = p.first;
329 auto c = p.second;
331 // Common case is that we actually find the alias:
332 if (td) {
333 if (td->nullable && tv->m_type == KindOfNull) return true;
334 auto result = annotCompat(tv->m_type, td->type,
335 td->klass ? td->klass->name() : nullptr);
336 switch (result) {
337 case AnnotAction::Pass: return true;
338 case AnnotAction::Fail: return false;
339 case AnnotAction::CallableCheck:
340 return is_callable(tvAsCVarRef(tv));
341 case AnnotAction::ObjectCheck: break;
342 case AnnotAction::VArrayCheck:
343 assertx(tvIsArray(tv));
344 return tv->m_data.parr->isVArray();
345 case AnnotAction::DArrayCheck:
346 assertx(tvIsArray(tv));
347 return tv->m_data.parr->isDArray();
348 case AnnotAction::VArrayOrDArrayCheck:
349 assertx(tvIsArray(tv));
350 return !tv->m_data.parr->isNotDVArray();
351 case AnnotAction::NonVArrayOrDArrayCheck:
352 assertx(tvIsArray(tv));
353 return tv->m_data.parr->isNotDVArray();
355 assertx(result == AnnotAction::ObjectCheck);
356 assertx(td->type == AnnotType::Object);
357 // Fall through to the check below, since this could be a type
358 // alias to an enum type
359 c = td->klass;
362 // Otherwise, this isn't a proper type alias, but it *might* be a
363 // first-class enum. Check if the type is an enum and check the
364 // constraint if it is. We only need to do this when the underlying
365 // type is not an object, since only int and string can be enums.
366 if (c && isEnum(c)) {
367 auto dt = c->enumBaseTy();
368 // For an enum, if the underlying type is mixed, we still require
369 // it is either an int or a string!
370 if (dt) {
371 return equivDataTypes(*dt, tv->m_type);
372 } else {
373 return isIntType(tv->m_type) || isStringType(tv->m_type);
376 return false;
379 bool TypeConstraint::checkTypeAliasObj(const Class* cls) const {
380 assertx(isObject() && m_namedEntity && m_typeName);
381 // Look up the type alias (autoloading if necessary)
382 // and fail if we can't find it
383 auto const td = getTypeAliasWithAutoload(m_namedEntity, m_typeName);
384 if (!td) {
385 return false;
387 // We found the type alias, check if an object of type cls
388 // is compatible
389 switch (getAnnotMetaType(td->type)) {
390 case AnnotMetaType::Precise:
391 return td->type == AnnotType::Object && td->klass &&
392 cls->classof(td->klass);
393 case AnnotMetaType::Mixed:
394 return true;
395 case AnnotMetaType::Callable:
396 return cls->lookupMethod(s___invoke.get()) != nullptr;
397 case AnnotMetaType::Self:
398 case AnnotMetaType::Parent:
399 case AnnotMetaType::Number:
400 case AnnotMetaType::ArrayKey:
401 case AnnotMetaType::This:
402 case AnnotMetaType::VArray:
403 case AnnotMetaType::DArray:
404 case AnnotMetaType::VArrOrDArr:
405 case AnnotMetaType::VecOrDict:
406 // Self and Parent should never happen, because type
407 // aliases are not allowed to use those MetaTypes
408 return false;
410 not_reached();
414 void TypeConstraint::verifyReturnNonNull(TypedValue* tv, const Func* func,
415 bool useStrictTypes) const {
416 const auto DEBUG_ONLY tc = func->returnTypeConstraint();
417 assertx(!tc.isNullable());
418 if (UNLIKELY(cellIsNull(tv))) {
419 verifyReturnFail(func, tv, useStrictTypes);
420 } else if (debug) {
421 auto vm = &*g_context;
422 always_assert_flog(
423 check(tv, func),
424 "HHBBC incorrectly converted VerifyRetTypeC to VerifyRetNonNull in {}:{}",
425 vm->getContainingFileName()->data(),
426 vm->getLine()
431 bool TypeConstraint::check(TypedValue* tv, const Func* func) const {
432 assertx(hasConstraint() && !isTypeVar() && !isMixed() && !isTypeConstant());
434 // This is part of the interpreter runtime; perf matters.
435 if (tv->m_type == KindOfRef) {
436 tv = tv->m_data.pref->tv();
439 if (isNullable() && tv->m_type == KindOfNull) {
440 return true;
443 if (tv->m_type == KindOfObject) {
444 // Perfect match seems common enough to be worth skipping the hash
445 // table lookup.
446 const Class *c = nullptr;
447 if (isObject()) {
448 if (m_typeName->isame(tv->m_data.pobj->getVMClass()->name())) {
449 return true;
451 // We can't save the Class* since it moves around from request
452 // to request.
453 assertx(m_namedEntity);
454 c = Unit::lookupClass(m_namedEntity);
455 } else {
456 switch (metaType()) {
457 case MetaType::Self:
458 selfToClass(func, &c);
459 break;
460 case MetaType::This:
461 switch (RuntimeOption::EvalThisTypeHintLevel) {
462 case 0: // Like Mixed.
463 return true;
464 break;
465 case 1: // Like Self.
466 selfToClass(func, &c);
467 break;
468 case 2: // Soft this in irgen verifyTypeImpl and verifyFail.
469 case 3: // Hard this.
470 thisToClass(&c);
471 if (c) {
472 return tv->m_data.pobj->getVMClass() == c;
474 return false;
475 break;
477 break;
478 case MetaType::Parent:
479 parentToClass(func, &c);
480 break;
481 case MetaType::Callable:
482 return is_callable(tvAsCVarRef(tv));
483 case MetaType::Precise:
484 case MetaType::Number:
485 case MetaType::ArrayKey:
486 case MetaType::VArray:
487 case MetaType::DArray:
488 case MetaType::VArrOrDArr:
489 case MetaType::VecOrDict:
490 return false;
491 case MetaType::Mixed:
492 // We assert'd at the top of this function that the
493 // metatype cannot be Mixed
494 not_reached();
497 if (c && tv->m_data.pobj->instanceof(c)) {
498 return true;
500 return isObject() && checkTypeAliasObj(tv->m_data.pobj->getVMClass());
503 auto const result = annotCompat(tv->m_type, m_type, m_typeName);
504 switch (result) {
505 case AnnotAction::Pass: return true;
506 case AnnotAction::Fail: return false;
507 case AnnotAction::CallableCheck:
508 return is_callable(tvAsCVarRef(tv));
509 case AnnotAction::ObjectCheck:
510 assertx(isObject());
511 return checkTypeAliasNonObj(tv);
512 case AnnotAction::VArrayCheck:
513 assertx(tvIsArray(tv));
514 return tv->m_data.parr->isVArray();
515 case AnnotAction::DArrayCheck:
516 assertx(tvIsArray(tv));
517 return tv->m_data.parr->isDArray();
518 case AnnotAction::VArrayOrDArrayCheck:
519 assertx(tvIsArray(tv));
520 return !tv->m_data.parr->isNotDVArray();
521 case AnnotAction::NonVArrayOrDArrayCheck:
522 assertx(tvIsArray(tv));
523 return tv->m_data.parr->isNotDVArray();
525 not_reached();
528 const char* describe_actual_type(const TypedValue* tv, bool isHHType) {
529 tv = tvToCell(tv);
530 switch (tv->m_type) {
531 case KindOfUninit: return "undefined variable";
532 case KindOfNull: return "null";
533 case KindOfBoolean: return "bool";
534 case KindOfInt64: return "int";
535 case KindOfDouble: return isHHType ? "float" : "double";
536 case KindOfPersistentString:
537 case KindOfString: return "string";
538 case KindOfPersistentVec:
539 case KindOfVec: return "HH\\vec";
540 case KindOfPersistentDict:
541 case KindOfDict: return "HH\\dict";
542 case KindOfPersistentKeyset:
543 case KindOfKeyset: return "HH\\keyset";
544 case KindOfPersistentArray:
545 case KindOfArray: return "array";
546 case KindOfObject: return tv->m_data.pobj->getClassName().c_str();
547 case KindOfResource:
548 return tv->m_data.pres->data()->o_getClassName().c_str();
550 case KindOfRef:
551 break;
553 not_reached();
556 void TypeConstraint::verifyParamFail(const Func* func, TypedValue* tv,
557 int paramNum, bool useStrictTypes) const {
558 verifyFail(func, tv, paramNum, useStrictTypes);
559 assertx(
560 isSoft() || !RuntimeOption::EvalHardTypeHints || (isThis() && couldSeeMockObject()) ||
561 (RuntimeOption::EvalHackArrCompatTypeHintNotices &&
562 isArrayType(tv->m_type)) ||
563 check(tv, func)
567 void TypeConstraint::verifyOutParamFail(const Func* func,
568 TypedValue* tv,
569 int paramNum) const {
570 auto const c = tvToCell(tv);
572 auto const done = [&] {
573 auto const check = [&](AnnotType at) {
574 switch (at) {
575 case AnnotType::Array:
576 if (c->m_data.parr->isNotDVArray()) return true;
577 break;
578 case AnnotType::VArray:
579 if (c->m_data.parr->isVArray()) return true;
580 break;
581 case AnnotType::DArray:
582 if (c->m_data.parr->isDArray()) return true;
583 break;
584 case AnnotType::VArrOrDArr:
585 if (!c->m_data.parr->isNotDVArray()) return true;
586 break;
587 default:
588 return false;
590 raise_hackarr_type_hint_outparam_notice(
591 func, c->m_data.parr, at, paramNum
593 return true;
595 if (LIKELY(!RuntimeOption::EvalHackArrCompatTypeHintNotices)) return false;
596 if (!isArrayType(c->m_type)) return false;
597 if (isArray()) return check(m_type);
598 if (!isObject()) return false;
599 if (auto alias = getTypeAliasWithAutoload(m_namedEntity, m_typeName)) {
600 return check(alias->type);
602 return false;
603 }();
604 if (done) return;
606 raise_return_typehint_error(
607 folly::sformat(
608 "Argument {} returned from {}() as an inout parameter must be of type "
609 "{}, {} given",
610 paramNum + 1,
611 func->fullDisplayName(),
612 displayName(func),
613 describe_actual_type(tv, isHHType())
618 void TypeConstraint::verifyFail(const Func* func, TypedValue* tv,
619 int id, bool useStrictTypes) const {
620 VMRegAnchor _;
621 std::string name = displayName(func);
622 auto const givenType = describe_actual_type(tv, isHHType());
624 auto const c = tvToCell(tv);
626 auto const done = [&] {
627 auto const check = [&](AnnotType at) {
628 switch (at) {
629 case AnnotType::Array:
630 if (c->m_data.parr->isNotDVArray()) return true;
631 break;
632 case AnnotType::VArray:
633 if (c->m_data.parr->isVArray()) return true;
634 break;
635 case AnnotType::DArray:
636 if (c->m_data.parr->isDArray()) return true;
637 break;
638 case AnnotType::VArrOrDArr:
639 if (!c->m_data.parr->isNotDVArray()) return true;
640 break;
641 default:
642 return false;
644 if (id == ReturnId) {
645 raise_hackarr_type_hint_ret_notice(
646 func,
647 c->m_data.parr,
650 } else {
651 raise_hackarr_type_hint_param_notice(
652 func,
653 c->m_data.parr,
658 return true;
660 if (LIKELY(!RuntimeOption::EvalHackArrCompatTypeHintNotices)) return false;
661 if (!isArrayType(c->m_type)) return false;
662 if (isArray()) return check(m_type);
663 if (!isObject()) return false;
664 if (auto alias = getTypeAliasWithAutoload(m_namedEntity, m_typeName)) {
665 return check(alias->type);
667 return false;
668 }();
669 if (done) return;
671 if (UNLIKELY(isThis() && c->m_type == KindOfObject)) {
672 Class* cls = c->m_data.pobj->getVMClass();
673 const Class* thisClass = nullptr;
674 thisToClass(&thisClass);
675 if (cls->preClass()->userAttributes().count(s___MockClass.get()) &&
676 cls->parent() == thisClass) {
677 return;
681 if (UNLIKELY(!useStrictTypes)) {
682 if (auto dt = underlyingDataType()) {
683 // In non-strict mode we may be able to coerce a type failure. For object
684 // typehints there is no possible coercion in the failure case, but HNI
685 // builtins currently only guard on kind not class so the following wil
686 // generate false positives for objects.
687 if (*dt != KindOfObject) {
688 // HNI conversions implicitly unbox references, this behavior is wrong,
689 // in particular it breaks the way type conversion works for PHP 7
690 // scalar type hints
691 if (tv->m_type == KindOfRef) {
692 auto inner = tv->m_data.pref->var()->asTypedValue();
693 if (tvCoerceParamInPlace(inner, *dt, func->isBuiltin())) {
694 tvAsVariant(tv) = tvAsVariant(inner);
695 return;
697 } else {
698 if (tvCoerceParamInPlace(tv, *dt, func->isBuiltin())) {
699 return;
704 } else if (UNLIKELY((!func->unit()->isHHFile() || func->isBuiltin()) &&
705 !RuntimeOption::EnableHipHopSyntax)) {
706 // PHP 7 allows for a widening conversion from Int to Float. We still ban
707 // this in HH files.
708 if (auto dt = underlyingDataType()) {
709 if (*dt == KindOfDouble && tv->m_type == KindOfInt64 &&
710 tvCoerceParamToDoubleInPlace(tv, func->isBuiltin())) {
711 return;
716 // Handle return type constraint failures
717 if (id == ReturnId) {
718 std::string msg;
719 if (func->isClosureBody()) {
720 msg =
721 folly::format(
722 "Value returned from {}closure must be of type {}, {} given",
723 func->isAsync() ? "async " : "",
724 name,
725 givenType
726 ).str();
727 } else {
728 msg =
729 folly::format(
730 "Value returned from {}{} {}() must be of type {}, {} given",
731 func->isAsync() ? "async " : "",
732 func->preClass() ? "method" : "function",
733 func->fullDisplayName(),
734 name,
735 givenType
736 ).str();
738 if (RuntimeOption::EvalCheckReturnTypeHints >= 2 && !isSoft()
739 && (!isThis() || RuntimeOption::EvalThisTypeHintLevel != 2)) {
740 raise_return_typehint_error(msg);
741 } else {
742 raise_warning_unsampled(msg);
744 return;
747 // Handle implicit collection->array conversion for array parameter type
748 // constraints
749 if (isArray() && !isSoft() && !func->mustBeRef(id) &&
750 c->m_type == KindOfObject && c->m_data.pobj->isCollection()) {
751 // To ease migration, the 'array' type constraint will implicitly cast
752 // collections to arrays, provided the type constraint is not soft and
753 // the parameter is not by reference. We raise a notice to let the user
754 // know that there was a type mismatch and that an implicit conversion
755 // was performed.
756 raise_notice(
757 folly::format(
758 "Argument {} to {}() must be of type {}, {} given; argument {} was "
759 "implicitly cast to array",
760 id + 1, func->fullDisplayName(), name, givenType, id + 1
761 ).str()
763 tvCastToArrayInPlace(tv);
764 return;
767 // Handle parameter type constraint failures
768 if (isExtended() &&
769 (isSoft() || (isThis() && RuntimeOption::EvalThisTypeHintLevel == 2))) {
770 // Soft extended type hints raise warnings instead of recoverable
771 // errors, to ease migration.
772 raise_warning_unsampled(
773 folly::format(
774 "Argument {} to {}() must be of type {}, {} given",
775 id + 1, func->fullDisplayName(), name, givenType
776 ).str()
778 } else if (isExtended() && isNullable()) {
779 raise_typehint_error(
780 folly::format(
781 "Argument {} to {}() must be of type {}, {} given",
782 id + 1, func->fullDisplayName(), name, givenType
783 ).str()
785 } else {
786 auto cls = Unit::lookupClass(m_typeName);
787 if (cls && isInterface(cls)) {
788 raise_typehint_error(
789 folly::format(
790 "Argument {} passed to {}() must implement interface {}, {} given",
791 id + 1, func->fullDisplayName(), name, givenType
792 ).str()
794 } else {
795 raise_typehint_error(
796 folly::format(
797 "Argument {} passed to {}() must be an instance of {}, {} given",
798 id + 1, func->fullDisplayName(), name, givenType
799 ).str()
805 void TypeConstraint::thisToClass(const Class **cls) const {
806 const ActRec* ar = vmfp();
807 if (ar->func()->cls()) {
808 if (ar->hasThis()) {
809 *cls = ar->getThis()->getVMClass();
810 } else {
811 assertx(ar->hasClass());
812 *cls = ar->getClass();
817 void TypeConstraint::selfToClass(const Func* func, const Class **cls) const {
818 const Class* c = func->cls();
819 if (c) {
820 *cls = c;
824 void TypeConstraint::selfToTypeName(const Func* func,
825 const StringData **typeName) const {
826 const Class* c = func->cls();
827 if (c) {
828 *typeName = c->name();
832 void TypeConstraint::parentToClass(const Func* func, const Class **cls) const {
833 Class* c1 = func->cls();
834 const Class* c2 = c1 ? c1->parent() : nullptr;
835 if (c2) {
836 *cls = c2;
840 void TypeConstraint::parentToTypeName(const Func* func,
841 const StringData **typeName) const {
842 const Class* c = nullptr;
843 parentToClass(func, &c);
844 if (c) {
845 *typeName = c->name();
849 //////////////////////////////////////////////////////////////////////
851 MemoKeyConstraint memoKeyConstraintFromTC(const TypeConstraint& tc) {
852 using MK = MemoKeyConstraint;
854 // Soft constraints aren't useful because they're not enforced.
855 if (!tc.hasConstraint() || tc.isTypeVar() ||
856 tc.isTypeConstant() || tc.isSoft()) {
857 return MK::None;
860 // Only a subset of possible type-constraints are useful to use. Namely,
861 // single types which might be nullable, and int/string combination.
862 switch (tc.metaType()) {
863 case AnnotMetaType::Precise: {
864 auto const dt = tc.underlyingDataType();
865 assertx(dt.hasValue());
866 switch (*dt) {
867 case KindOfNull: return MK::Null;
868 case KindOfBoolean:
869 return tc.isNullable() ? MK::BoolOrNull : MK::Bool;
870 case KindOfInt64:
871 return tc.isNullable() ? MK::IntOrNull : MK::Int;
872 case KindOfPersistentString:
873 case KindOfString:
874 return tc.isNullable() ? MK::StrOrNull : MK::Str;
875 case KindOfDouble:
876 case KindOfPersistentVec:
877 case KindOfVec:
878 case KindOfPersistentDict:
879 case KindOfDict:
880 case KindOfPersistentKeyset:
881 case KindOfKeyset:
882 case KindOfPersistentArray:
883 case KindOfArray:
884 case KindOfResource:
885 case KindOfObject: return MK::None;
886 case KindOfUninit:
887 case KindOfRef:
888 always_assert_flog(false, "Unexpected DataType");
890 not_reached();
892 case AnnotMetaType::ArrayKey:
893 return tc.isNullable() ? MK::None : MK::IntOrStr;
894 case AnnotMetaType::Mixed:
895 case AnnotMetaType::Self:
896 case AnnotMetaType::This:
897 case AnnotMetaType::Parent:
898 case AnnotMetaType::Callable:
899 case AnnotMetaType::Number:
900 case AnnotMetaType::VArray:
901 case AnnotMetaType::DArray:
902 case AnnotMetaType::VArrOrDArr:
903 case AnnotMetaType::VecOrDict:
904 return MK::None;
906 not_reached();
909 //////////////////////////////////////////////////////////////////////