@override-unit-failures Initial support for <?hh typedefs and shapes
Adds runtime support for non-class typehints. Typedefs are introduced using type statements, and autoloaded via a new autoload map entry. Shapes are parsed but the structure is currently thrown away and treated as arrays at runtime. This extends the NamedEntity structure to sometimes cache 'NameDefs', which are either Typedef*'s or Class*'s. VerifyParamType now has to check for typedefs if an object fails a class check, or when checking non-Object types against a non-primitive type name that isn't a class.
Esse commit está contido em:
@@ -90,6 +90,7 @@
|
||||
#include <compiler/statement/use_trait_statement.h>
|
||||
#include <compiler/statement/trait_prec_statement.h>
|
||||
#include <compiler/statement/trait_alias_statement.h>
|
||||
#include "compiler/statement/typedef_statement.h"
|
||||
|
||||
#include <compiler/parser/parser.h>
|
||||
|
||||
@@ -1636,6 +1637,10 @@ void EmitterVisitor::visit(FileScopePtr file) {
|
||||
if (h < allHoistable) allHoistable = h;
|
||||
break;
|
||||
}
|
||||
case Statement::KindOfTypedefStatement:
|
||||
emitTypedef(e, static_pointer_cast<TypedefStatement>(s));
|
||||
notMergeOnly = true; // TODO(#2103206): typedefs should be mergable
|
||||
break;
|
||||
case Statement::KindOfReturnStatement:
|
||||
if (mainReturn.m_type != KindOfInvalid) break;
|
||||
if (notMergeOnly) {
|
||||
@@ -1939,6 +1944,12 @@ bool EmitterVisitor::visit(ConstructPtr node) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
void EmitterVisitor::emitFatal(Emitter& e, const char* message) {
|
||||
const StringData* msg = StringData::GetStaticString(message);
|
||||
e.String(msg);
|
||||
e.Fatal(0);
|
||||
}
|
||||
|
||||
bool EmitterVisitor::visitImpl(ConstructPtr node) {
|
||||
if (!node) return false;
|
||||
|
||||
@@ -1951,6 +1962,12 @@ bool EmitterVisitor::visitImpl(ConstructPtr node) {
|
||||
visitKids(node);
|
||||
return false;
|
||||
|
||||
case Statement::KindOfTypedefStatement: {
|
||||
emitFatal(e, "Type statements are currently only allowed at "
|
||||
"the top-level");
|
||||
return false;
|
||||
}
|
||||
|
||||
case Statement::KindOfContinueStatement:
|
||||
case Statement::KindOfBreakStatement: {
|
||||
BreakStatementPtr bs(static_pointer_cast<BreakStatement>(s));
|
||||
@@ -6050,6 +6067,33 @@ void EmitterVisitor::emitClassUseTrait(PreClassEmitter* pce,
|
||||
}
|
||||
}
|
||||
|
||||
void EmitterVisitor::emitTypedef(Emitter& e, TypedefStatementPtr td) {
|
||||
auto kind = !strcasecmp(td->value.c_str(), "array") ? KindOfArray :
|
||||
!strcasecmp(td->value.c_str(), "int") ? KindOfInt64 :
|
||||
!strcasecmp(td->value.c_str(), "integer") ? KindOfInt64 :
|
||||
!strcasecmp(td->value.c_str(), "bool") ? KindOfBoolean :
|
||||
!strcasecmp(td->value.c_str(), "boolean") ? KindOfBoolean :
|
||||
!strcasecmp(td->value.c_str(), "string") ? KindOfString :
|
||||
!strcasecmp(td->value.c_str(), "real") ? KindOfDouble :
|
||||
!strcasecmp(td->value.c_str(), "float") ? KindOfDouble :
|
||||
!strcasecmp(td->value.c_str(), "double") ? KindOfDouble :
|
||||
KindOfObject;
|
||||
|
||||
// We have to merge the strings as litstrs to ensure namedentity
|
||||
// creation.
|
||||
auto const name = StringData::GetStaticString(td->name);
|
||||
auto const value = StringData::GetStaticString(td->value);
|
||||
m_ue.mergeLitstr(name);
|
||||
m_ue.mergeLitstr(value);
|
||||
|
||||
Typedef record;
|
||||
record.m_name = name;
|
||||
record.m_value = value;
|
||||
record.m_kind = kind;
|
||||
Id id = m_ue.addTypedef(record);
|
||||
e.DefTypedef(id);
|
||||
}
|
||||
|
||||
PreClass::Hoistable EmitterVisitor::emitClass(Emitter& e, ClassScopePtr cNode,
|
||||
bool toplevel) {
|
||||
InterfaceStatementPtr is(
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <compiler/statement/use_trait_statement.h>
|
||||
#include <compiler/statement/trait_prec_statement.h>
|
||||
#include <compiler/statement/trait_alias_statement.h>
|
||||
#include "compiler/statement/typedef_statement.h"
|
||||
|
||||
#include <runtime/vm/func.h>
|
||||
#include <runtime/vm/unit.h>
|
||||
@@ -504,6 +505,10 @@ private:
|
||||
int defI;
|
||||
};
|
||||
|
||||
private:
|
||||
void emitFatal(Emitter& e, const char* message);
|
||||
|
||||
private:
|
||||
static const size_t kMinStringSwitchCases = 8;
|
||||
UnitEmitter& m_ue;
|
||||
FuncEmitter* m_curFunc;
|
||||
@@ -642,6 +647,7 @@ public:
|
||||
void emitBuiltinDefaultArg(Emitter& e, Variant& v, DataType t, int paramId);
|
||||
PreClass::Hoistable emitClass(Emitter& e, ClassScopePtr cNode,
|
||||
bool topLevel);
|
||||
void emitTypedef(Emitter& e, TypedefStatementPtr);
|
||||
void emitBreakHandler(Emitter& e, Label& brkTarg, Label& cntTarg,
|
||||
Label& brkHand, Label& cntHand, Id iter = -1,
|
||||
IterKind itKind = KindOfIter);
|
||||
|
||||
+30869
-29498
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
+4581
-4342
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
@@ -78,6 +78,7 @@
|
||||
#include <compiler/statement/use_trait_statement.h>
|
||||
#include <compiler/statement/trait_prec_statement.h>
|
||||
#include <compiler/statement/trait_alias_statement.h>
|
||||
#include "compiler/statement/typedef_statement.h"
|
||||
|
||||
#include <compiler/analysis/function_scope.h>
|
||||
|
||||
@@ -1579,6 +1580,10 @@ void Parser::onGoto(Token &out, Token &label, bool limited) {
|
||||
out->stmt = NEW_STMT(GotoStatement, label.text());
|
||||
}
|
||||
|
||||
void Parser::onTypedef(Token& out, const Token& name, const Token& type) {
|
||||
out->stmt = NEW_STMT(TypedefStatement, name.text(), type.text());
|
||||
}
|
||||
|
||||
void Parser::invalidateGoto(TStatementPtr stmt, GotoError error) {
|
||||
GotoStatement *gs = (GotoStatement*) stmt;
|
||||
assert(gs);
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#ifndef __HPHP_PARSER_H__
|
||||
#define __HPHP_PARSER_H__
|
||||
#ifndef incl_HPHP_COMPILER_PARSER_H_
|
||||
#define incl_HPHP_COMPILER_PARSER_H_
|
||||
|
||||
#include <runtime/base/util/exceptions.h>
|
||||
#include <util/parser/parser.h>
|
||||
#include <compiler/construct.h>
|
||||
#include <compiler/option.h>
|
||||
#include "runtime/base/util/exceptions.h"
|
||||
#include "util/parser/parser.h"
|
||||
#include "compiler/construct.h"
|
||||
#include "compiler/option.h"
|
||||
|
||||
#ifdef HPHP_PARSER_NS
|
||||
#undef HPHP_PARSER_NS
|
||||
@@ -236,6 +236,7 @@ public:
|
||||
void onClosureParam(Token &out, Token *params, Token ¶m, bool ref);
|
||||
void onLabel(Token &out, Token &label);
|
||||
void onGoto(Token &out, Token &label, bool limited);
|
||||
void onTypedef(Token& out, const Token& name, const Token& type);
|
||||
|
||||
virtual void invalidateGoto(TStatementPtr stmt, GotoError error);
|
||||
virtual void invalidateLabel(TStatementPtr stmt);
|
||||
@@ -310,4 +311,4 @@ private:
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
}}
|
||||
|
||||
#endif // __HPHP_PARSER_H__
|
||||
#endif
|
||||
|
||||
@@ -78,7 +78,8 @@ DECLARE_BOOST_TYPES(Statement);
|
||||
x(LabelStatement), \
|
||||
x(UseTraitStatement), \
|
||||
x(TraitPrecStatement), \
|
||||
x(TraitAliasStatement)
|
||||
x(TraitAliasStatement), \
|
||||
x(TypedefStatement)
|
||||
|
||||
class Statement : public Construct {
|
||||
public:
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
+----------------------------------------------------------------------+
|
||||
| HipHop for PHP |
|
||||
+----------------------------------------------------------------------+
|
||||
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
|
||||
+----------------------------------------------------------------------+
|
||||
| This source file is subject to version 3.01 of the PHP license, |
|
||||
| that is bundled with this package in the file LICENSE, and is |
|
||||
| available through the world-wide-web at the following url: |
|
||||
| http://www.php.net/license/3_01.txt |
|
||||
| If you did not receive a copy of the PHP license and are unable to |
|
||||
| obtain it through the world-wide-web, please send a note to |
|
||||
| license@php.net so we can mail you a copy immediately. |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
#include "compiler/statement/typedef_statement.h"
|
||||
|
||||
namespace HPHP {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
TypedefStatement::TypedefStatement(
|
||||
STATEMENT_CONSTRUCTOR_PARAMETERS,
|
||||
const std::string& name,
|
||||
const std::string& value)
|
||||
: Statement(STATEMENT_CONSTRUCTOR_PARAMETER_VALUES(TypedefStatement))
|
||||
, name(name)
|
||||
, value(value)
|
||||
{}
|
||||
|
||||
TypedefStatement::~TypedefStatement() {}
|
||||
|
||||
StatementPtr TypedefStatement::clone() {
|
||||
return StatementPtr(new TypedefStatement(*this));
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
ConstructPtr TypedefStatement::getNthKid(int n) const {
|
||||
always_assert(0);
|
||||
}
|
||||
|
||||
int TypedefStatement::getKidCount() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void TypedefStatement::setNthKid(int n, ConstructPtr cp) {
|
||||
always_assert(0);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
void TypedefStatement::analyzeProgram(AnalysisResultPtr) {}
|
||||
|
||||
void TypedefStatement::inferTypes(AnalysisResultPtr) {}
|
||||
|
||||
void TypedefStatement::outputPHP(CodeGenerator& cg, AnalysisResultPtr ar) {
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
+----------------------------------------------------------------------+
|
||||
| HipHop for PHP |
|
||||
+----------------------------------------------------------------------+
|
||||
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
|
||||
+----------------------------------------------------------------------+
|
||||
| This source file is subject to version 3.01 of the PHP license, |
|
||||
| that is bundled with this package in the file LICENSE, and is |
|
||||
| available through the world-wide-web at the following url: |
|
||||
| http://www.php.net/license/3_01.txt |
|
||||
| If you did not receive a copy of the PHP license and are unable to |
|
||||
| obtain it through the world-wide-web, please send a note to |
|
||||
| license@php.net so we can mail you a copy immediately. |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#ifndef incl_HPHP_COMPILER_TYPEDEF_STATEMENT_H_
|
||||
#define incl_HPHP_COMPILER_TYPEDEF_STATEMENT_H_
|
||||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
#include <string>
|
||||
|
||||
#include "compiler/statement/statement.h"
|
||||
|
||||
namespace HPHP {
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
struct TypedefStatement : Statement {
|
||||
explicit TypedefStatement(STATEMENT_CONSTRUCTOR_PARAMETERS,
|
||||
const std::string& name,
|
||||
const std::string& value);
|
||||
~TypedefStatement();
|
||||
|
||||
DECLARE_STATEMENT_VIRTUAL_FUNCTIONS;
|
||||
|
||||
const std::string name;
|
||||
const std::string value;
|
||||
};
|
||||
|
||||
typedef boost::shared_ptr<TypedefStatement> TypedefStatementPtr;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -3498,6 +3498,16 @@ DefCns <litstr id> [C] -> [C]
|
||||
pushes false. Otherwise defines the constant named %1 to have the value $1,
|
||||
and pushes true.
|
||||
|
||||
DefTypedef <litstr id> [] -> []
|
||||
|
||||
Define typedef. Typedefs are a hhvm extension to php that allow
|
||||
declaring new names for existing types. The unit contains a table
|
||||
of the typedefs it was compiled with. This instruction looks up the
|
||||
typedef given by %1 in the table. If there is an existing class or
|
||||
typedef defined with the same name as %1, this function checks
|
||||
whether it is compatible with the typedef given by %1, and if it
|
||||
isn't it raises a fatal error.
|
||||
|
||||
|
||||
13. Miscellaneous instructions
|
||||
------------------------------
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
Notes on the `shapes' feature in Hack:
|
||||
|
||||
Shapes in <?hh files are a way for us to add typechecking
|
||||
incrementally to existing php code that uses php arrays somewhat like
|
||||
structs.
|
||||
|
||||
For example, code like this:
|
||||
|
||||
<?php
|
||||
|
||||
function dot_product($a, $b) {
|
||||
return $a['x'] * $b['x'] + $a['y'] * $b['y'];
|
||||
}
|
||||
|
||||
Can be annotated with types to look like this:
|
||||
|
||||
<?hh
|
||||
|
||||
type Point2D = shape('x' => int, 'y' => int);
|
||||
|
||||
function dot_product(Point2D $a, Point2D $b): int {
|
||||
return $a['x'] * $b['x'] + $a['y'] * $b['y'];
|
||||
}
|
||||
|
||||
This is not a complete design rationale, but contains a few notes to
|
||||
explain why things are the way they are so far.
|
||||
|
||||
Notes:
|
||||
|
||||
- Classes with public members (or getters/setters) exist and probably
|
||||
work fine as a sort of record/struct thing. When people want them
|
||||
(particularly in newer code), they work.
|
||||
|
||||
- This is intended to allow type-checking cases where arrays are
|
||||
currently being used as aggregates (instead of people using
|
||||
classes). In order for them to be adopted and do what they are
|
||||
intended to do, migration has to be as painless as possible (which
|
||||
is why the object-like structs above are not "enough": no one is
|
||||
going to switch all of www away from struct-like arrays to that).
|
||||
|
||||
- Generally we want most <?hh migration to be primarily about adding
|
||||
type annotations somewhere in a function signature, not about
|
||||
significantly changing method bodies.
|
||||
|
||||
- To be explicit: this last point means $x['foo'] still needs to be
|
||||
valid syntax for accessing a shape member to ease conversion in the
|
||||
short term. For construction sites we are just doing s/array/shape/
|
||||
when you want them checked as shapes (which will also require at
|
||||
parse time that the keys are string literals, do not start with
|
||||
digits, etc).
|
||||
|
||||
- People sometimes write code that operates on sub-pieces of these
|
||||
record-like arrays. This means a traditional (C/C++/Java style)
|
||||
structure is going to be a little weird to add type annotations
|
||||
for. You can imagine trying to migrate a function to <?hh that
|
||||
legitimately take "any array with elements named 'id' and 'name'
|
||||
that are int and string", and is called with various other
|
||||
struct-like arrays that have other elements lying around in them. To
|
||||
make this possible to migrate with minimal pain, we're using
|
||||
structural subtyping.
|
||||
|
||||
- Because they don't work like the "structs" people are used to,
|
||||
calling it a "struct" seemed potentially confusing.
|
||||
|
||||
- With enough code in hack so we know the types of things, it might be
|
||||
possible to automate migration to a new syntax at access
|
||||
sites. However, in the short term, we definitely can't use $x->foo
|
||||
since this is an object property access and the runtime can't
|
||||
compile it differently based on the type of $x. You might also
|
||||
consider $x.foo (which might work in <?hh files for reasons I'll
|
||||
leave out), but again for the foreseeable future we probably want
|
||||
the same syntax to work in a <?php file, and right now this means
|
||||
the variable $x converted to a string and concatenated with the
|
||||
value of the constant foo (which will be the string "foo" if
|
||||
undefined).
|
||||
|
||||
- In the syntax for defining shape *types*, our hands are probably a
|
||||
little less tied. The current choice was to try to keep it somewhat
|
||||
similar to the access sites and arrays in general (maybe helps so we
|
||||
can explain this as "shapes are just special/limited arrays"), and
|
||||
also to fit in with typedefs and possible syntax for adding enums.
|
||||
@@ -0,0 +1,100 @@
|
||||
Notes on the typedef feature in Hack:
|
||||
|
||||
In HipHop, <?hh files allow a language extension to define new types
|
||||
for use in typehinting. The syntax looks like this:
|
||||
|
||||
type MyInt = int;
|
||||
type Point = (int, int);
|
||||
|
||||
Or
|
||||
|
||||
newtype AbstractInt = int;
|
||||
|
||||
This document is not a complete design rationale, but may address a
|
||||
few questions.
|
||||
|
||||
Notes:
|
||||
|
||||
- We want to support short names for types that should be compatible
|
||||
with the underlying representation. The syntax used above is found
|
||||
in several languages (Haskell, OCaml, F#, Rust, others). Go is
|
||||
similar, but has no equal sign. C#/Java don't have typedefs so they
|
||||
don't provide much inspiration. C/C++'s typedef/declaration syntax
|
||||
isn't appropriate (or particularly reasonable).
|
||||
|
||||
- Another use case is making types that abstract away the details of
|
||||
their underlying implementation. For example:
|
||||
|
||||
<?hh
|
||||
|
||||
newtype SQLSafeString = string;
|
||||
|
||||
function scrub_user_input_for_sql(string $str): SQLSafeString {
|
||||
// ...
|
||||
}
|
||||
|
||||
Haskell uses "newtype" in this case, but requires a named "value
|
||||
constructor" which you have to use to turn strings into
|
||||
SQLSafeString in the first place. OCaml doesn't have this kind of
|
||||
newtype, but you can use the module system to create abstract types.
|
||||
|
||||
C++ has strong typedefs only for integral types, with the syntax
|
||||
"enum class Foo : int {}", which is clearly not something to
|
||||
emulate. A language proposal for general strong typedefs has
|
||||
suggested a "using Foo = private int" syntax, which is probably
|
||||
worth avoiding based on keyword reuse and being a little strange
|
||||
(and for that matter not actually part of C++ yet, so it may
|
||||
change).
|
||||
|
||||
The semantics planned here is that a type declared with "newtype" is
|
||||
exactly the same as one declared with "type" as far as the runtime
|
||||
is concerned, but Hack will consider it compatible with its
|
||||
underlying type only in the file that declared it.
|
||||
|
||||
- Since we want to use them for signatures on functions, we considered
|
||||
two choices relating to how typedefs would interact with the
|
||||
runtime. One option is to "drop" typehints about them the same way
|
||||
most Hack annotations are dropped. Unfortunately this has an impact
|
||||
on syntax. Imagine the following function definition:
|
||||
|
||||
function is_x_large(Point2D $x): bool { return $x['x'] > 10; }
|
||||
|
||||
Since hhvm compiles php files separately, we don't know at bytecode
|
||||
generation time whether Point2D should be a typehint for a class or
|
||||
dropped for Hack. One option is to mangle hints for types:
|
||||
|
||||
function is_x_large(type Point2D $x): bool { return $x['x'] > 10; }
|
||||
|
||||
We figured this syntax (and other variants) might hurt adoption
|
||||
excessively, and runtime checking of typedefs is also preferable to
|
||||
wrong behavior.
|
||||
|
||||
- Because of this, we need some runtime changes for typedefs. An
|
||||
identifier in a typehint might be a typedef instead of a class in a
|
||||
given request.
|
||||
|
||||
- We support typedefs from one class to another. For Hack the
|
||||
motivation is mostly support for typedefs of generics. For example:
|
||||
|
||||
newtype LinearModel = Vector<float>;
|
||||
|
||||
As far as the runtime is concerned, this just means that the type
|
||||
LinearModel = Vector. Generic information is currently dropped, and
|
||||
only checked by Hack.
|
||||
|
||||
- Typehints for typedefs only check the "top-level" type for primitive
|
||||
types. In the case of shapes, this means we only check for now that
|
||||
the parameter is KindOfArray, and rely on Hack to typecheck the
|
||||
contents. This is pretty much the best we can do currently: the
|
||||
runtime doesn't really know much about the "deep" type of most
|
||||
runtime values.
|
||||
|
||||
- We do not support "instanceof SomeTypedef". Rationale: instanceof
|
||||
is important to perf, and we don't know of an important use case.
|
||||
Not implementing it yet seems like a safe position, since we could
|
||||
find a way to add it later if there is a compelling use case.
|
||||
|
||||
- You can't allocate a class instance via a typedef name.
|
||||
Essentially, declaring new types only creates new names for
|
||||
type---it does not create new "value constructors". This may be
|
||||
changed later if we find it is useful.
|
||||
@@ -182,7 +182,7 @@ void ProcessInit() {
|
||||
|
||||
#define INIT_SYSTEMLIB_CLASS_FIELD(cls) \
|
||||
{ \
|
||||
Class *cls = *Unit::GetNamedEntity(s_##cls.get())->clsList(); \
|
||||
Class *cls = Unit::GetNamedEntity(s_##cls.get())->clsList(); \
|
||||
assert(!hhbc_ext_class_count || cls); \
|
||||
SystemLib::s_##cls##Class = cls; \
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ StaticString s_static("static");
|
||||
StaticString s_class("class");
|
||||
StaticString s_function("function");
|
||||
StaticString s_constant("constant");
|
||||
StaticString s_type("type");
|
||||
StaticString s_failure("failure");
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@@ -1370,6 +1371,15 @@ bool AutoloadHandler::autoloadConstant(CStrRef name) {
|
||||
loadFromMap(name, s_constant, false, ConstantExistsChecker()) != Failure;
|
||||
}
|
||||
|
||||
bool AutoloadHandler::autoloadType(CStrRef name) {
|
||||
return !m_map.isNull() &&
|
||||
loadFromMap(name, s_type, true,
|
||||
[] (CStrRef name) {
|
||||
return !!VM::Unit::GetNamedEntity(name.get())->getCachedNameDef();
|
||||
}
|
||||
) != Failure;
|
||||
}
|
||||
|
||||
/**
|
||||
* invokeHandler returns true if any autoload handlers were executed,
|
||||
* false otherwise. When this function returns true, it is the caller's
|
||||
|
||||
@@ -582,6 +582,7 @@ public:
|
||||
bool invokeHandler(CStrRef className, bool forceSplStack = false);
|
||||
bool autoloadFunc(CStrRef name);
|
||||
bool autoloadConstant(CStrRef name);
|
||||
bool autoloadType(CStrRef name);
|
||||
bool setMap(CArrRef map, CStrRef root);
|
||||
DECLARE_STATIC_REQUEST_LOCAL(AutoloadHandler, s_instance);
|
||||
|
||||
|
||||
@@ -1092,18 +1092,19 @@ void RuntimeOption::Load(Hdf &config, StringVec *overwrites /* = NULL */,
|
||||
EnableHipHopExperimentalSyntax =
|
||||
eval["EnableHipHopExperimentalSyntax"].getBool();
|
||||
EnableShortTags= eval["EnableShortTags"].getBool(true);
|
||||
if (EnableShortTags) ScannerType |= Scanner::AllowShortTags;
|
||||
else ScannerType &= ~Scanner::AllowShortTags;
|
||||
|
||||
EnableAspTags = eval["EnableAspTags"].getBool();
|
||||
if (EnableAspTags) ScannerType |= Scanner::AllowAspTags;
|
||||
else ScannerType &= ~Scanner::AllowAspTags;
|
||||
|
||||
EnableXHP = eval["EnableXHP"].getBool(true);
|
||||
EnableObjDestructCall = eval["EnableObjDestructCall"].getBool(false);
|
||||
MaxUserFunctionId = eval["MaxUserFunctionId"].getInt32(2 * 65536);
|
||||
CheckSymLink = eval["CheckSymLink"].getBool(false);
|
||||
|
||||
if (EnableHipHopSyntax) ScannerType |= Scanner::EnableHipHopKeywords;
|
||||
else ScannerType &= ~Scanner::EnableHipHopKeywords;
|
||||
if (EnableShortTags) ScannerType |= Scanner::AllowShortTags;
|
||||
else ScannerType &= ~Scanner::AllowShortTags;
|
||||
if (EnableAspTags) ScannerType |= Scanner::AllowAspTags;
|
||||
else ScannerType &= ~Scanner::AllowAspTags;
|
||||
|
||||
EnableAlternative = eval["EnableAlternative"].getInt32(0);
|
||||
|
||||
EnableFinallyStatement = eval["EnableFinallyStatement"].getBool();
|
||||
|
||||
@@ -1470,13 +1470,14 @@ bool f_fb_rename_function(CStrRef orig_func_name, CStrRef new_func_name) {
|
||||
}
|
||||
|
||||
/*
|
||||
fb_autoload_map($map, $root) specifies a mapping
|
||||
from classes, functions and constants to the files
|
||||
that define them. The map has the form:
|
||||
fb_autoload_map($map, $root) specifies a mapping from classes,
|
||||
functions, constants, and typedefs to the files that define
|
||||
them. The map has the form:
|
||||
|
||||
array('class' => array('cls' => 'cls_file.php', ...),
|
||||
'function' => array('fun' => 'fun_file.php', ...),
|
||||
'constant' => array('con' => 'con_file.php', ...),
|
||||
'type' => array('type' => 'type_file.php', ...),
|
||||
'failure' => callable);
|
||||
|
||||
If the 'failure' element exists, it will be called if the
|
||||
|
||||
@@ -5646,7 +5646,7 @@ inline void OPTBLD_INLINE VMExecutionContext::iopFPushCtorD(PC& pc) {
|
||||
m_fp->m_func->unit()->lookupNamedEntityPairId(id);
|
||||
Class* cls = Unit::loadClass(nep.second, nep.first);
|
||||
if (cls == nullptr) {
|
||||
raise_error("Undefined class: %s",
|
||||
raise_error(Strings::UNKNOWN_CLASS,
|
||||
m_fp->m_func->unit()->lookupLitstrId(id)->data());
|
||||
}
|
||||
// Lookup the ctor
|
||||
@@ -6477,6 +6477,12 @@ inline void OPTBLD_INLINE VMExecutionContext::iopDefCls(PC& pc) {
|
||||
Unit::defClass(c);
|
||||
}
|
||||
|
||||
inline void OPTBLD_INLINE VMExecutionContext::iopDefTypedef(PC& pc) {
|
||||
NEXT();
|
||||
DECODE_IVA(tid);
|
||||
m_fp->m_func->unit()->defTypedef(tid);
|
||||
}
|
||||
|
||||
static inline void checkThis(ActRec* fp) {
|
||||
if (!fp->hasThis()) {
|
||||
raise_error(Strings::FATAL_NULL_THIS);
|
||||
@@ -7386,7 +7392,7 @@ void VMExecutionContext::requestInit() {
|
||||
profileRequestStart();
|
||||
|
||||
#ifdef DEBUG
|
||||
Class *cls = *Unit::GetNamedEntity(s_stdclass.get())->clsList();
|
||||
Class* cls = Unit::GetNamedEntity(s_stdclass.get())->clsList();
|
||||
assert(cls);
|
||||
assert(cls == SystemLib::s_stdclassClass);
|
||||
#endif
|
||||
|
||||
@@ -527,12 +527,7 @@ void Class::atomicRelease() {
|
||||
PreClass* pcls = m_preClass.get();
|
||||
{
|
||||
Lock l(Unit::s_classesMutex);
|
||||
Class *const*cls = pcls->namedEntity()->clsList();
|
||||
while (*cls != this) {
|
||||
assert(*cls);
|
||||
cls = &(*cls)->m_nextClass;
|
||||
}
|
||||
*const_cast<Class**>(cls) = m_nextClass;
|
||||
pcls->namedEntity()->removeClass(this);
|
||||
}
|
||||
Treadmill::WorkItem::enqueue(new Treadmill::FreeClassTrigger(this));
|
||||
return;
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <util/range.h>
|
||||
#include <runtime/vm/fixed_string_map.h>
|
||||
#include <runtime/vm/indexed_string_map.h>
|
||||
#include "runtime/vm/named_entity.h"
|
||||
|
||||
namespace HPHP {
|
||||
|
||||
@@ -311,7 +312,7 @@ class PreClass : public AtomicCountable {
|
||||
|
||||
void prettyPrint(std::ostream& out) const;
|
||||
|
||||
const NamedEntity* namedEntity() const { return m_namedEntity; }
|
||||
NamedEntity* namedEntity() const { return m_namedEntity; }
|
||||
|
||||
private:
|
||||
typedef IndexedStringMap<Func*,false,Slot> MethodMap;
|
||||
@@ -320,7 +321,7 @@ private:
|
||||
|
||||
private:
|
||||
Unit* m_unit;
|
||||
const NamedEntity* m_namedEntity;
|
||||
NamedEntity* m_namedEntity;
|
||||
int m_line1;
|
||||
int m_line2;
|
||||
Offset m_offset;
|
||||
|
||||
@@ -536,6 +536,7 @@ enum SetOpOp {
|
||||
O(DefFunc, ONE(IVA), NOV, NOV, NF) \
|
||||
O(DefCls, ONE(IVA), NOV, NOV, NF) \
|
||||
O(DefCns, ONE(SA), ONE(CV), ONE(CV), NF) \
|
||||
O(DefTypedef, ONE(IVA), NOV, NOV, NF) \
|
||||
O(This, NA, NOV, ONE(CV), NF) \
|
||||
O(BareThis, ONE(OA), NOV, ONE(CV), NF) \
|
||||
O(CheckThis, NA, NOV, NOV, NF) \
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
+----------------------------------------------------------------------+
|
||||
| HipHop for PHP |
|
||||
+----------------------------------------------------------------------+
|
||||
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
|
||||
+----------------------------------------------------------------------+
|
||||
| This source file is subject to version 3.01 of the PHP license, |
|
||||
| that is bundled with this package in the file LICENSE, and is |
|
||||
| available through the world-wide-web at the following url: |
|
||||
| http://www.php.net/license/3_01.txt |
|
||||
| If you did not receive a copy of the PHP license and are unable to |
|
||||
| obtain it through the world-wide-web, please send a note to |
|
||||
| license@php.net so we can mail you a copy immediately. |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#ifndef incl_HPHP_VM_NAMED_ENTITY_H_
|
||||
#define incl_HPHP_VM_NAMED_ENTITY_H_
|
||||
|
||||
#include <tbb/concurrent_unordered_map.h>
|
||||
#include <boost/operators.hpp>
|
||||
|
||||
#include "runtime/base/complex_types.h"
|
||||
|
||||
#include "util/atomic.h"
|
||||
|
||||
namespace HPHP { namespace VM {
|
||||
|
||||
class Class;
|
||||
class Typedef;
|
||||
class Func;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
* A NameDef is a definition of a type for a given name. This
|
||||
* currently means it is either a Class*, a Typedef*, or null.
|
||||
*/
|
||||
struct NameDef : private boost::equality_comparable<NameDef> {
|
||||
explicit NameDef() : m_p(0) {}
|
||||
explicit NameDef(Class* c) : m_p(reinterpret_cast<uintptr_t>(c)) {
|
||||
assert(!(m_p & 0x1));
|
||||
}
|
||||
|
||||
explicit NameDef(Typedef* t)
|
||||
: m_p(reinterpret_cast<uintptr_t>(t) | 0x1)
|
||||
{}
|
||||
|
||||
Class* asClass() const {
|
||||
return m_p & 0x1 ? nullptr : reinterpret_cast<Class*>(m_p);
|
||||
}
|
||||
|
||||
Typedef* asTypedef() const {
|
||||
return m_p & 0x1 ? reinterpret_cast<Typedef*>(m_p - 1) : nullptr;
|
||||
}
|
||||
|
||||
explicit operator bool() const { return !!m_p; }
|
||||
|
||||
bool operator==(NameDef other) const {
|
||||
return m_p == other.m_p;
|
||||
}
|
||||
|
||||
private:
|
||||
uintptr_t m_p;
|
||||
};
|
||||
|
||||
/*
|
||||
* NamedEntity represents a user-defined name that may map to
|
||||
* different objects (NameDefs) in different requests. Classes and
|
||||
* functions are in separate namespaces, so we have a targetcache
|
||||
* offset for resolving each.
|
||||
*
|
||||
* Classes and typedefs are in the same namespace when we're naming
|
||||
* types, but different namespaces at sites that allocate classes. If
|
||||
* a typedef is defined for a given name, we'll cache a NameDef in
|
||||
* each request at m_cachedNameDefOffset. Classes are always cached
|
||||
* at m_cachedClassOffset. See TargetCache::allocNameDef.
|
||||
*/
|
||||
struct NamedEntity {
|
||||
explicit NamedEntity()
|
||||
: m_cachedClassOffset(0)
|
||||
, m_cachedFuncOffset(0)
|
||||
, m_cachedNameDefOffset(0)
|
||||
, m_clsList(nullptr)
|
||||
{}
|
||||
|
||||
unsigned m_cachedClassOffset;
|
||||
unsigned m_cachedFuncOffset;
|
||||
unsigned m_cachedNameDefOffset;
|
||||
|
||||
void setCachedFunc(Func *f);
|
||||
Func* getCachedFunc() const;
|
||||
|
||||
void setCachedClass(Class* c);
|
||||
Class* getCachedClass() const;
|
||||
|
||||
NameDef getCachedNameDef() const;
|
||||
void setCachedNameDef(NameDef);
|
||||
|
||||
Class* clsList() const {
|
||||
return m_clsList;
|
||||
}
|
||||
|
||||
// Call while holding s_classesMutex. Add or remove classes from
|
||||
// the list.
|
||||
void pushClass(Class* cls);
|
||||
void removeClass(Class* goner);
|
||||
|
||||
private:
|
||||
Class* m_clsList;
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
typedef tbb::concurrent_unordered_map<
|
||||
const StringData*,
|
||||
NamedEntity,
|
||||
string_data_hash,
|
||||
string_data_isame
|
||||
> NamedEntityMap;
|
||||
typedef std::pair<const StringData*,const NamedEntity*> NamedEntityPair;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
}}
|
||||
|
||||
#endif
|
||||
@@ -1850,16 +1850,28 @@ void HhbcTranslator::emitVerifyParamType(int32_t paramId) {
|
||||
m_tb->gen(VerifyParamCallable, locVal, cns(paramId));
|
||||
return;
|
||||
}
|
||||
// For non-object guards, or object guards with non-object runtime
|
||||
// types, we rely on what we know from the tracelet guards and never
|
||||
// have to do runtime checks.
|
||||
if ((!tc.isObject() && !tc.check(locType.toDataType())) ||
|
||||
(tc.isObject() && !locType.subtypeOf(Type::Obj))) {
|
||||
spillStack();
|
||||
m_tb->gen(VerifyParamFail, cns(paramId));
|
||||
|
||||
// For non-object guards, we rely on what we know from the tracelet
|
||||
// guards and never have to do runtime checks.
|
||||
if (!tc.isObjectOrTypedef()) {
|
||||
if (!tc.checkPrimitive(locType.toDataType())) {
|
||||
spillStack();
|
||||
m_tb->gen(VerifyParamFail, cns(paramId));
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!tc.isObject()) {
|
||||
|
||||
/*
|
||||
* If the parameter is an object, we check the object in one of
|
||||
* various ways (similar to instance of). If the parameter is not
|
||||
* an object, it still might pass the VerifyParamType if the
|
||||
* constraint is a typedef.
|
||||
*
|
||||
* For now we just interp that case.
|
||||
*/
|
||||
if (!locType.isObj()) {
|
||||
emitInterpOneOrPunt(Type::None);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1897,9 +1909,9 @@ void HhbcTranslator::emitVerifyParamType(int32_t paramId) {
|
||||
locVal = m_tb->gen(Unbox, getExitTrace(), locVal);
|
||||
SSATmp* objClass = m_tb->gen(LdObjClass, locVal);
|
||||
if (haveBit || classIsUniqueNormalClass(knownConstraint)) {
|
||||
SSATmp* isInstance = haveBit ?
|
||||
m_tb->gen(InstanceOfBitmask, objClass, cns(clsName))
|
||||
: m_tb->gen(ExtendsClass, objClass, constraint);
|
||||
SSATmp* isInstance = haveBit
|
||||
? m_tb->gen(InstanceOfBitmask, objClass, cns(clsName))
|
||||
: m_tb->gen(ExtendsClass, objClass, constraint);
|
||||
m_tb->ifThen(getCurFunc(),
|
||||
[&](Block* taken) {
|
||||
m_tb->gen(JmpZero, taken, isInstance);
|
||||
@@ -1912,7 +1924,11 @@ void HhbcTranslator::emitVerifyParamType(int32_t paramId) {
|
||||
);
|
||||
} else {
|
||||
spillStack();
|
||||
m_tb->gen(VerifyParamCls, objClass, constraint, cns(paramId));
|
||||
m_tb->gen(VerifyParamCls,
|
||||
objClass,
|
||||
constraint,
|
||||
cns(paramId),
|
||||
cns(uintptr_t(&tc)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -334,7 +334,10 @@ O(DefLabel, DMulti, SUnk, E) \
|
||||
O(Marker, ND, NA, E) \
|
||||
O(DefFP, D(StkPtr), NA, E) \
|
||||
O(DefSP, D(StkPtr), S(StkPtr) C(Int), E) \
|
||||
O(VerifyParamCls, ND, S(Cls) S(Cls) C(Int),E|N|Mem|Refs|Er) \
|
||||
O(VerifyParamCls, ND, S(Cls) \
|
||||
S(Cls) \
|
||||
C(Int) \
|
||||
C(Int), E|N|Mem|Refs|Er) \
|
||||
O(VerifyParamCallable, ND, S(Cell) C(Int), E|N|Mem|Refs|Er) \
|
||||
O(VerifyParamFail, ND, C(Int), E|N|Mem|Refs|Er) \
|
||||
O(RaiseUninitLoc, ND, S(Str), E|N|Mem|Refs|Er) \
|
||||
|
||||
@@ -78,7 +78,7 @@ static CallMap s_callMap({
|
||||
{PrintInt, (TCA)print_int, DNone, SNone, {{SSA, 0}}},
|
||||
{PrintBool, (TCA)print_boolean, DNone, SNone, {{SSA, 0}}},
|
||||
{VerifyParamCls, (TCA)VerifyParamTypeSlow, DNone, SSync,
|
||||
{{SSA, 0}, {SSA, 1}, {SSA, 2}}},
|
||||
{{SSA, 0}, {SSA, 1}, {SSA, 2}, {SSA, 3}}},
|
||||
{VerifyParamCallable, (TCA)VerifyParamTypeCallable, DNone, SSync,
|
||||
{{TV, 0}, {SSA, 1}}},
|
||||
{VerifyParamFail, (TCA)VerifyParamTypeFail, DNone, SSync, {{SSA, 0}}},
|
||||
|
||||
@@ -717,6 +717,14 @@ CacheHandle allocFixedFunction(const StringData* name) {
|
||||
return allocFixedFunction(Unit::GetNamedEntity(name), false);
|
||||
}
|
||||
|
||||
CacheHandle allocNameDef(const NamedEntity* ne) {
|
||||
if (ne->m_cachedNameDefOffset) {
|
||||
return ne->m_cachedNameDefOffset;
|
||||
}
|
||||
return allocFuncOrClass(&ne->m_cachedNameDefOffset,
|
||||
false /* persistent */); // TODO(#2103214): support persistent
|
||||
}
|
||||
|
||||
template<bool checkOnly>
|
||||
Class*
|
||||
lookupKnownClass(Class** cache, const StringData* clsName, bool isClass) {
|
||||
|
||||
@@ -304,9 +304,22 @@ Class* lookupKnownClass(Class** cache, const StringData* clsName,
|
||||
CacheHandle allocClassInitProp(const StringData* name);
|
||||
CacheHandle allocClassInitSProp(const StringData* name);
|
||||
|
||||
/*
|
||||
* Functions.
|
||||
*/
|
||||
CacheHandle allocFixedFunction(const NamedEntity* ne, bool persistent);
|
||||
CacheHandle allocFixedFunction(const StringData* name);
|
||||
|
||||
/*
|
||||
* NameDefs.
|
||||
*
|
||||
* Request-private values for typedef names. When a typedef is
|
||||
* defined, a NameDef for it is cached here. If it is a typedef for a
|
||||
* class, the NameDef will be for the target class. Otherwise it's a
|
||||
* NameDef pointing to the Typedef*.
|
||||
*/
|
||||
CacheHandle allocNameDef(const NamedEntity* name);
|
||||
|
||||
/*
|
||||
* Constants.
|
||||
*
|
||||
|
||||
@@ -82,10 +82,32 @@ void VerifyParamTypeCallable(TypedValue value, int param) {
|
||||
}
|
||||
|
||||
HOT_FUNC_VM
|
||||
void VerifyParamTypeSlow(const Class* cls, const Class* constraint, int param) {
|
||||
if (UNLIKELY(!(constraint && cls->classof(constraint)))) {
|
||||
VerifyParamTypeFail(param);
|
||||
void VerifyParamTypeSlow(const Class* cls,
|
||||
const Class* constraint,
|
||||
int param,
|
||||
const TypeConstraint* expected) {
|
||||
if (LIKELY(constraint && cls->classof(constraint))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check a typedef for a class. We interp'd if the param wasn't an
|
||||
// object, so if it's a typedef for something non-objecty we're
|
||||
// failing anyway.
|
||||
if (auto namedEntity = expected->namedEntity()) {
|
||||
NameDef def = namedEntity->getCachedNameDef();
|
||||
if (UNLIKELY(!def)) {
|
||||
VMRegAnchor _;
|
||||
String nameStr(const_cast<StringData*>(expected->typeName()));
|
||||
if (AutoloadHandler::s_instance->autoloadType(nameStr)) {
|
||||
def = namedEntity->getCachedNameDef();
|
||||
}
|
||||
}
|
||||
if (def && (constraint = def.asClass()) && cls->classof(constraint)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
VerifyParamTypeFail(param);
|
||||
}
|
||||
|
||||
} } }
|
||||
|
||||
@@ -61,7 +61,10 @@ void raisePropertyOnNonObject();
|
||||
void raiseUndefProp(ObjectData* base, const StringData* name);
|
||||
void VerifyParamTypeFail(int param);
|
||||
void VerifyParamTypeCallable(TypedValue value, int param);
|
||||
void VerifyParamTypeSlow(const Class* cls, const Class* constraint, int param);
|
||||
void VerifyParamTypeSlow(const Class* cls,
|
||||
const Class* constraint,
|
||||
int param,
|
||||
const TypeConstraint* expected);
|
||||
|
||||
|
||||
void raise_error_sd(const StringData* sd);
|
||||
|
||||
@@ -10288,7 +10288,7 @@ void
|
||||
TranslatorX64::analyzeVerifyParamType(Tracelet& t, NormalizedInstruction& i) {
|
||||
int param = i.imm[0].u_IVA;
|
||||
const TypeConstraint& tc = curFunc()->params()[param].typeConstraint();
|
||||
if (!tc.isObject()) {
|
||||
if (!tc.isObjectOrTypedef()) {
|
||||
// We are actually using the translation-time value of this local as a
|
||||
// prediction; if the param check failed at compile-time, we predict it
|
||||
// will continue failing.
|
||||
@@ -10387,18 +10387,20 @@ TranslatorX64::translateVerifyParamType(const Tracelet& t,
|
||||
Stats::emitInc(a, Stats::Tx64_VerifyParamTypeFast);
|
||||
emitInstanceCheck(t, i, clsName, constraint, inCls, cls, dummy);
|
||||
} else {
|
||||
// Compare this class to the incoming object's class. If the typehint's
|
||||
// class is not present, can not be an instance: fail
|
||||
// Compare this class to the incoming object's class. If the
|
||||
// typehint's class is not present, can not be an instance, unless
|
||||
// this is a typedef. The slow path handles that case.
|
||||
Stats::emitInc(a, Stats::Tx64_VerifyParamTypeSlowShortcut);
|
||||
a. cmp_reg64_reg64(r(inCls), r(cls));
|
||||
{
|
||||
JccBlock<CC_E> subclassCheck(a);
|
||||
// Call helper since ObjectData::instanceof is a member function
|
||||
if (false) {
|
||||
VerifyParamTypeSlow(constraint, constraint, param);
|
||||
VerifyParamTypeSlow(constraint, constraint, param, &tc);
|
||||
}
|
||||
EMIT_RCALL(a, i, VerifyParamTypeSlow, R(inCls), R(cls),
|
||||
IMM(param));
|
||||
IMM(param),
|
||||
IMM(uintptr_t(&tc)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,8 +567,8 @@ predictOutputs(const Tracelet& t,
|
||||
const NamedEntityPair& cne =
|
||||
curFrame()->m_func->unit()->lookupNamedEntityPairId(ni->imm[1].u_SA);
|
||||
StringData* cnsName = curUnit()->lookupLitstrId(ni->imm[0].u_SA);
|
||||
Class* cls = *cne.second->clsList();
|
||||
if (cls && (cls = cls->getCached())) {
|
||||
Class* cls = cne.second->getCachedClass();
|
||||
if (cls) {
|
||||
DataType dt = cls->clsCnsType(cnsName);
|
||||
if (dt != KindOfUninit) {
|
||||
ni->outputPredicted = true;
|
||||
@@ -1168,6 +1168,7 @@ static const struct {
|
||||
{ OpReqDoc, {Stack1, Stack1, OutUnknown, 0 }},
|
||||
{ OpEval, {Stack1, Stack1, OutUnknown, 0 }},
|
||||
{ OpDefFunc, {None, None, OutNone, 0 }},
|
||||
{ OpDefTypedef, {None, None, OutNone, 0 }},
|
||||
{ OpDefCls, {None, None, OutNone, 0 }},
|
||||
{ OpDefCns, {Stack1, Stack1, OutBoolean, 0 }},
|
||||
|
||||
|
||||
@@ -17,10 +17,13 @@
|
||||
#ifndef incl_TREADMILL_H_
|
||||
#define incl_TREADMILL_H_
|
||||
|
||||
#include "runtime/vm/unit.h"
|
||||
|
||||
namespace HPHP {
|
||||
namespace VM {
|
||||
|
||||
class Class;
|
||||
class Typedef;
|
||||
|
||||
namespace Treadmill {
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ TypeConstraint::TypeConstraint(const StringData* typeName /* = NULL */,
|
||||
this, typeName->data());
|
||||
m_type = { KindOfObject, Precise };
|
||||
m_namedEntity = Unit::GetNamedEntity(typeName);
|
||||
TRACE(5, "TypeConstraint: NamedEntity: %p\n", m_namedEntity);
|
||||
return;
|
||||
}
|
||||
if (RuntimeOption::EnableHipHopSyntax || dtype.m_dt == KindOfArray ||
|
||||
@@ -90,6 +91,47 @@ TypeConstraint::TypeConstraint(const StringData* typeName /* = NULL */,
|
||||
assert(IMPLIES(isSelf(), m_type.m_dt == KindOfObject));
|
||||
}
|
||||
|
||||
/*
|
||||
* Note:
|
||||
*
|
||||
* We don't need to autoload classes because you can't have an
|
||||
* instance of a class if it's not defined. However, we need to
|
||||
* autoload typedefs because they can affect whether the
|
||||
* VerifyParamType would succeed.
|
||||
*/
|
||||
static NameDef getNameDefWithAutoload(const NamedEntity* ne,
|
||||
const StringData* name) {
|
||||
NameDef def = ne->getCachedNameDef();
|
||||
if (!def) {
|
||||
String nameStr(const_cast<StringData*>(name));
|
||||
if (!AutoloadHandler::s_instance->autoloadType(nameStr)) {
|
||||
return NameDef();
|
||||
}
|
||||
def = ne->getCachedNameDef();
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
bool TypeConstraint::checkTypedefNonObj(const TypedValue* tv) const {
|
||||
assert(tv->m_type != KindOfObject); // this checks when tv is not an object
|
||||
assert(!isSelf() && !isParent());
|
||||
|
||||
NameDef def = getNameDefWithAutoload(m_namedEntity, m_typeName);
|
||||
if (!def) return false;
|
||||
Typedef* td = def.asTypedef();
|
||||
return td && equivDataTypes(td->m_kind, tv->m_type);
|
||||
}
|
||||
|
||||
bool TypeConstraint::checkTypedefObj(const TypedValue* tv) const {
|
||||
assert(tv->m_type == KindOfObject); // this checks when tv is an object
|
||||
assert(!isSelf() && !isParent() && !isCallable());
|
||||
|
||||
NameDef def = getNameDefWithAutoload(m_namedEntity, m_typeName);
|
||||
if (!def) return false;
|
||||
Class* c = def.asClass();
|
||||
return c && tv->m_data.pobj->instanceof(c);
|
||||
}
|
||||
|
||||
bool
|
||||
TypeConstraint::check(const TypedValue* tv, const Func* func) const {
|
||||
assert(exists());
|
||||
@@ -101,7 +143,7 @@ TypeConstraint::check(const TypedValue* tv, const Func* func) const {
|
||||
if (m_nullable && IS_NULL_TYPE(tv->m_type)) return true;
|
||||
|
||||
if (tv->m_type == KindOfObject) {
|
||||
if (!isObject()) return false;
|
||||
if (!isObjectOrTypedef()) return false;
|
||||
// Perfect match seems common enough to be worth skipping the hash
|
||||
// table lookup.
|
||||
if (m_typeName->isame(tv->m_data.pobj->getVMClass()->name())) {
|
||||
@@ -109,12 +151,14 @@ TypeConstraint::check(const TypedValue* tv, const Func* func) const {
|
||||
return true;
|
||||
}
|
||||
const Class *c = nullptr;
|
||||
if (isSelf() || isParent() || isCallable()) {
|
||||
const bool selfOrParentOrCallable = isSelf() || isParent() || isCallable();
|
||||
if (selfOrParentOrCallable) {
|
||||
if (isSelf()) {
|
||||
selfToClass(func, &c);
|
||||
} else if (isParent()) {
|
||||
parentToClass(func, &c);
|
||||
} else if (isCallable()) {
|
||||
} else {
|
||||
assert(isCallable());
|
||||
return f_is_callable(tvAsCVarRef(tv));
|
||||
}
|
||||
} else {
|
||||
@@ -126,13 +170,19 @@ TypeConstraint::check(const TypedValue* tv, const Func* func) const {
|
||||
if (shouldProfile() && c) {
|
||||
Class::profileInstanceOf(c->preClass()->name());
|
||||
}
|
||||
return c && tv->m_data.pobj->instanceof(c);
|
||||
if (c && tv->m_data.pobj->instanceof(c)) {
|
||||
return true;
|
||||
}
|
||||
return !selfOrParentOrCallable && checkTypedefObj(tv);
|
||||
}
|
||||
return equivDataTypes(m_type.m_dt, tv->m_type);
|
||||
|
||||
return isObjectOrTypedef() && !isCallable()
|
||||
? checkTypedefNonObj(tv)
|
||||
: equivDataTypes(m_type.m_dt, tv->m_type);
|
||||
}
|
||||
|
||||
bool
|
||||
TypeConstraint::check(DataType dt) const {
|
||||
TypeConstraint::checkPrimitive(DataType dt) const {
|
||||
assert(m_type.m_dt != KindOfObject);
|
||||
assert(dt != KindOfRef);
|
||||
if (m_nullable && IS_NULL_TYPE(dt)) return true;
|
||||
|
||||
@@ -52,6 +52,10 @@ protected:
|
||||
}
|
||||
};
|
||||
|
||||
// m_type represents the DataType to check on. We don't know
|
||||
// whether a bare name is a class/interface name or a typedef, so
|
||||
// when this is set to KindOfObject we may have to look up a typedef
|
||||
// name and test for a different DataType.
|
||||
Type m_type;
|
||||
bool m_nullable;
|
||||
const StringData* m_typeName;
|
||||
@@ -68,6 +72,7 @@ public:
|
||||
bool exists() const { return m_typeName; }
|
||||
|
||||
const StringData* typeName() const { return m_typeName; }
|
||||
const NamedEntity* namedEntity() const { return m_namedEntity; }
|
||||
|
||||
bool nullable() const { return m_nullable; }
|
||||
|
||||
@@ -83,7 +88,7 @@ public:
|
||||
return m_type.isCallable();
|
||||
}
|
||||
|
||||
bool isObject() const {
|
||||
bool isObjectOrTypedef() const {
|
||||
assert(IMPLIES(isParent(), m_type.m_dt == KindOfObject));
|
||||
assert(IMPLIES(isSelf(), m_type.m_dt == KindOfObject));
|
||||
assert(IMPLIES(isCallable(), m_type.m_dt == KindOfObject));
|
||||
@@ -111,8 +116,15 @@ public:
|
||||
(IS_NULL_TYPE(t1) && IS_NULL_TYPE(t2));
|
||||
}
|
||||
|
||||
// General check for any constraint.
|
||||
bool check(const TypedValue* tv, const Func* func) const;
|
||||
bool check(DataType dt) const;
|
||||
|
||||
// Check a constraint when !isObjectOrTypedef().
|
||||
bool checkPrimitive(DataType dt) const;
|
||||
|
||||
// Typedef checks when we know tv is or is not an object.
|
||||
bool checkTypedefObj(const TypedValue* tv) const;
|
||||
bool checkTypedefNonObj(const TypedValue* tv) const;
|
||||
|
||||
// NB: will throw if the check fails.
|
||||
void verify(const TypedValue* tv,
|
||||
|
||||
+209
-61
@@ -58,7 +58,7 @@ Mutex Unit::s_classesMutex;
|
||||
*/
|
||||
static NamedEntityMap *s_namedDataMap;
|
||||
|
||||
const NamedEntity* Unit::GetNamedEntity(const StringData *str) {
|
||||
NamedEntity* Unit::GetNamedEntity(const StringData *str) {
|
||||
if (!s_namedDataMap) s_namedDataMap = new NamedEntityMap();
|
||||
NamedEntityMap::const_iterator it = s_namedDataMap->find(str);
|
||||
if (it != s_namedDataMap->end()) return &it->second;
|
||||
@@ -71,6 +71,7 @@ const NamedEntity* Unit::GetNamedEntity(const StringData *str) {
|
||||
}
|
||||
|
||||
void NamedEntity::setCachedFunc(Func* f) {
|
||||
assert(m_cachedFuncOffset);
|
||||
*(Func**)Transl::TargetCache::handleToPtr(m_cachedFuncOffset) = f;
|
||||
}
|
||||
|
||||
@@ -81,6 +82,45 @@ Func* NamedEntity::getCachedFunc() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void NamedEntity::setCachedClass(Class* f) {
|
||||
assert(m_cachedClassOffset);
|
||||
*(Class**)Transl::TargetCache::handleToPtr(m_cachedClassOffset) = f;
|
||||
}
|
||||
|
||||
Class* NamedEntity::getCachedClass() const {
|
||||
if (LIKELY(m_cachedClassOffset != 0)) {
|
||||
return *(Class**)Transl::TargetCache::handleToPtr(m_cachedClassOffset);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void NamedEntity::setCachedNameDef(NameDef nd) {
|
||||
assert(m_cachedNameDefOffset);
|
||||
Transl::TargetCache::handleToRef<NameDef>(m_cachedNameDefOffset) = nd;
|
||||
}
|
||||
|
||||
NameDef NamedEntity::getCachedNameDef() const {
|
||||
if (LIKELY(m_cachedNameDefOffset != 0)) {
|
||||
return Transl::TargetCache::handleToRef<NameDef>(m_cachedNameDefOffset);
|
||||
}
|
||||
return NameDef();
|
||||
}
|
||||
|
||||
void NamedEntity::pushClass(Class* cls) {
|
||||
assert(!cls->m_nextClass);
|
||||
cls->m_nextClass = m_clsList;
|
||||
atomic_release_store(&m_clsList, cls); // TODO(#2054448): ARMv8
|
||||
}
|
||||
|
||||
void NamedEntity::removeClass(Class* goner) {
|
||||
Class** cls = &m_clsList; // TODO(#2054448): ARMv8
|
||||
while (*cls != goner) {
|
||||
assert(*cls);
|
||||
cls = &(*cls)->m_nextClass;
|
||||
}
|
||||
*cls = goner->m_nextClass;
|
||||
}
|
||||
|
||||
UnitMergeInfo* UnitMergeInfo::alloc(size_t size) {
|
||||
UnitMergeInfo* mi = (UnitMergeInfo*)malloc(
|
||||
sizeof(UnitMergeInfo) + size * sizeof(void*));
|
||||
@@ -112,7 +152,7 @@ Array Unit::getUserFunctions() {
|
||||
AllClasses::AllClasses()
|
||||
: m_next(s_namedDataMap->begin())
|
||||
, m_end(s_namedDataMap->end())
|
||||
, m_current(m_next != m_end ? *m_next->second.clsList() : nullptr) {
|
||||
, m_current(m_next != m_end ? m_next->second.clsList() : nullptr) {
|
||||
if (!empty()) skip();
|
||||
}
|
||||
|
||||
@@ -121,7 +161,7 @@ void AllClasses::skip() {
|
||||
assert(!empty());
|
||||
++m_next;
|
||||
while (!empty()) {
|
||||
m_current = *m_next->second.clsList();
|
||||
m_current = m_next->second.clsList();
|
||||
if (m_current) break;
|
||||
++m_next;
|
||||
}
|
||||
@@ -156,16 +196,17 @@ class AllCachedClasses {
|
||||
void skip() {
|
||||
Class* cls;
|
||||
while (!empty()) {
|
||||
cls = *m_next->second.clsList();
|
||||
cls = m_next->second.clsList();
|
||||
if (cls && cls->getCached()) break;
|
||||
++m_next;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
AllCachedClasses()
|
||||
: m_next(s_namedDataMap->begin())
|
||||
, m_end(s_namedDataMap->end()) {
|
||||
AllCachedClasses()
|
||||
: m_next(s_namedDataMap->begin())
|
||||
, m_end(s_namedDataMap->end())
|
||||
{
|
||||
skip();
|
||||
}
|
||||
bool empty() const {
|
||||
@@ -173,7 +214,7 @@ AllCachedClasses()
|
||||
}
|
||||
Class* front() {
|
||||
assert(!empty());
|
||||
Class* c = *m_next->second.clsList();
|
||||
Class* c = m_next->second.clsList();
|
||||
assert(c);
|
||||
c = c->getCached();
|
||||
assert(c);
|
||||
@@ -308,16 +349,13 @@ Unit::~Unit() {
|
||||
// it is possible for Class'es to outlive their Unit.
|
||||
for (int i = m_preClasses.size(); i--; ) {
|
||||
PreClass* pcls = m_preClasses[i].get();
|
||||
Class * const* clsh = pcls->namedEntity()->clsList();
|
||||
if (clsh) {
|
||||
Class *cls = *clsh;
|
||||
while (cls) {
|
||||
Class* cur = cls;
|
||||
cls = cls->m_nextClass;
|
||||
if (cur->preClass() == pcls) {
|
||||
if (!cur->decAtomicCount()) {
|
||||
cur->atomicRelease();
|
||||
}
|
||||
Class* cls = pcls->namedEntity()->clsList();
|
||||
while (cls) {
|
||||
Class* cur = cls;
|
||||
cls = cls->m_nextClass;
|
||||
if (cur->preClass() == pcls) {
|
||||
if (!cur->decAtomicCount()) {
|
||||
cur->atomicRelease();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -431,26 +469,42 @@ class FrameRestore {
|
||||
|
||||
Class* Unit::defClass(const PreClass* preClass,
|
||||
bool failIsFatal /* = true */) {
|
||||
// TODO(#2054448): ARMv8
|
||||
Class* const* clsList = preClass->namedEntity()->clsList();
|
||||
Class* top = *clsList;
|
||||
if (top) {
|
||||
Class *cls = top->getCached();
|
||||
if (cls) {
|
||||
// Raise a fatal unless the existing class definition is identical to the
|
||||
// one this invocation would create.
|
||||
if (cls->preClass() != preClass) {
|
||||
if (failIsFatal) {
|
||||
FrameRestore fr(preClass);
|
||||
raise_error("Class already declared: %s", preClass->name()->data());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
return cls;
|
||||
}
|
||||
}
|
||||
// Get a compatible Class, and add it to the list of defined classes.
|
||||
NamedEntity* const nameList = preClass->namedEntity();
|
||||
Class* top = nameList->clsList();
|
||||
|
||||
/*
|
||||
* Check if there is already a name defined in this request for this
|
||||
* NamedEntity.
|
||||
*
|
||||
* Raise a fatal unless the existing class definition is identical to the
|
||||
* one this invocation would create.
|
||||
*/
|
||||
if (NameDef current = nameList->getCachedNameDef()) {
|
||||
auto name = current.asTypedef()
|
||||
? current.asTypedef()->m_name
|
||||
: current.asClass()->name();
|
||||
|
||||
FrameRestore fr(preClass);
|
||||
raise_error("Cannot declare class with the same name (%s) as an "
|
||||
"existing type", name->data());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// If it's compatible, the class must have been declared as a
|
||||
// DefClass, not a typedef. So we don't need to check the NameDef
|
||||
// for a class, only the cached class offset.
|
||||
if (Class* cls = nameList->getCachedClass()) {
|
||||
if (cls->preClass() != preClass) {
|
||||
if (failIsFatal) {
|
||||
FrameRestore fr(preClass);
|
||||
raise_error("Class already declared: %s", preClass->name()->data());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
return cls;
|
||||
}
|
||||
|
||||
// Get a compatible Class, and add it to the list of defined classes.
|
||||
Class* parent = nullptr;
|
||||
for (;;) {
|
||||
// Search for a compatible extant class. Searching from most to least
|
||||
@@ -493,27 +547,26 @@ Class* Unit::defClass(const PreClass* preClass,
|
||||
newClass = Class::newClass(const_cast<PreClass*>(preClass), parent);
|
||||
}
|
||||
Lock l(Unit::s_classesMutex);
|
||||
|
||||
/*
|
||||
We could re-enter via Unit::getClass() or class_->avail(), so
|
||||
no need for *clsList to be volatile
|
||||
We could re-enter via Unit::getClass() or class_->avail().
|
||||
*/
|
||||
if (UNLIKELY(top != *clsList)) {
|
||||
top = *clsList;
|
||||
if (UNLIKELY(top != nameList->clsList())) {
|
||||
top = nameList->clsList();
|
||||
continue;
|
||||
}
|
||||
if (top) {
|
||||
newClass->m_cachedOffset = top->m_cachedOffset;
|
||||
} else {
|
||||
newClass->m_cachedOffset =
|
||||
Transl::TargetCache::allocKnownClass(newClass.get());
|
||||
|
||||
if (!nameList->m_cachedClassOffset) {
|
||||
nameList->m_cachedClassOffset = Transl::TargetCache::
|
||||
allocKnownClass(newClass.get());
|
||||
}
|
||||
newClass->m_nextClass = top;
|
||||
newClass->m_cachedOffset = nameList->m_cachedClassOffset;
|
||||
|
||||
if (Class::s_instanceBitsInit.load(std::memory_order_acquire)) {
|
||||
// If the instance bitmap has already been set up, we can just initialize
|
||||
// our new class's bits and add ourselves to the class list normally.
|
||||
newClass->setInstanceBits();
|
||||
atomic_release_store(const_cast<Class**>(clsList), newClass.get());
|
||||
nameList->pushClass(newClass.get());
|
||||
} else {
|
||||
// Otherwise, we have to grab the read lock. If the map has been
|
||||
// initialized since we checked, initialize the bits normally. If not, we
|
||||
@@ -523,7 +576,7 @@ Class* Unit::defClass(const PreClass* preClass,
|
||||
if (Class::s_instanceBitsInit.load(std::memory_order_acquire)) {
|
||||
newClass->setInstanceBits();
|
||||
}
|
||||
atomic_release_store(const_cast<Class**>(clsList), newClass.get());
|
||||
nameList->pushClass(newClass.get());
|
||||
}
|
||||
newClass.get()->incAtomicCount();
|
||||
newClass.get()->setCached();
|
||||
@@ -532,6 +585,84 @@ Class* Unit::defClass(const PreClass* preClass,
|
||||
}
|
||||
}
|
||||
|
||||
void Unit::defTypedef(Id id) {
|
||||
assert(id < m_typedefs.size());
|
||||
auto thisType = &m_typedefs[id];
|
||||
auto nameList = GetNamedEntity(thisType->m_name);
|
||||
|
||||
auto checkExistingClass = [&] (Class* cls) {
|
||||
if (thisType->m_kind != KindOfObject ||
|
||||
!cls->name()->isame(thisType->m_value)) {
|
||||
raise_error("The type %s is already defined to a different class (%s)",
|
||||
thisType->m_name->data(),
|
||||
cls->name()->data());
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Check if this name already has a NameDef, and if so make sure it
|
||||
* is compatible.
|
||||
*/
|
||||
if (NameDef current = nameList->getCachedNameDef()) {
|
||||
if (Class* cls = current.asClass()) {
|
||||
checkExistingClass(cls);
|
||||
return;
|
||||
}
|
||||
Typedef* td = current.asTypedef();
|
||||
assert(td);
|
||||
if (thisType->m_kind != td->m_kind ||
|
||||
!td->m_value->isame(thisType->m_value)) {
|
||||
raise_error("The type %s is already defined to an incompatible type",
|
||||
thisType->m_name->data());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// There might also be a class with this name already.
|
||||
if (Class* cls = nameList->getCachedClass()) {
|
||||
checkExistingClass(cls);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!nameList->m_cachedNameDefOffset) {
|
||||
nameList->m_cachedNameDefOffset =
|
||||
Transl::TargetCache::allocNameDef(nameList);
|
||||
}
|
||||
|
||||
/*
|
||||
* The cached NameDef for this typedef will be the actual Class* if
|
||||
* it is a typedef for a class type, otherwise it is a pointer to a
|
||||
* Typedef structure.
|
||||
*
|
||||
* If this typedef is a KindOfObject and the name on the right hand
|
||||
* side was another typedef, we will bind the name to the other side
|
||||
* for this request. We need to inspect the right hand side and
|
||||
* figure out what it was first.
|
||||
*/
|
||||
|
||||
if (thisType->m_kind != KindOfObject) {
|
||||
nameList->setCachedNameDef(NameDef(thisType));
|
||||
return;
|
||||
}
|
||||
if (auto klass = Unit::loadClass(thisType->m_value)) {
|
||||
nameList->setCachedNameDef(NameDef(klass));
|
||||
return;
|
||||
}
|
||||
|
||||
auto targetNameList = GetNamedEntity(thisType->m_value);
|
||||
NameDef target = targetNameList->getCachedNameDef();
|
||||
if (!target) {
|
||||
AutoloadHandler::s_instance->autoloadType(thisType->m_value->data());
|
||||
target = targetNameList->getCachedNameDef();
|
||||
if (!target) {
|
||||
raise_error("Unknown type or class %s", thisType->m_value->data());
|
||||
return;
|
||||
}
|
||||
}
|
||||
assert(target);
|
||||
nameList->setCachedNameDef(target);
|
||||
}
|
||||
|
||||
void Unit::renameFunc(const StringData* oldName, const StringData* newName) {
|
||||
// renameFunc() should only be used by VMExecutionContext::createFunction.
|
||||
// We do a linear scan over all the functions in the unit searching for the
|
||||
@@ -554,10 +685,9 @@ void Unit::renameFunc(const StringData* oldName, const StringData* newName) {
|
||||
|
||||
Class* Unit::loadClass(const NamedEntity* ne,
|
||||
const StringData* name) {
|
||||
Class *cls = *ne->clsList();
|
||||
if (LIKELY(cls != nullptr)) {
|
||||
cls = cls->getCached();
|
||||
if (LIKELY(cls != nullptr)) return cls;
|
||||
Class* cls;
|
||||
if (LIKELY((cls = ne->getCachedClass()) != nullptr)) {
|
||||
return cls;
|
||||
}
|
||||
VMRegAnchor _;
|
||||
AutoloadHandler::s_instance->invokeHandler(
|
||||
@@ -604,6 +734,7 @@ void Unit::initialMerge() {
|
||||
if (LIKELY(m_mergeState == UnitMergeStateUnmerged)) {
|
||||
int state = 0;
|
||||
m_mergeState = UnitMergeStateMerging;
|
||||
|
||||
bool allFuncsUnique = RuntimeOption::RepoAuthoritative;
|
||||
for (MutableFuncRange fr(nonMainFuncs()); !fr.empty();) {
|
||||
Func* f = fr.popFront();
|
||||
@@ -772,7 +903,7 @@ size_t compactUnitMergeInfo(UnitMergeInfo* in, UnitMergeInfo* out) {
|
||||
void* obj = in->mergeableObj(ix);
|
||||
assert((uintptr_t(obj) & 1) == 0);
|
||||
PreClass* pre = (PreClass*)obj;
|
||||
Class* cls = *pre->namedEntity()->clsList();
|
||||
Class* cls = pre->namedEntity()->clsList();
|
||||
assert(cls && !cls->m_nextClass);
|
||||
assert(cls->preClass() == pre);
|
||||
if (TargetCache::isPersistentHandle(cls->m_cachedOffset)) {
|
||||
@@ -793,7 +924,7 @@ size_t compactUnitMergeInfo(UnitMergeInfo* in, UnitMergeInfo* out) {
|
||||
switch (k) {
|
||||
case UnitMergeKindClass: {
|
||||
PreClass* pre = (PreClass*)obj;
|
||||
Class* cls = *pre->namedEntity()->clsList();
|
||||
Class* cls = pre->namedEntity()->clsList();
|
||||
assert(cls && !cls->m_nextClass);
|
||||
assert(cls->preClass() == pre);
|
||||
if (TargetCache::isPersistentHandle(cls->m_cachedOffset)) {
|
||||
@@ -1310,7 +1441,7 @@ void Unit::enableIntercepts() {
|
||||
Lock lock(s_classesMutex);
|
||||
for (int i = m_preClasses.size(); i--; ) {
|
||||
PreClass* pcls = m_preClasses[i].get();
|
||||
Class *cls = *pcls->namedEntity()->clsList();
|
||||
Class* cls = pcls->namedEntity()->clsList();
|
||||
while (cls) {
|
||||
/*
|
||||
* verify that this class corresponds to the
|
||||
@@ -1405,7 +1536,7 @@ void UnitRepoProxy::createSchema(int repoId, RepoTxn& txn) {
|
||||
ssCreate << "CREATE TABLE " << m_repo.table(repoId, "Unit")
|
||||
<< "(unitSn INTEGER PRIMARY KEY, md5 BLOB, bc BLOB,"
|
||||
" bc_meta BLOB, mainReturn BLOB, mergeable INTEGER,"
|
||||
"lines BLOB, UNIQUE (md5));";
|
||||
"lines BLOB, typedefs BLOB, UNIQUE (md5));";
|
||||
txn.exec(ssCreate.str());
|
||||
}
|
||||
{
|
||||
@@ -1488,14 +1619,16 @@ void UnitRepoProxy::InsertUnitStmt
|
||||
const uchar* bc, size_t bclen,
|
||||
const uchar* bc_meta, size_t bc_meta_len,
|
||||
const TypedValue* mainReturn, bool mergeOnly,
|
||||
const LineTable& lines) {
|
||||
const LineTable& lines,
|
||||
const std::vector<Typedef>& typedefs) {
|
||||
BlobEncoder linesBlob;
|
||||
BlobEncoder typedefsBlob;
|
||||
|
||||
if (!prepared()) {
|
||||
std::stringstream ssInsert;
|
||||
ssInsert << "INSERT INTO " << m_repo.table(m_repoId, "Unit")
|
||||
<< " VALUES(NULL, @md5, @bc, @bc_meta,"
|
||||
" @mainReturn, @mergeable, @lines);";
|
||||
" @mainReturn, @mergeable, @lines, @typedefs);";
|
||||
txn.prepare(*this, ssInsert.str());
|
||||
}
|
||||
RepoTxnQuery query(txn, *this);
|
||||
@@ -1507,6 +1640,7 @@ void UnitRepoProxy::InsertUnitStmt
|
||||
query.bindTypedValue("@mainReturn", *mainReturn);
|
||||
query.bindBool("@mergeable", mergeOnly);
|
||||
query.bindBlob("@lines", linesBlob(lines), /* static */ true);
|
||||
query.bindBlob("@typedefs", typedefsBlob(typedefs), /* static */ true);
|
||||
query.exec();
|
||||
unitSn = query.getInsertedRowid();
|
||||
}
|
||||
@@ -1517,7 +1651,8 @@ bool UnitRepoProxy::GetUnitStmt
|
||||
RepoTxn txn(m_repo);
|
||||
if (!prepared()) {
|
||||
std::stringstream ssSelect;
|
||||
ssSelect << "SELECT unitSn,bc,bc_meta,mainReturn,mergeable,lines FROM "
|
||||
ssSelect << "SELECT unitSn,bc,bc_meta,mainReturn,mergeable,"
|
||||
"lines,typedefs FROM "
|
||||
<< m_repo.table(m_repoId, "Unit")
|
||||
<< " WHERE md5 == @md5;";
|
||||
txn.prepare(*this, ssSelect.str());
|
||||
@@ -1535,6 +1670,7 @@ bool UnitRepoProxy::GetUnitStmt
|
||||
TypedValue value; /**/ query.getTypedValue(3, value);
|
||||
bool mergeable; /**/ query.getBool(4, mergeable);
|
||||
BlobDecoder linesBlob = /**/ query.getBlob(5);
|
||||
BlobDecoder typedefsBlob = /**/ query.getBlob(6);
|
||||
ue.setRepoId(m_repoId);
|
||||
ue.setSn(unitSn);
|
||||
ue.setBc((const uchar*)bc, bclen);
|
||||
@@ -1546,6 +1682,8 @@ bool UnitRepoProxy::GetUnitStmt
|
||||
linesBlob(lines);
|
||||
ue.setLines(lines);
|
||||
|
||||
typedefsBlob(ue.m_typedefs);
|
||||
|
||||
txn.commit();
|
||||
} catch (RepoExc& re) {
|
||||
return true;
|
||||
@@ -2117,6 +2255,12 @@ PreClassEmitter* UnitEmitter::newPreClassEmitter(const StringData* n,
|
||||
return pce;
|
||||
}
|
||||
|
||||
Id UnitEmitter::addTypedef(const Typedef& td) {
|
||||
Id id = m_typedefs.size();
|
||||
m_typedefs.push_back(td);
|
||||
return id;
|
||||
}
|
||||
|
||||
void UnitEmitter::recordSourceLocation(const Location* sLoc, Offset start) {
|
||||
SourceLoc newLoc(*sLoc);
|
||||
if (!m_sourceLocTab.empty()) {
|
||||
@@ -2188,7 +2332,8 @@ bool UnitEmitter::insert(UnitOrigin unitOrigin, RepoTxn& txn) {
|
||||
LineTable lines = createLineTable(m_sourceLocTab, m_bclen);
|
||||
urp.insertUnit(repoId).insert(txn, m_sn, m_md5, m_bc, m_bclen,
|
||||
m_bc_meta, m_bc_meta_len,
|
||||
&m_mainReturn, m_mergeOnly, lines);
|
||||
&m_mainReturn, m_mergeOnly, lines,
|
||||
m_typedefs);
|
||||
}
|
||||
int64_t usn = m_sn;
|
||||
for (unsigned i = 0; i < m_litstrs.size(); ++i) {
|
||||
@@ -2207,6 +2352,7 @@ bool UnitEmitter::insert(UnitOrigin unitOrigin, RepoTxn& txn) {
|
||||
++it) {
|
||||
(*it)->commit(txn);
|
||||
}
|
||||
|
||||
for (int i = 0, n = m_mergeableStmts.size(); i < n; i++) {
|
||||
switch (m_mergeableStmts[i].first) {
|
||||
case UnitMergeKindDone:
|
||||
@@ -2302,6 +2448,8 @@ Unit* UnitEmitter::create() {
|
||||
++it) {
|
||||
u->m_preClasses.push_back(PreClassPtr((*it)->create(*u)));
|
||||
}
|
||||
u->m_typedefs = m_typedefs;
|
||||
|
||||
size_t ix = m_fes.size() + m_hoistablePceIdVec.size();
|
||||
if (m_mergeOnly && !m_allClassesHoistable) {
|
||||
size_t extra = 0;
|
||||
|
||||
+46
-34
@@ -17,13 +17,12 @@
|
||||
#ifndef incl_VM_UNIT_H_
|
||||
#define incl_VM_UNIT_H_
|
||||
|
||||
#include <tbb/concurrent_unordered_map.h>
|
||||
|
||||
// Expects that runtime/vm/core_types.h is already included.
|
||||
#include "runtime/base/runtime_option.h"
|
||||
#include "runtime/vm/hhbc.h"
|
||||
#include "runtime/vm/class.h"
|
||||
#include "runtime/vm/repo_helpers.h"
|
||||
#include "runtime/vm/named_entity.h"
|
||||
#include "runtime/base/array/hphp_array.h"
|
||||
#include "util/range.h"
|
||||
#include "util/parser/location.h"
|
||||
@@ -108,26 +107,10 @@ struct UnitMergeInfo {
|
||||
}
|
||||
void*& mergeableObj(int ix) { return ((void**)m_mergeables)[ix]; }
|
||||
void* mergeableData(int ix) { return (char*)m_mergeables + ix*sizeof(void*); }
|
||||
|
||||
};
|
||||
|
||||
typedef const uchar* PC;
|
||||
|
||||
struct NamedEntity {
|
||||
Class* m_class;
|
||||
unsigned m_cachedClassOffset;
|
||||
unsigned m_cachedFuncOffset;
|
||||
|
||||
Class* const* clsList() const { return &m_class; }
|
||||
void setCachedFunc(Func *f);
|
||||
Func* getCachedFunc() const;
|
||||
};
|
||||
|
||||
typedef tbb::concurrent_unordered_map<const StringData *, NamedEntity,
|
||||
string_data_hash,
|
||||
string_data_isame> NamedEntityMap;
|
||||
typedef std::pair<const StringData*,const NamedEntity*> NamedEntityPair;
|
||||
|
||||
// Exception handler table entry.
|
||||
class EHEnt {
|
||||
public:
|
||||
@@ -276,6 +259,28 @@ struct PreConst {
|
||||
|
||||
typedef std::vector<PreConst> PreConstVec;
|
||||
|
||||
/*
|
||||
* This is the runtime representation of a typedef. Typedefs are only
|
||||
* allowed when hip hop extensions are enabled.
|
||||
*
|
||||
* The m_kind field is KindOfObject whenever the typedef is basically
|
||||
* just a name. At runtime we still might resolve this name to
|
||||
* another typedef, becoming a typedef for KindOfArray or something in
|
||||
* that request.
|
||||
*/
|
||||
struct Typedef {
|
||||
const StringData* m_name;
|
||||
const StringData* m_value;
|
||||
DataType m_kind;
|
||||
|
||||
template<class SerDe> void serde(SerDe& sd) {
|
||||
sd(m_name)
|
||||
(m_value)
|
||||
(m_kind)
|
||||
;
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// (const StringData*) versus (StringData*)
|
||||
//
|
||||
@@ -442,7 +447,7 @@ struct Unit {
|
||||
|
||||
MD5 md5() const { return m_md5; }
|
||||
|
||||
static const NamedEntity* GetNamedEntity(const StringData *);
|
||||
static NamedEntity* GetNamedEntity(const StringData *);
|
||||
static Array getUserFunctions();
|
||||
static Array getClassesInfo();
|
||||
static Array getInterfacesInfo();
|
||||
@@ -484,34 +489,39 @@ struct Unit {
|
||||
|
||||
static Class* defClass(const HPHP::VM::PreClass* preClass,
|
||||
bool failIsFatal = true);
|
||||
void defTypedef(Id id);
|
||||
|
||||
static Class *lookupClass(const NamedEntity *ne) {
|
||||
Class *cls = *ne->clsList(); // TODO(#2054448): ARMv8
|
||||
if (LIKELY(cls != nullptr)) cls = cls->getCached();
|
||||
return cls;
|
||||
Class* cls;
|
||||
if (LIKELY((cls = ne->getCachedClass()) != nullptr)) {
|
||||
return cls;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static Class *lookupClass(const StringData *clsName) {
|
||||
return lookupClass(GetNamedEntity(clsName));
|
||||
}
|
||||
|
||||
/*
|
||||
* Look up a unique class even if it hasn't been loaded in this
|
||||
* request yet.
|
||||
*/
|
||||
static Class *lookupUniqueClass(const NamedEntity *ne) {
|
||||
Class *cls = *ne->clsList(); // TODO(#2054448): ARMv8
|
||||
Class* cls = ne->clsList();
|
||||
if (LIKELY(cls != nullptr)) {
|
||||
if (cls->attrs() & AttrUnique && RuntimeOption::RepoAuthoritative) {
|
||||
return cls;
|
||||
}
|
||||
cls = cls->getCached();
|
||||
return cls->getCached();
|
||||
}
|
||||
return cls;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static Class *lookupUniqueClass(const StringData *clsName) {
|
||||
return lookupUniqueClass(GetNamedEntity(clsName));
|
||||
}
|
||||
|
||||
static Class *lookupClass(const StringData *clsName) {
|
||||
Class *cls = *GetNamedEntity(clsName)->clsList();
|
||||
if (LIKELY(cls != nullptr)) cls = cls->getCached();
|
||||
return cls;
|
||||
}
|
||||
|
||||
static Class *loadClass(const NamedEntity *ne,
|
||||
const StringData *name);
|
||||
|
||||
@@ -560,7 +570,6 @@ public:
|
||||
return m_mergeInfo->hoistableFuncs();
|
||||
}
|
||||
void renameFunc(const StringData* oldName, const StringData* newName);
|
||||
void mergeFuncs() const;
|
||||
static void loadFunc(const Func *func);
|
||||
FuncRange funcs() const {
|
||||
return m_mergeInfo->funcs();
|
||||
@@ -580,7 +589,6 @@ public:
|
||||
return m_preClasses[id].get();
|
||||
}
|
||||
typedef std::vector<PreClassPtr> PreClassPtrVec;
|
||||
typedef std::vector<PreClass*> PreClassVec;
|
||||
typedef Range<PreClassPtrVec> PreClassRange;
|
||||
void initialMerge();
|
||||
void merge();
|
||||
@@ -661,6 +669,7 @@ private:
|
||||
std::vector<NamedEntityPair> m_namedInfo;
|
||||
std::vector<const ArrayData*> m_arrays;
|
||||
PreClassPtrVec m_preClasses;
|
||||
FixedVector<Typedef> m_typedefs;
|
||||
UnitMergeInfo* m_mergeInfo;
|
||||
unsigned m_cacheOffset;
|
||||
int8_t m_repoId;
|
||||
@@ -693,6 +702,7 @@ class UnitEmitter {
|
||||
void setMergeOnly(bool b) { m_mergeOnly = b; }
|
||||
const MD5& md5() const { return m_md5; }
|
||||
Id addPreConst(const StringData* name, const TypedValue& value);
|
||||
Id addTypedef(const Typedef& td);
|
||||
Id mergeLitstr(const StringData* litstr);
|
||||
Id mergeArray(ArrayData* a, const StringData* key=nullptr);
|
||||
FuncEmitter* getMain();
|
||||
@@ -840,6 +850,7 @@ class UnitEmitter {
|
||||
std::vector<std::pair<Offset,SourceLoc> > m_sourceLocTab;
|
||||
std::vector<std::pair<Offset,const FuncEmitter*> > m_feTab;
|
||||
PreConstVec m_preConsts;
|
||||
std::vector<Typedef> m_typedefs;
|
||||
};
|
||||
|
||||
class UnitRepoProxy : public RepoProxy {
|
||||
@@ -876,7 +887,8 @@ class UnitRepoProxy : public RepoProxy {
|
||||
void insert(RepoTxn& txn, int64_t& unitSn, const MD5& md5, const uchar* bc,
|
||||
size_t bclen, const uchar* bc_meta, size_t bc_meta_len,
|
||||
const TypedValue* mainReturn, bool mergeOnly,
|
||||
const LineTable& lines);
|
||||
const LineTable& lines,
|
||||
const std::vector<Typedef>&);
|
||||
};
|
||||
class GetUnitStmt : public RepoProxy::Stmt {
|
||||
public:
|
||||
|
||||
Arquivo executável
+2
@@ -0,0 +1,2 @@
|
||||
#!/bin/sh
|
||||
exec sed -e 's@in .*@@g'
|
||||
@@ -0,0 +1,8 @@
|
||||
<?hh
|
||||
|
||||
function foo(string $y) : shape('a' => string) {
|
||||
// shapes must have compile-time constant key names
|
||||
$x = shape($y => 'asd');
|
||||
}
|
||||
|
||||
foo();
|
||||
@@ -0,0 +1 @@
|
||||
HipHop Fatal error: syntax error, unexpected T_VARIABLE, expecting ')'
|
||||
Link simbólico
+1
@@ -0,0 +1 @@
|
||||
filter.remove_in
|
||||
@@ -0,0 +1,9 @@
|
||||
<?hh
|
||||
|
||||
// Keys must not be numeric or start with integers.
|
||||
type BadPoint = shape(
|
||||
'123' => int,
|
||||
'124' => int,
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
HipHop Fatal error: Shape key names may not start with integers
|
||||
Link simbólico
+1
@@ -0,0 +1 @@
|
||||
filter.remove_in
|
||||
@@ -0,0 +1,100 @@
|
||||
<?hh
|
||||
|
||||
type Point = shape(
|
||||
'x' => int,
|
||||
'y' => int,
|
||||
);
|
||||
|
||||
type Recur = shape(
|
||||
'shape' => Recur
|
||||
);
|
||||
|
||||
type Point3 = shape(
|
||||
'x' => int,
|
||||
'y' => int,
|
||||
'z' => int,
|
||||
);
|
||||
|
||||
function foo(Point $x) : Point {
|
||||
$x['x'] = 42;
|
||||
return $x;
|
||||
}
|
||||
|
||||
function foo2() : shape('x' => int, 'y' => int) {
|
||||
$x = shape(
|
||||
'x' => 12,
|
||||
'y' => 12,
|
||||
);
|
||||
return $x;
|
||||
}
|
||||
|
||||
function foo3<T as shape('x' => int)>(T $pt) : T {
|
||||
$pt['x'] = 42;
|
||||
return $pt;
|
||||
}
|
||||
|
||||
function foo31<T as shape('x' => int, 'y' => string)>(T $pt) : T {
|
||||
$pt['x'] = 42;
|
||||
return $pt;
|
||||
}
|
||||
|
||||
function foo32<T as shape('x' => int, 'y' => string,)>(T $pt) : T {
|
||||
$pt['x'] = 42;
|
||||
return $pt;
|
||||
}
|
||||
|
||||
function foo4() {
|
||||
$x = shape(
|
||||
'x' => 12,
|
||||
'y' => 12
|
||||
);
|
||||
}
|
||||
|
||||
function foo5<T as Point>(T $pt) : T {
|
||||
$pt['x'] = 42;
|
||||
return $pt;
|
||||
}
|
||||
|
||||
function foo6<T as shape()>(T $pt) : T {
|
||||
return $pt;
|
||||
}
|
||||
|
||||
function foo7(shape('id' => int, 'name' => string) $x) : int {
|
||||
return 12;
|
||||
}
|
||||
|
||||
type Foobar = shape(
|
||||
'foobar' => string,
|
||||
);
|
||||
|
||||
function foo8(string $foo, string $bar) : Foobar {
|
||||
$x = shape(
|
||||
'foobar' => $foo . $bar
|
||||
);
|
||||
return 12;
|
||||
}
|
||||
|
||||
// You can have a shape as a parameter to a user attribute.
|
||||
<<
|
||||
AttrTakingShape(
|
||||
shape(
|
||||
'x' => 1,
|
||||
'y' => 1,
|
||||
)
|
||||
)
|
||||
>>
|
||||
class SomeClass {}
|
||||
|
||||
// These parse, but don't work at runtime currently:
|
||||
const Foobar SCALAR_SHAPE = shape(
|
||||
'foobar' => 'constant'
|
||||
);
|
||||
const Point3 SCALAR_SHAPE2 = shape(
|
||||
'x' => 12,
|
||||
'y' => 13,
|
||||
'z' => 11,
|
||||
);
|
||||
const SCALAR_SHAPE3 = shape(
|
||||
'x' => 12,
|
||||
'foo' => 1.0
|
||||
);
|
||||
@@ -0,0 +1,3 @@
|
||||
HipHop Warning: Constants may only evaluate to scalar values
|
||||
HipHop Warning: Constants may only evaluate to scalar values
|
||||
HipHop Warning: Constants may only evaluate to scalar values
|
||||
Link simbólico
+1
@@ -0,0 +1 @@
|
||||
filter.remove_in
|
||||
@@ -0,0 +1,18 @@
|
||||
<?hh
|
||||
|
||||
function type() { // ok, 'type' is context sensitive
|
||||
echo "Hi\n";
|
||||
}
|
||||
type();
|
||||
|
||||
class Foo {
|
||||
const TYPE = 'hi2';
|
||||
}
|
||||
|
||||
echo Foo::TYPE . "\n";
|
||||
|
||||
type t = int;
|
||||
function wat(t $type) {
|
||||
echo $type . "\n";
|
||||
}
|
||||
wat(12);
|
||||
@@ -0,0 +1,3 @@
|
||||
Hi
|
||||
hi2
|
||||
12
|
||||
@@ -0,0 +1,26 @@
|
||||
<?hh
|
||||
|
||||
class MyClass {
|
||||
public function __construct() {
|
||||
echo "ctor\n";
|
||||
}
|
||||
public function method() {
|
||||
echo "method\n";
|
||||
}
|
||||
|
||||
const JUNK = "ASD";
|
||||
|
||||
public static $SomeProp = null;
|
||||
}
|
||||
type Yeah = MyClass;
|
||||
|
||||
function target(Yeah $x): void {
|
||||
$x->method();
|
||||
}
|
||||
|
||||
function hinting(): void {
|
||||
$x = new MyClass();
|
||||
target($x);
|
||||
}
|
||||
|
||||
hinting();
|
||||
@@ -0,0 +1,2 @@
|
||||
ctor
|
||||
method
|
||||
@@ -0,0 +1,10 @@
|
||||
<?hh
|
||||
|
||||
class MyClass {}
|
||||
|
||||
type Foo = MyClass;
|
||||
|
||||
function blah(): void {
|
||||
$x = new Foo(); // Error, new types don't create new value constructors
|
||||
}
|
||||
blah();
|
||||
@@ -0,0 +1 @@
|
||||
HipHop Fatal error: Class undefined: Foo
|
||||
Link simbólico
+1
@@ -0,0 +1 @@
|
||||
filter.remove_in
|
||||
@@ -0,0 +1,13 @@
|
||||
<?hh
|
||||
|
||||
class MyClass {
|
||||
const ASD = 'wat';
|
||||
}
|
||||
type Foo = MyClass;
|
||||
|
||||
function test(): void {
|
||||
// error: making a new type name doesn't make it available outside
|
||||
// of type expressions (parameter lists, etc).
|
||||
$x = Foo::ASD;
|
||||
}
|
||||
test();
|
||||
@@ -0,0 +1 @@
|
||||
HipHop Fatal error: Class undefined: Foo
|
||||
Link simbólico
+1
@@ -0,0 +1 @@
|
||||
filter.remove_in
|
||||
@@ -0,0 +1,3 @@
|
||||
<?hh
|
||||
|
||||
type Foo = Foo;
|
||||
@@ -0,0 +1 @@
|
||||
HipHop Fatal error: Unknown type or class Foo
|
||||
Link simbólico
+1
@@ -0,0 +1 @@
|
||||
filter.remove_in
|
||||
@@ -0,0 +1,10 @@
|
||||
<?hh
|
||||
|
||||
class MyClass {}
|
||||
class OtherClass {}
|
||||
|
||||
type MyType = MyClass;
|
||||
type MyType = MyClass; //ok
|
||||
|
||||
// raise a fatal:
|
||||
type MyType = OtherClass;
|
||||
@@ -0,0 +1 @@
|
||||
HipHop Fatal error: The type MyType is already defined to a different class (MyClass)
|
||||
Link simbólico
+1
@@ -0,0 +1 @@
|
||||
filter.remove_in
|
||||
@@ -0,0 +1,5 @@
|
||||
<?hh
|
||||
type Coord = shape('x' => int); // ok
|
||||
type Coord = shape('x' => int,
|
||||
'y' => int); // ok: typechecker catches it
|
||||
type Coord = string; // bad
|
||||
@@ -0,0 +1 @@
|
||||
HipHop Fatal error: The type Coord is already defined to an incompatible type
|
||||
Link simbólico
+1
@@ -0,0 +1 @@
|
||||
filter.remove_in
|
||||
@@ -0,0 +1,6 @@
|
||||
<?hh
|
||||
|
||||
class Existing {
|
||||
}
|
||||
|
||||
type Existing = int; // error, Existing exists
|
||||
@@ -0,0 +1 @@
|
||||
HipHop Fatal error: The type Existing is already defined to a different class (Existing)
|
||||
Link simbólico
+1
@@ -0,0 +1 @@
|
||||
filter.remove_in
|
||||
@@ -0,0 +1,5 @@
|
||||
<?hh
|
||||
|
||||
type Something = int;
|
||||
class Something {} // error, Something already exists
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
HipHop Fatal error: The type Something is already defined to a different class (Something)
|
||||
Link simbólico
+1
@@ -0,0 +1 @@
|
||||
filter.remove_in
|
||||
@@ -0,0 +1,21 @@
|
||||
<?hh
|
||||
|
||||
type Something = int;
|
||||
type Something = int;
|
||||
type Something = int;
|
||||
|
||||
type Coord = (int, int);
|
||||
type Coord = (int, int, int); // only caught by typechecker
|
||||
|
||||
type Foo = shape(
|
||||
'x' => int,
|
||||
);
|
||||
type Foo = shape( // only caught by typechecker
|
||||
'x' => string,
|
||||
);
|
||||
|
||||
class Something2 {}
|
||||
type SomethingElse = Something2;
|
||||
type SomethingElse = Something2;
|
||||
|
||||
echo "Yeah\n";
|
||||
@@ -0,0 +1 @@
|
||||
Yeah
|
||||
@@ -0,0 +1,3 @@
|
||||
<?hh
|
||||
|
||||
type Yo = IDontExist;
|
||||
@@ -0,0 +1 @@
|
||||
HipHop Fatal error: Unknown type or class IDontExist
|
||||
Link simbólico
+1
@@ -0,0 +1 @@
|
||||
filter.remove_in
|
||||
@@ -0,0 +1,53 @@
|
||||
<?hh
|
||||
|
||||
newtype Foo = Int;
|
||||
newtype Foo2 = Integer;
|
||||
|
||||
function foo(Foo $x, Foo2 $y) {}
|
||||
|
||||
newtype Bar = floAt;
|
||||
|
||||
function bar(Bar $k) {}
|
||||
|
||||
class bazcls {}
|
||||
newtype Baz = BazCls;
|
||||
function baz(Baz $k) {}
|
||||
function baz2(Bazcls $k) {}
|
||||
|
||||
type A = aRray;
|
||||
type B = iNt;
|
||||
type C = iNteger;
|
||||
type D = bOol;
|
||||
type E = bOolean;
|
||||
type F = sTring;
|
||||
type G = rEal;
|
||||
type H = fLoat;
|
||||
type I = dOuble;
|
||||
function lots(A $x,
|
||||
B $xx,
|
||||
C $xxx,
|
||||
D $xxxx,
|
||||
E $xxxxx,
|
||||
F $xxxxxx,
|
||||
G $xxxxxxx,
|
||||
H $xxxxxxxx,
|
||||
I $xxxxxxxxx)
|
||||
{}
|
||||
|
||||
function main() {
|
||||
foo(12, 13);
|
||||
bar(1.0);
|
||||
baz(new Bazcls());
|
||||
baz2(new Bazcls());
|
||||
lots(array(),
|
||||
12,
|
||||
12,
|
||||
false,
|
||||
true,
|
||||
"asd",
|
||||
1.3,
|
||||
1.4,
|
||||
1.5);
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,5 @@
|
||||
<?hh
|
||||
|
||||
newtype UserID = int;
|
||||
|
||||
function make_user_id(UserId $id): UserID { return $id; }
|
||||
@@ -0,0 +1,37 @@
|
||||
<?hh
|
||||
|
||||
type MyInt = int;
|
||||
type MyBool = bool;
|
||||
type MyDouble = double;
|
||||
type MyArray = array;
|
||||
type MyString = string;
|
||||
|
||||
function fooInt(MyInt $x): void {
|
||||
echo "int:\n";
|
||||
var_dump($x);
|
||||
}
|
||||
function fooBool(MyBool $x): void {
|
||||
echo "bool\n";
|
||||
var_dump($x);
|
||||
}
|
||||
function fooDouble(MyDouble $x): void {
|
||||
echo "double\n";
|
||||
var_dump($x);
|
||||
}
|
||||
function fooArray(MyArray $x): void {
|
||||
echo "array:\n";
|
||||
var_dump($x);
|
||||
}
|
||||
function fooString(MyString $x): void {
|
||||
echo "string:\n";
|
||||
var_dump($x);
|
||||
}
|
||||
|
||||
function main() {
|
||||
fooInt(12);
|
||||
fooBool(false);
|
||||
fooDouble(1.2);
|
||||
fooArray(array(1,2,3));
|
||||
fooString("asdasd");
|
||||
}
|
||||
main();
|
||||
@@ -0,0 +1,17 @@
|
||||
int:
|
||||
int(12)
|
||||
bool
|
||||
bool(false)
|
||||
double
|
||||
float(1.2)
|
||||
array:
|
||||
array(3) {
|
||||
[0]=>
|
||||
int(1)
|
||||
[1]=>
|
||||
int(2)
|
||||
[2]=>
|
||||
int(3)
|
||||
}
|
||||
string:
|
||||
string(6) "asdasd"
|
||||
@@ -0,0 +1,11 @@
|
||||
<?hh
|
||||
|
||||
type MyString = string;
|
||||
|
||||
function foo(MyString $x): void {
|
||||
}
|
||||
|
||||
function test() {
|
||||
foo(123); // failure, expected string
|
||||
}
|
||||
test();
|
||||
@@ -0,0 +1 @@
|
||||
HipHop Fatal error: Argument 1 passed to foo() must be an instance of MyString, int given
|
||||
Link simbólico
+1
@@ -0,0 +1 @@
|
||||
filter.remove_in
|
||||
@@ -0,0 +1,16 @@
|
||||
<?hh
|
||||
|
||||
type Point = shape(
|
||||
'x' => real,
|
||||
'y' => real,
|
||||
);
|
||||
|
||||
function woot(Point $x) : Point {
|
||||
return $x;
|
||||
}
|
||||
|
||||
function test(): void {
|
||||
woot(null); // failure, expected Point
|
||||
}
|
||||
|
||||
test();
|
||||
@@ -0,0 +1 @@
|
||||
HipHop Fatal error: Argument 1 passed to woot() must be an instance of Point, null given
|
||||
Link simbólico
+1
@@ -0,0 +1 @@
|
||||
filter.remove_in
|
||||
@@ -0,0 +1,19 @@
|
||||
<?hh
|
||||
|
||||
type Point = shape(
|
||||
'x' => int,
|
||||
'y' => int,
|
||||
);
|
||||
|
||||
function foo(Point $pt): void {
|
||||
var_dump($pt);
|
||||
}
|
||||
|
||||
function main(): void {
|
||||
foo(shape(
|
||||
'x' => 12,
|
||||
'y' => 12
|
||||
));
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,6 @@
|
||||
array(2) {
|
||||
[0]=>
|
||||
int(12)
|
||||
[1]=>
|
||||
int(12)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?hh
|
||||
|
||||
type Foo = array;
|
||||
type Bar = Foo;
|
||||
|
||||
function main(Bar $x) {
|
||||
echo "Hi\n";
|
||||
}
|
||||
|
||||
main(array(12));
|
||||
@@ -0,0 +1 @@
|
||||
Hi
|
||||
@@ -7,7 +7,6 @@
|
||||
# % fbconfig hphp/util/parser/test && fbmake dbg && \
|
||||
# ./hphp/tools/run_verify_parse.sh
|
||||
#
|
||||
|
||||
: ${FBMAKE_BIN_ROOT=$HPHP_HOME/_bin}
|
||||
|
||||
VERIFY_SCRIPT=./test/verify.sh
|
||||
@@ -18,10 +17,10 @@ PARSE_SKIP='dv_i0.php strict_bad_end.php strict_bad_start.php \
|
||||
strict_numbers.php syntax-error.php xhp-malformed.php Xhp.php \
|
||||
trailing_comma_bad1.php trailing_comma_bad2.php trailing_comma_bad3.php \
|
||||
trailing_comma_bad4.php trailing_comma_bad5.php trailing_comma_bad6.php'
|
||||
PARSE_SKIP="$PARSE_SKIP $(cd test/vm && ls parse_fail_*.php)"
|
||||
|
||||
######################################################################
|
||||
|
||||
cd $HPHP_HOME/hphp
|
||||
|
||||
skip_list=
|
||||
for x in $PARSE_SKIP ; do
|
||||
|
||||
@@ -189,10 +189,13 @@
|
||||
YYTOKEN(398, T_TYPELIST_LT),
|
||||
YYTOKEN(399, T_TYPELIST_GT),
|
||||
YYTOKEN(400, T_UNRESOLVED_LT),
|
||||
YYTOKEN(401, T_COLLECTION)
|
||||
YYTOKEN(401, T_COLLECTION),
|
||||
YYTOKEN(402, T_SHAPE),
|
||||
YYTOKEN(403, T_TYPE),
|
||||
YYTOKEN(404, T_UNRESOLVED_TYPE)
|
||||
};
|
||||
#ifndef YYTOKEN_MAX
|
||||
#define YYTOKEN_MAX 401
|
||||
#define YYTOKEN_MAX 404
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -30,6 +30,11 @@
|
||||
#define SETTOKEN _scanner->setToken(yytext, yyleng)
|
||||
#define STEPPOS _scanner->stepPos(yytext, yyleng)
|
||||
|
||||
#define HH_ONLY_KEYWORD(tok) do { \
|
||||
SETTOKEN; \
|
||||
return _scanner->hipHopKeywordsEnabled() ? tok : T_STRING; \
|
||||
} while (0)
|
||||
|
||||
#define IS_LABEL_START(c) \
|
||||
(((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z') || \
|
||||
(c) == '_' || (c) >= 0x7F)
|
||||
@@ -395,6 +400,10 @@ BACKQUOTE_CHARS ("{"*([^$`\\{]|("\\"{ANY_CHAR}))|{BACKQUOTE_LITERAL_DOLLAR})
|
||||
<ST_IN_SCRIPTING>"<<" { STEPPOS; return T_SL;}
|
||||
<ST_IN_SCRIPTING>"..." { SETTOKEN; return T_VARARG; }
|
||||
|
||||
<ST_IN_SCRIPTING>"shape" { HH_ONLY_KEYWORD(T_SHAPE); }
|
||||
<ST_IN_SCRIPTING>"type" { HH_ONLY_KEYWORD(T_UNRESOLVED_TYPE); }
|
||||
<ST_IN_SCRIPTING>"newtype" { HH_ONLY_KEYWORD(T_UNRESOLVED_TYPE); }
|
||||
|
||||
<ST_IN_SCRIPTING>">>" {
|
||||
if (_scanner->getLookaheadLtDepth() < 2) {
|
||||
STEPPOS;
|
||||
|
||||
+123
-4
@@ -656,6 +656,17 @@ static void only_in_hphp_syntax(Parser *_p) {
|
||||
}
|
||||
}
|
||||
|
||||
// Shapes may not have leading integers in key names, considered as a
|
||||
// parse time error. This is because at runtime they are currently
|
||||
// hphp arrays, which will treat leading integer keys as numbers.
|
||||
static void validate_shape_keyname(Token& tok, Parser* _p) {
|
||||
if (tok.text().empty()) {
|
||||
HPHP_PARSER_ERROR("Shape key names may not be empty", _p);
|
||||
}
|
||||
if (isdigit(tok.text()[0])) {
|
||||
HPHP_PARSER_ERROR("Shape key names may not start with integers", _p);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -795,6 +806,9 @@ static int yylex(YYSTYPE *token, HPHP::Location *loc, Parser *_p) {
|
||||
%token T_UNRESOLVED_LT
|
||||
|
||||
%token T_COLLECTION
|
||||
%token T_SHAPE
|
||||
%token T_TYPE
|
||||
%token T_UNRESOLVED_TYPE
|
||||
|
||||
%%
|
||||
|
||||
@@ -814,6 +828,7 @@ top_statement:
|
||||
| function_declaration_statement { _p->nns(); $$ = $1;}
|
||||
| class_declaration_statement { _p->nns(); $$ = $1;}
|
||||
| trait_declaration_statement { _p->nns(); $$ = $1;}
|
||||
| sm_typedef_statement { $$ = $1; }
|
||||
| T_HALT_COMPILER '(' ')' ';' { $$.reset();}
|
||||
| T_NAMESPACE namespace_name ';' { _p->onNamespaceStart($2.text());
|
||||
$$.reset();}
|
||||
@@ -1593,8 +1608,9 @@ expr_no_variable:
|
||||
| T_UNSET_CAST expr { UEXP($$,$2,T_UNSET_CAST,1);}
|
||||
| T_EXIT exit_expr { UEXP($$,$2,T_EXIT,1);}
|
||||
| '@' expr { UEXP($$,$2,'@',1);}
|
||||
| scalar { $$ = $1;}
|
||||
| array_literal { $$ = $1;}
|
||||
| scalar { $$ = $1; }
|
||||
| array_literal { $$ = $1; }
|
||||
| shape_literal { $$ = $1; }
|
||||
| '`' backticks_expr '`' { _p->onEncapsList($$,'`',$2);}
|
||||
| T_PRINT expr { UEXP($$,$2,T_PRINT,1);}
|
||||
| function_loc
|
||||
@@ -1618,8 +1634,49 @@ expr_no_variable:
|
||||
| collection_literal { $$ = $1;}
|
||||
;
|
||||
|
||||
non_empty_shape_pair_list:
|
||||
non_empty_shape_pair_list ','
|
||||
T_CONSTANT_ENCAPSED_STRING
|
||||
T_DOUBLE_ARROW
|
||||
expr { validate_shape_keyname($3, _p);
|
||||
_p->onArrayPair($$,&$1,&$3,$5,0); }
|
||||
| T_CONSTANT_ENCAPSED_STRING
|
||||
T_DOUBLE_ARROW
|
||||
expr { validate_shape_keyname($1, _p);
|
||||
_p->onArrayPair($$, 0,&$1,$3,0); }
|
||||
;
|
||||
|
||||
non_empty_static_shape_pair_list:
|
||||
non_empty_static_shape_pair_list ','
|
||||
T_CONSTANT_ENCAPSED_STRING
|
||||
T_DOUBLE_ARROW
|
||||
static_scalar { validate_shape_keyname($3, _p);
|
||||
_p->onArrayPair($$,&$1,&$3,$5,0); }
|
||||
| T_CONSTANT_ENCAPSED_STRING
|
||||
T_DOUBLE_ARROW
|
||||
static_scalar { validate_shape_keyname($1, _p);
|
||||
_p->onArrayPair($$, 0,&$1,$3,0); }
|
||||
;
|
||||
|
||||
shape_pair_list:
|
||||
non_empty_shape_pair_list
|
||||
possible_comma { $$ = $1; }
|
||||
| { $$.reset(); }
|
||||
;
|
||||
|
||||
static_shape_pair_list:
|
||||
non_empty_static_shape_pair_list
|
||||
possible_comma { $$ = $1; }
|
||||
| { $$.reset(); }
|
||||
;
|
||||
|
||||
shape_literal:
|
||||
T_SHAPE '(' shape_pair_list ')' { only_in_strict_mode(_p);
|
||||
_p->onArray($$, $3, T_ARRAY); }
|
||||
;
|
||||
|
||||
array_literal:
|
||||
T_ARRAY '(' array_pair_list ')' { _p->onArray($$,$3,T_ARRAY);}
|
||||
T_ARRAY '(' array_pair_list ')' { _p->onArray($$,$3,T_ARRAY);}
|
||||
;
|
||||
|
||||
collection_literal:
|
||||
@@ -1801,6 +1858,7 @@ xhp_bareword:
|
||||
| T_NS_C { $$ = $1;}
|
||||
| T_TRAIT { $$ = $1;}
|
||||
| T_TRAIT_C { $$ = $1;}
|
||||
| T_TYPE { $$ = $1;}
|
||||
;
|
||||
|
||||
simple_function_call:
|
||||
@@ -1867,10 +1925,14 @@ static_scalar:
|
||||
| '+' static_scalar { UEXP($$,$2,'+',1);}
|
||||
| '-' static_scalar { UEXP($$,$2,'-',1);}
|
||||
| T_ARRAY '('
|
||||
static_array_pair_list ')' { _p->onArray($$,$3,T_ARRAY);}
|
||||
static_array_pair_list ')' { _p->onArray($$,$3,T_ARRAY); }
|
||||
| T_SHAPE '('
|
||||
static_shape_pair_list ')' { only_in_strict_mode(_p);
|
||||
_p->onArray($$,$3,T_ARRAY); }
|
||||
| static_class_constant { $$ = $1;}
|
||||
| static_collection_literal { $$ = $1;}
|
||||
;
|
||||
|
||||
static_class_constant:
|
||||
class_namespace_string_typeargs
|
||||
T_PAAMAYIM_NEKUDOTAYIM
|
||||
@@ -1879,6 +1941,7 @@ static_class_constant:
|
||||
ident { $1.xhpLabel();
|
||||
_p->onClassConst($$, $1, $3, 1);}
|
||||
;
|
||||
|
||||
scalar:
|
||||
namespace_string { _p->onConstantValue($$, $1);}
|
||||
| T_STRING_VARNAME { _p->onConstantValue($$, $1);}
|
||||
@@ -1895,6 +1958,7 @@ static_array_pair_list:
|
||||
possible_comma { $$ = $1;}
|
||||
| { $$.reset();}
|
||||
;
|
||||
|
||||
possible_comma:
|
||||
',' { $$.reset();}
|
||||
| { $$.reset();}
|
||||
@@ -1903,6 +1967,7 @@ possible_comma_in_hphp_syntax:
|
||||
',' { only_in_hphp_syntax(_p); $$.reset();}
|
||||
| { $$.reset();}
|
||||
;
|
||||
|
||||
non_empty_static_array_pair_list:
|
||||
non_empty_static_array_pair_list
|
||||
',' static_scalar T_DOUBLE_ARROW
|
||||
@@ -1937,7 +2002,11 @@ static_scalar_ae:
|
||||
| '-' static_numeric_scalar_ae { UEXP($$,$2,'-',1);}
|
||||
| T_ARRAY '('
|
||||
static_array_pair_list_ae ')' { _p->onArray($$,$3,T_ARRAY);}
|
||||
| T_SHAPE '('
|
||||
static_shape_pair_list_ae ')' { only_in_strict_mode(_p);
|
||||
_p->onArray($$,$3,T_ARRAY); }
|
||||
;
|
||||
|
||||
static_array_pair_list_ae:
|
||||
non_empty_static_array_pair_list_ae
|
||||
possible_comma { $$ = $1;}
|
||||
@@ -1958,6 +2027,23 @@ non_empty_static_scalar_list_ae:
|
||||
',' static_scalar_ae { _p->onArrayPair($$,&$1, 0,$3,0);}
|
||||
| static_scalar_ae { _p->onArrayPair($$, 0, 0,$1,0);}
|
||||
;
|
||||
|
||||
static_shape_pair_list_ae:
|
||||
non_empty_static_shape_pair_list_ae
|
||||
possible_comma { $$ = $1; }
|
||||
| { $$.reset(); }
|
||||
;
|
||||
non_empty_static_shape_pair_list_ae:
|
||||
non_empty_static_shape_pair_list_ae
|
||||
',' T_CONSTANT_ENCAPSED_STRING
|
||||
T_DOUBLE_ARROW static_scalar_ae { validate_shape_keyname($3, _p);
|
||||
_p->onArrayPair($$,&$1,&$3,$5,0); }
|
||||
| T_CONSTANT_ENCAPSED_STRING
|
||||
T_DOUBLE_ARROW
|
||||
static_scalar_ae { validate_shape_keyname($1, _p);
|
||||
_p->onArrayPair($$, 0,&$1,$3,0); }
|
||||
;
|
||||
|
||||
static_scalar_list_ae:
|
||||
non_empty_static_scalar_list_ae
|
||||
possible_comma { $$ = $1;}
|
||||
@@ -1968,6 +2054,7 @@ attribute_static_scalar_list:
|
||||
| { Token t; t.reset();
|
||||
_p->onArray($$,t,T_ARRAY);}
|
||||
;
|
||||
|
||||
non_empty_user_attribute_list:
|
||||
non_empty_user_attribute_list
|
||||
',' ident
|
||||
@@ -2240,6 +2327,11 @@ class_constant:
|
||||
* mode, but simplify down to the original thing
|
||||
*/
|
||||
|
||||
sm_typedef_statement:
|
||||
T_TYPE ident '=' sm_type ';' { only_in_strict_mode(_p);
|
||||
_p->onTypedef($$, $2, $4); }
|
||||
;
|
||||
|
||||
sm_name_with_type: /* foo -> int foo */
|
||||
ident { $$ = $1; }
|
||||
| sm_type ident { only_in_strict_mode(_p); $$ = $2; }
|
||||
@@ -2283,10 +2375,36 @@ sm_opt_return_type:
|
||||
sm_typevar_list:
|
||||
ident ',' sm_typevar_list { _p->addTypeVar($1.text()); }
|
||||
| ident { _p->addTypeVar($1.text()); }
|
||||
| ident T_AS sm_shape_type
|
||||
sm_typevar_list
|
||||
| ident T_AS ident ','
|
||||
sm_typevar_list { _p->addTypeVar($1.text()); }
|
||||
| ident T_AS ident { _p->addTypeVar($1.text()); }
|
||||
| ident T_AS sm_shape_type
|
||||
;
|
||||
|
||||
sm_shape_member_type:
|
||||
T_CONSTANT_ENCAPSED_STRING
|
||||
T_DOUBLE_ARROW
|
||||
sm_type { validate_shape_keyname($1, _p); }
|
||||
;
|
||||
|
||||
sm_non_empty_shape_member_list:
|
||||
sm_non_empty_shape_member_list ','
|
||||
sm_shape_member_type
|
||||
| sm_shape_member_type
|
||||
;
|
||||
|
||||
sm_shape_member_list:
|
||||
sm_non_empty_shape_member_list
|
||||
possible_comma { $$ = $1; }
|
||||
| /* empty */
|
||||
{}
|
||||
|
||||
sm_shape_type:
|
||||
T_SHAPE
|
||||
'(' sm_shape_member_list ')' { only_in_strict_mode(_p);
|
||||
$$.setText("array"); }
|
||||
;
|
||||
|
||||
/* extends non_empty_type_decl with some more types */
|
||||
@@ -2307,6 +2425,7 @@ sm_type:
|
||||
}
|
||||
}
|
||||
| T_ARRAY { $$.setText("array"); }
|
||||
| sm_shape_type { $$ = $1; }
|
||||
| T_ARRAY T_TYPELIST_LT sm_type
|
||||
T_TYPELIST_GT { only_in_strict_mode(_p);
|
||||
$$.setText("array"); }
|
||||
|
||||
+57735
-57366
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
@@ -226,6 +226,12 @@ void Scanner::nextLookahead(TokenStore::iterator& pos) {
|
||||
}
|
||||
}
|
||||
|
||||
bool Scanner::nextIfToken(TokenStore::iterator& pos, int tok) {
|
||||
if (pos->t != tok) return false;
|
||||
nextLookahead(pos);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Scanner::tryParseTypeList(TokenStore::iterator& pos) {
|
||||
for (;;) {
|
||||
if (!tryParseNSType(pos)) return false;
|
||||
@@ -303,6 +309,8 @@ Scanner::tryParseNSType(TokenStore::iterator& pos) {
|
||||
case T_ARRAY:
|
||||
nextLookahead(pos);
|
||||
break;
|
||||
case T_SHAPE:
|
||||
return tryParseShapeType(pos);
|
||||
case T_XHP_LABEL:
|
||||
nextLookahead(pos);
|
||||
return true;
|
||||
@@ -331,11 +339,55 @@ Scanner::tryParseNSType(TokenStore::iterator& pos) {
|
||||
}
|
||||
}
|
||||
|
||||
bool Scanner::tryParseShapeType(TokenStore::iterator& pos) {
|
||||
assert(pos->t == T_SHAPE);
|
||||
nextLookahead(pos);
|
||||
|
||||
if (pos->t == T_STRING) {
|
||||
nextLookahead(pos);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pos->t == '(') {
|
||||
nextLookahead(pos);
|
||||
if (pos->t != ')') {
|
||||
if (!tryParseShapeMemberList(pos)) return false;
|
||||
if (pos->t != ')') return false;
|
||||
}
|
||||
nextLookahead(pos);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Scanner::tryParseShapeMemberList(TokenStore::iterator& pos) {
|
||||
assert(pos->t != ')'); // already determined to be nonempty
|
||||
|
||||
for (;;) {
|
||||
if (!nextIfToken(pos, T_CONSTANT_ENCAPSED_STRING) ||
|
||||
!nextIfToken(pos, T_DOUBLE_ARROW)) {
|
||||
return false;
|
||||
}
|
||||
if (!tryParseNSType(pos)) return false;
|
||||
if (pos->t == ')') return true;
|
||||
if (!nextIfToken(pos, ',')) return false;
|
||||
if (pos->t == ')') return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool isUnresolved(int tokid) {
|
||||
return tokid == T_UNRESOLVED_LT ||
|
||||
tokid == T_UNRESOLVED_TYPE;
|
||||
}
|
||||
|
||||
int Scanner::getNextToken(ScannerToken &t, Location &l) {
|
||||
int tokid;
|
||||
bool la = !m_lookahead.empty();
|
||||
tokid = fetchToken(t, l);
|
||||
if (LIKELY(tokid != T_UNRESOLVED_LT)) {
|
||||
if (LIKELY(!isUnresolved(tokid))) {
|
||||
// In the common case, we don't have to perform any resolution
|
||||
// and we can just return the token
|
||||
if (UNLIKELY(la)) {
|
||||
@@ -345,7 +397,7 @@ int Scanner::getNextToken(ScannerToken &t, Location &l) {
|
||||
}
|
||||
return tokid;
|
||||
}
|
||||
// We encountered a '<' character that needs to be resolved.
|
||||
|
||||
if (!la) {
|
||||
// If this token didn't come from the lookahead store, we
|
||||
// need to stash it there
|
||||
@@ -353,20 +405,39 @@ int Scanner::getNextToken(ScannerToken &t, Location &l) {
|
||||
LookaheadToken ltd = { t, l, tokid };
|
||||
*it = ltd;
|
||||
}
|
||||
// Look at subsequent tokens to determine if the '<' character
|
||||
// is the start of a type list
|
||||
TokenStore::iterator pos = m_lookahead.begin();
|
||||
TokenStore::iterator ltPos = pos;
|
||||
nextLookahead(pos);
|
||||
++m_lookaheadLtDepth;
|
||||
bool isTypeList = tryParseTypeList(pos);
|
||||
--m_lookaheadLtDepth;
|
||||
if (!isTypeList || pos->t != '>') {
|
||||
ltPos->t = '<';
|
||||
} else {
|
||||
ltPos->t = T_TYPELIST_LT;
|
||||
pos->t = T_TYPELIST_GT;
|
||||
|
||||
switch (tokid) {
|
||||
case T_UNRESOLVED_TYPE: {
|
||||
auto pos = m_lookahead.begin();
|
||||
auto typePos = pos;
|
||||
nextLookahead(pos);
|
||||
if (pos->t == T_STRING) {
|
||||
typePos->t = T_TYPE;
|
||||
} else {
|
||||
typePos->t = T_STRING;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case T_UNRESOLVED_LT: {
|
||||
// Look at subsequent tokens to determine if the '<' character
|
||||
// is the start of a type list
|
||||
auto pos = m_lookahead.begin();
|
||||
auto ltPos = pos;
|
||||
nextLookahead(pos);
|
||||
++m_lookaheadLtDepth;
|
||||
bool isTypeList = tryParseTypeList(pos);
|
||||
--m_lookaheadLtDepth;
|
||||
if (!isTypeList || pos->t != '>') {
|
||||
ltPos->t = '<';
|
||||
} else {
|
||||
ltPos->t = T_TYPELIST_LT;
|
||||
pos->t = T_TYPELIST_GT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: always_assert(0);
|
||||
}
|
||||
|
||||
tokid = fetchToken(t, l);
|
||||
// We pulled a lookahead token, we need to remove it from the
|
||||
// lookahead store
|
||||
|
||||
@@ -168,9 +168,10 @@ struct TokenStore {
|
||||
class Scanner {
|
||||
public:
|
||||
enum Type {
|
||||
AllowShortTags = 1, // allow <?
|
||||
AllowAspTags = 2, // allow <% %>
|
||||
ReturnAllTokens = 8, // return comments and whitespaces
|
||||
AllowShortTags = 0x01, // allow <?
|
||||
AllowAspTags = 0x02, // allow <% %>
|
||||
ReturnAllTokens = 0x08, // return comments and whitespaces
|
||||
EnableHipHopKeywords = 0x10, // allow hip-hop specific reserved words
|
||||
};
|
||||
|
||||
public:
|
||||
@@ -277,15 +278,27 @@ public:
|
||||
m_isStrictMode = 1;
|
||||
}
|
||||
|
||||
bool isStrictMode() {
|
||||
bool isStrictMode() const {
|
||||
return m_isStrictMode;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns: whether HipHop-extension keywords are enabled.
|
||||
*/
|
||||
bool hipHopKeywordsEnabled() const {
|
||||
return m_type & EnableHipHopKeywords;
|
||||
}
|
||||
|
||||
int getLookaheadLtDepth() {
|
||||
return m_lookaheadLtDepth;
|
||||
}
|
||||
|
||||
private:
|
||||
bool tryParseShapeType(TokenStore::iterator& pos);
|
||||
bool tryParseShapeMemberList(TokenStore::iterator& pos);
|
||||
|
||||
bool nextIfToken(TokenStore::iterator& pos, int tok);
|
||||
|
||||
void computeMd5();
|
||||
|
||||
std::string m_filename;
|
||||
|
||||
+30869
-29498
Diferenças do arquivo suprimidas por serem muito extensas
Carregar Diff
Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais
Referência em uma Nova Issue
Bloquear um usuário