This commit is contained in:
Jakub Míšek 2016-02-20 00:32:22 +01:00
Родитель 6a9aa80f71
Коммит 6cb9b1b306
19 изменённых файлов: 2252 добавлений и 22 удалений

Просмотреть файл

@ -1,6 +1,7 @@
using Microsoft.CodeAnalysis;
using Pchp.CodeAnalysis.CodeGen;
using Pchp.CodeAnalysis.Emit;
using Pchp.CodeAnalysis.FlowAnalysis;
using Pchp.CodeAnalysis.Semantics;
using Pchp.CodeAnalysis.Symbols;
using System;
@ -21,6 +22,8 @@ namespace Pchp.CodeAnalysis
readonly PEModuleBuilder _moduleBuilder;
readonly bool _emittingPdb;
readonly DiagnosticBag _diagnostics;
readonly Worklist _worklist;
// readonly CallGraph _callgraph; //keeps graph of what methods call specific method // used to reanalyze caller methods when return type ot arg. type changes
private SourceCompiler(PhpCompilation compilation, PEModuleBuilder moduleBuilder, bool emittingPdb, DiagnosticBag diagnostics)
{
@ -33,8 +36,8 @@ namespace Pchp.CodeAnalysis
_emittingPdb = emittingPdb;
_diagnostics = diagnostics;
// var callgraph = new ...
// var worklist = new ... // parallel worklist algorithm
_worklist = new Worklist(); // parallel worklist algorithm
// semantic model
}
@ -45,7 +48,7 @@ namespace Pchp.CodeAnalysis
var methods = sourcesymbols.GetFunctions()
.Concat(sourcesymbols.GetTypes().SelectMany(t => t.GetMembers()))
.OfType<SourceBaseMethodSymbol>();
methods.Foreach(action);
methods.ForEach(action);
// TODO: methodsWalker.VisitNamespace(_compilation.SourceModule.GlobalNamespace)
}
@ -57,11 +60,24 @@ namespace Pchp.CodeAnalysis
return method.BoundBlock;
}
internal void AnalyzeMethod(SourceBaseMethodSymbol method)
internal void AnalyzeMethods()
{
//this.WalkMethods(m => worklist.Enlist(m, this.AnalyzeMethod))
//worklist.Do
// DEBUG
this.WalkMethods(this.AnalyzeMethod);
}
private void AnalyzeMethod(SourceBaseMethodSymbol method)
{
Contract.ThrowIfNull(method);
var bound = method.BoundBlock;
// Initial State // declared locals, initial types
// TypeAnalysis + ResolveSymbols
// if (LowerBody(bound)) Enlist(method)
}
/// <summary>
@ -84,7 +100,7 @@ namespace Pchp.CodeAnalysis
CancellationToken cancellationToken)
{
var compiler = new SourceCompiler(compilation, moduleBuilder, emittingPdb, diagnostics);
// 1. Synthetize magic
// a.inline syntax like traits
// b.synthetize entry point, getters, setters, ctors, dispose, magic methods, …
@ -100,7 +116,7 @@ namespace Pchp.CodeAnalysis
// b.build global variables/constants table
// c.type analysis(converge type - mask), resolve symbols
// d.lower semantics, update bound tree, repeat
compiler.WalkMethods(compiler.AnalyzeMethod);
compiler.AnalyzeMethods();
// 4. Emit method bodies
compiler.WalkMethods(compiler.EmitMethodBody);

Просмотреть файл

@ -119,7 +119,7 @@ namespace Pchp.CodeAnalysis.Symbols
if ((object)this.ContainingType == null &&
this.IsDefinition &&
this.ContainingModule == moduleBeingBuilt.SourceModule)
object.ReferenceEquals(this.ContainingModule, moduleBeingBuilt.SourceModule))
{
return this;
}
@ -154,7 +154,7 @@ namespace Pchp.CodeAnalysis.Symbols
if ((object)this.ContainingType != null &&
this.IsDefinition &&
this.ContainingModule == moduleBeingBuilt.SourceModule)
object.ReferenceEquals(this.ContainingModule, moduleBeingBuilt.SourceModule))
{
return this;
}
@ -194,7 +194,7 @@ namespace Pchp.CodeAnalysis.Symbols
Debug.Assert(this.IsDefinitionOrDistinct());
if (this.IsDefinition && // can't be generic instantiation
this.ContainingModule == moduleBeingBuilt.SourceModule) // must be declared in the module we are building
object.ReferenceEquals(this.ContainingModule, moduleBeingBuilt.SourceModule)) // must be declared in the module we are building
{
return this;
}

Просмотреть файл

@ -193,7 +193,7 @@ namespace Pchp.CodeAnalysis.Symbols
PEModuleBuilder moduleBeingBuilt = (PEModuleBuilder)context.Module;
if (this.IsDefinition &&
this.ContainingModule == moduleBeingBuilt.SourceModule)
object.ReferenceEquals(this.ContainingModule, moduleBeingBuilt.SourceModule))
{
return this;
}

Просмотреть файл

@ -0,0 +1,150 @@
using Pchp.Syntax;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pchp.CodeAnalysis.FlowAnalysis
{
/// <summary>
/// Used by analysis of routine in case we know additional information about called context.
/// </summary>
public struct CallInfo
{
#region Fields
/// <summary>
/// Context of type references used in call info.
/// </summary>
private readonly TypeRefContext _typeCtx;
/// <summary>
/// Known types of parameters.
/// </summary>
private readonly TypeRefMask[] _paramsType;
///// <summary>
///// Known parameters value.
///// </summary>
//public readonly object[] _paramsValue;
/// <summary>
/// Optional. Gets type of <c>static</c> in called context.
/// </summary>
private readonly TypeRefMask _lateStaticBindType;
#endregion
#region Construction
/// <summary>
/// Initializes <see cref="CallInfo"/>.
/// </summary>
/// <param name="ctx">Type context of the caller.</param>
/// <param name="paramsCount">Amount of parameters used for the call.</param>
public CallInfo(TypeRefContext ctx, int paramsCount)
: this(ctx, paramsCount, 0)
{
}
/// <summary>
/// Initializes <see cref="CallInfo"/>.
/// </summary>
/// <param name="ctx">Type context of the caller.</param>
/// <param name="paramsCount">Amount of parameters used for the call.</param>
/// <param name="lateStaticBindType">Type of the <c>self</c> in the caller context.</param>
public CallInfo(TypeRefContext ctx, int paramsCount, TypeRefMask lateStaticBindType)
: this(ctx, GetParamsTypeArr(paramsCount, TypeRefMask.AnyType), lateStaticBindType)
{
}
/// <summary>
/// Initializes <see cref="CallInfo"/>.
/// </summary>
/// <param name="ctx">Type context of the caller.</param>
/// <param name="paramsType">Type of parameters used for the call. Length of the array corresponds to the parameters count.</param>
public CallInfo(TypeRefContext ctx, TypeRefMask[] paramsType)
: this(ctx, paramsType, 0)
{
}
/// <summary>
/// Initializes <see cref="CallInfo"/>.
/// </summary>
/// <param name="ctx">Type context of the caller.</param>
/// <param name="paramsType">Type of parameters used for the call. Length of the array corresponds to the parameters count.</param>
/// <param name="lateStaticBindType">Type of the <c>self</c> in the caller context.</param>
public CallInfo(TypeRefContext ctx, TypeRefMask[] paramsType, TypeRefMask lateStaticBindType)
{
_typeCtx = ctx;
_paramsType = paramsType;
_lateStaticBindType = (lateStaticBindType.IsSingleType ? lateStaticBindType : 0);
}
private static TypeRefMask[] GetParamsTypeArr(int count, TypeRefMask value)
{
if (count < 0)
return null;
if (count == 0)
return EmptyArray<TypeRefMask>.Instance;
//
var arr = new TypeRefMask[count];
for (int i = 0; i < arr.Length; i++)
arr[i] = value;
return arr;
}
#endregion
/// <summary>
/// Gets known parameters count. If call info is empty, the method gets <c>-1</c>.
/// </summary>
public int ParametersCount { get { return (_paramsType != null) ? _paramsType.Length : -1; } }
/// <summary>
/// Gets actual parameter type if provided. Otherwise <c>void</c>.
/// </summary>
/// <param name="ctx">Target type context.</param>
/// <param name="index">Index of parameter.</param>
/// <returns>Type mask of the parameter or <c>void</c>.</returns>
public TypeRefMask GetParamType(TypeRefContext/*!*/ctx, int index)
{
if (ctx == null) throw new ArgumentNullException("ctx");
if (_typeCtx != null && index >= 0 && index < _paramsType.Length)
return ctx.AddToContext(_typeCtx, _paramsType[index]);
return 0;
}
/// <summary>
/// Gets actual lates static bind type (type of <c>static</c>) if provided.
/// Otherwise <c>void</c>.
/// </summary>
/// <param name="ctx">Target type context.</param>
/// <returns>TYpe mask of <c>static</c> in given context or <c>void</c>.</returns>
public TypeRefMask GetLateStaticBindType(TypeRefContext/*!*/ctx)
{
if (ctx == null) throw new ArgumentNullException("ctx");
if (_typeCtx != null && !_lateStaticBindType.IsUninitialized)
return ctx.AddToContext(_typeCtx, _lateStaticBindType);
return 0;
}
///// <summary>
///// Gets actual parameter value if known. Otherwise <see cref="Helpers.ExpressionValue.UnknownValue"/>.
///// </summary>
//public object GetParamValue(int index)
//{
// // if (index >= 0 && index < _paramValues.Length) return _paramValues[index];
// return Helpers.ExpressionValue.UnknownValue;
//}
}
}

Просмотреть файл

@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Pchp.Syntax;
using Pchp.Syntax.AST;
namespace Pchp.CodeAnalysis.FlowAnalysis
{
/// <summary>
/// Reference to a type.
/// </summary>
public interface ITypeRef : IEquatable<ITypeRef>
{
/// <summary>
/// Full type name.
/// </summary>
QualifiedName QualifiedName { get; }
/// <summary>
/// Gets value indicating whether the type represents an object (class or interface) and not a primitive type.
/// </summary>
bool IsObject { get; }
/// <summary>
/// Gets value indicating whether the type represents an array.
/// </summary>
bool IsArray { get; }
/// <summary>
/// Gets value indicating whether the type represents a primitive type.
/// </summary>
bool IsPrimitiveType { get; }
/// <summary>
/// Gets value indicating whether the type represents a lambda function or <c>callable</c> primitive type;
/// </summary>
bool IsLambda { get; }
/// <summary>
/// Gets known keys of the array. Each value is either <c>int</c> or <c>string</c>. Cannot be <c>null</c>.
/// </summary>
/// <exception cref="InvalidOperationException">In case the type does not represent an array.</exception>
IEnumerable<object> Keys { get; }
/// <summary>
/// Gets merged type information of array items values.
/// </summary>
TypeRefMask ElementType { get; }
/// <summary>
/// Gets type information of callable return value. This value is valid for lambda functions and instances of closures.
/// </summary>
/// <exception cref="InvalidOperationException">In case the type does not represent a callable.</exception>
TypeRefMask LambdaReturnType { get; }
/// <summary>
/// Gets lambda function signature.
/// </summary>
/// <exception cref="InvalidOperationException">In case the type does not represent a callable.</exception>
Signature LambdaSignature { get; }
/// <summary>
/// Gets the type code.
/// </summary>
PhpDataType TypeCode { get; }
/// <summary>
/// Transfers this type reference to the target type context.
/// The method may return <c>this</c>, it cannot return <c>null</c>.
/// </summary>
ITypeRef/*!*/Transfer(TypeRefContext/*!*/source, TypeRefContext/*!*/target);
}
}

Просмотреть файл

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Pchp.CodeAnalysis.FlowAnalysis
{
/// <summary>
/// PHP value type codes.
/// </summary>
public enum PhpDataType : int
{
Undefined = 0,
Null,
Boolean,
Long,
Double,
String,
Array,
Object,
Resource,
Callable,
/// <summary>
/// Amount of types within the enum.
/// </summary>
Count
}
/// <summary>
/// Helper methods for <see cref="PhpDataType"/>.
/// </summary>
public static class PhpDataTypeEnum
{
/// <summary>
/// Mask which bits corresponds to <see cref="PhpDataType"/> values. Determines which type can be a <c>null</c> value.
/// </summary>
private const int NullableTypesMask =
(1 << (int)PhpDataType.Object) |
(1 << (int)PhpDataType.Array) |
(1 << (int)PhpDataType.Resource) |
(1 << (int)PhpDataType.Callable) |
(1 << (int)PhpDataType.Null);
/// <summary>
/// Gets value indicating whether given type is a nullable type.
/// </summary>
public static bool IsNullable(this PhpDataType code)
{
return (NullableTypesMask & (1 << (int)code)) != 0;
}
}
}

Просмотреть файл

@ -0,0 +1,197 @@
using Pchp.CodeAnalysis.FlowAnalysis;
using Pchp.CodeAnalysis.Semantics;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pchp.CodeAnalysis.FlowAnalysis
{
/// <summary>
/// Provides helper methods for working with types.
/// </summary>
internal static class TypeHelpers
{
///// <summary>
///// Gets value determining whether <paramref name="totype"/> type can be assigned from <paramref name="fromtype"/>.
///// </summary>
///// <param name="totype">Type mask which we check whether is assignable from <paramref name="fromtype"/>.</param>
///// <param name="fromtype">Type mask we check whether is equal or is a subclass of <paramref name="totype"/>.</param>
///// <param name="ctx">Type context for resolving class names from type mask.</param>
///// <param name="model">Helper object which caches class inheritance.</param>
///// <remarks>
///// Gets <c>true</c>, if <paramref name="totype"/> is equal to or is a base type of <paramref name="fromtype"/>.
///// Gets <c>False</c> for <c>void</c> type masks.
///// </remarks>
//public static bool IsAssignableFrom(this TypeRefMask totype, TypeRefMask fromtype, TypeRefContext/*!*/ctx, ISemanticModel/*!*/model)
//{
// Debug.Assert(ctx != null);
// Debug.Assert(model != null);
// if ((totype & fromtype & ~(ulong)TypeRefMask.FlagsMask) != 0)
// return true; // types are equal (or at least one of them is Any Type)
// // object <-> unspecified object instance
// if ((ctx.IsObject(totype) && ctx.IsObject(fromtype)) &&
// (ctx.IsAnObject(totype) || ctx.IsAnObject(fromtype)))
// return true;
// if (IsImplicitConversion(fromtype, totype, ctx, model))
// return true;
// // cut off object types (primitive types do not have subclasses)
// var selfObjs = ctx.GetObjectTypes(totype);
// if (selfObjs.Count == 0)
// return false; // self mask does not represent a class
// var typeObjs = ctx.GetObjectTypes(fromtype);
// if (typeObjs.Count == 0)
// return false; // type mask does not represent a class
// // build inheritance graph and check whether any type from self is a base of anything in type
// if (selfObjs.Count == 1 && typeObjs.Count == 1)
// return model.IsAssignableFrom(selfObjs[0].QualifiedName, typeObjs[0].QualifiedName);
// //
// return model.IsAssignableFrom(selfObjs.Select(t => t.QualifiedName), typeObjs.Select(t => t.QualifiedName));
//}
///// <summary>
///// Determines whether there is an implicit conversion from one type to another.
///// </summary>
//private static bool IsImplicitConversion(TypeRefMask fromtype, TypeRefMask totype, TypeRefContext/*!*/ctx, ISemanticModel/*!*/model)
//{
// // TODO: optimize bit operations
// //
// if (ctx.IsArray(totype) && ctx.IsArray(fromtype))
// {
// // both types are arrays, type may be more specific (int[]) and self just ([])
// // TODO: check whether their element types are assignable,
// // avoid infinite recursion!
// return true;
// }
// // any callable -> "callable"
// if (ctx.IsLambda(totype) && IsCallable(fromtype, ctx, model))
// return true;
// //// allowed conversions
// // int <-> bool
// //if (ctx.IsInteger(totype) && ctx.IsBoolean(fromtype)) // int -> bool
// // return true;
// // int <-> double
// if (ctx.IsNumber(fromtype) && ctx.IsNumber(totype)) // TODO: maybe settings for strict number type check
// return true;
// if (ctx.IsString(totype) && IsConversionToString(fromtype, ctx, model))
// return true;
// //
// if (ctx.IsNull(fromtype) && ctx.IsNullable(totype))
// return true; // NULL can be assigned to any nullable
// //
// return false;
//}
///// <summary>
///// Determines whether given type can be converted to string without warning.
///// </summary>
//internal static bool IsConversionToString(TypeRefMask fromtype, TypeRefContext/*!*/ctx, ISemanticModel/*!*/model)
//{
// // primitive -> string
// if (ctx.IsPrimitiveType(fromtype))
// return true;
// // object with __toString() -> string
// if (ctx.IsObject(fromtype) && ctx.GetObjectTypes(fromtype).Any(tref => model.GetClass(tref.QualifiedName).HasMethod(NameUtils.SpecialNames.__toString, model)))
// return true;
// //
// return false;
//}
///// <summary>
///// Checks whether given type is callable.
///// </summary>
//internal static bool IsCallable(TypeRefMask type, TypeRefContext/*!*/ctx, ISemanticModel/*!*/model)
//{
// if (ctx.IsLambda(type) || ctx.IsString(type) || ctx.IsArray(type))
// return true;
// // type has "__invoke" method
// if (type.IsSingleType) // just optimization
// {
// var tref = ctx.GetObjectTypes(type).FirstOrDefault();
// if (tref != null)
// {
// var node = model.GetClass(tref.QualifiedName);
// // type has __invoke method or is assignable from Closure
// if (node.HasMethod(NameUtils.SpecialNames.__invoke, model) || model.IsAssignableFrom(NameUtils.SpecialNames.Closure, node))
// return true;
// }
// }
// return false;
//}
///// <summary>
///// Gets value indicating whether specified object of type <paramref name="type"/> can handle <c>[]</c> operator.
///// </summary>
///// <param name="type">Type of the object.</param>
///// <param name="ctx">Type context.</param>
///// <param name="model">Type graph.</param>
///// <returns>True iff <c>[]</c> operator is allowed.</returns>
//internal static bool HasArrayAccess(TypeRefMask type, TypeRefContext/*!*/ctx, ISemanticModel/*!*/model)
//{
// //
// if (type.IsAnyType || type.IsVoid || ctx.IsArray(type) || ctx.IsString(type))
// return true;
// // object implementing ArrayAccess
// if (ctx.IsObject(type))
// {
// var types = ctx.GetObjectTypes(type);
// foreach (var t in types)
// if (model.IsAssignableFrom(NameUtils.SpecialNames.ArrayAccess, t.QualifiedName))
// return true;
// }
// //
// return false;
//}
///// <summary>
///// Gets value indicating whether specified object of type <paramref name="type"/> can be used as <c>foreach</c> enumerable variable.
///// </summary>
///// <param name="type">Type of the object.</param>
///// <param name="ctx">Type context.</param>
///// <param name="model">Type graph.</param>
///// <returns>True iff <paramref name="type"/> is allowed as <c>foreach</c> enumerator.</returns>
//internal static bool IsTraversable(TypeRefMask type, TypeRefContext/*!*/ctx, ISemanticModel/*!*/model)
//{
// //
// if (type.IsAnyType || type.IsVoid || ctx.IsArray(type))
// return true;
// // object implementing Traversable
// if (ctx.IsObject(type))
// {
// var types = ctx.GetObjectTypes(type);
// foreach (var t in types)
// if (model.IsAssignableFrom(NameUtils.SpecialNames.Traversable, t.QualifiedName))
// return true;
// }
// //
// return false;
//}
}
}

Просмотреть файл

@ -0,0 +1,379 @@
using Pchp.Syntax;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using AST = Pchp.Syntax.AST;
namespace Pchp.CodeAnalysis.FlowAnalysis
{
#region ITypeRef Implementation
/// <summary>
/// Represents a direct type reference.
/// </summary>
internal class ClassTypeRef : ITypeRef, IEquatable<ClassTypeRef>
{
private readonly QualifiedName _qname;
public ClassTypeRef(QualifiedName qname)
{
Debug.Assert(!string.IsNullOrEmpty(qname.Name.Value));
Debug.Assert(!qname.IsReservedClassName); // not self, parent, static
Debug.Assert(!qname.IsPrimitiveTypeName); // use PrimitiveTypeRef instead
_qname = qname;
}
#region ITypeRef Members
public QualifiedName QualifiedName { get { return _qname; } }
public bool IsObject { get { return true; } }
public bool IsArray { get { return false; } }
public bool IsPrimitiveType { get { return false; } }
public bool IsLambda { get { return false; } }
public IEnumerable<object> Keys { get { throw new InvalidOperationException(); } }
public TypeRefMask ElementType { get { return default(TypeRefMask); } }
public TypeRefMask LambdaReturnType { get { throw new InvalidOperationException(); } }
public AST.Signature LambdaSignature { get { throw new InvalidOperationException(); } }
public PhpDataType TypeCode { get { return PhpDataType.Object; } }
public ITypeRef/*!*/Transfer(TypeRefContext/*!*/source, TypeRefContext/*!*/target) { return this; } // there is nothing depending on the context
#endregion
#region IEquatable<ITypeRef> Members
public override int GetHashCode()
{
return _qname.GetHashCode();
}
public override bool Equals(object obj)
{
return Equals(obj as ClassTypeRef);
}
public bool Equals(ITypeRef other)
{
return Equals(other as ClassTypeRef);
}
#endregion
#region IEquatable<ClassTypeRef> Members
public bool Equals(ClassTypeRef other)
{
return other != null && other._qname == _qname;
}
#endregion
}
/// <summary>
/// Represents an array type.
/// </summary>
internal sealed class ArrayTypeRef : ITypeRef, IEquatable<ArrayTypeRef>
{
// TODO: manage keys, handle duplicities (HashSet?), fast merging
// flag whether there might be more keys in addition to listed\
// limit number of keys, ignore numeric keys? remember just strings and constant names
private readonly HashSet<object> _keys;
private readonly TypeRefMask _elementType;
public ArrayTypeRef(IEnumerable<object> keys, TypeRefMask elementType)
{
_keys = null;// new HashSet<object>(keys);
_elementType = elementType;
}
#region ITypeRef Members
public QualifiedName QualifiedName
{
get { return QualifiedName.Array; }
}
public bool IsObject { get { return false; } }
public bool IsArray { get { return true; } }
public bool IsPrimitiveType { get { return true; } }
public bool IsLambda { get { return false; } }
public IEnumerable<object> Keys { get { return (IEnumerable<object>)_keys ?? ArrayUtils.EmptyObjects; } }
public TypeRefMask ElementType { get { return _elementType; } }
public TypeRefMask LambdaReturnType { get { throw new InvalidOperationException(); } }
public AST.Signature LambdaSignature { get { throw new InvalidOperationException(); } }
public PhpDataType TypeCode { get { return PhpDataType.Array; } }
public ITypeRef/*!*/Transfer(TypeRefContext/*!*/source, TypeRefContext/*!*/target)
{
Contract.ThrowIfNull(source);
Contract.ThrowIfNull(target);
// TODO: keys
if (source == target || _elementType.IsVoid || _elementType.IsAnyType)
return this;
// note: there should be no circular dependency
return new ArrayTypeRef(_keys, target.AddToContext(source, _elementType));
}
#endregion
#region IEquatable<ITypeRef> Members
public override bool Equals(object obj)
{
return Equals(obj as ArrayTypeRef);
}
public override int GetHashCode()
{
return ~_elementType.GetHashCode();
}
public bool Equals(ITypeRef other)
{
return Equals(other as ArrayTypeRef);
}
#endregion
#region IEquatable<ArrayTypeRef> Members
public bool Equals(ArrayTypeRef other)
{
return other != null && other._elementType == _elementType; // TODO: keys
}
#endregion
}
/// <summary>
/// Represents a PHP primitive type.
/// </summary>
internal sealed class PrimitiveTypeRef : ITypeRef, IEquatable<PrimitiveTypeRef>
{
private readonly PhpDataType _code;
public PrimitiveTypeRef(PhpDataType code)
{
_code = code;
}
#region ITypeRef Members
public QualifiedName QualifiedName
{
get
{
switch (_code)
{
case PhpDataType.Null: return QualifiedName.Null;
case PhpDataType.Boolean: return QualifiedName.Boolean;
case PhpDataType.Long: return QualifiedName.Integer;
case PhpDataType.Double: return QualifiedName.Double;
case PhpDataType.String: return QualifiedName.String;
case PhpDataType.Array: return QualifiedName.Array;
case PhpDataType.Object: return QualifiedName.Object;
case PhpDataType.Resource: return QualifiedName.Resource;
case PhpDataType.Callable: return QualifiedName.Callable;
default:
throw new ArgumentException();
}
}
}
public bool IsObject { get { return _code == PhpDataType.Object; } }
public bool IsArray { get { return _code == PhpDataType.Array; } }
public bool IsPrimitiveType { get { return true; } }
public bool IsLambda { get { return _code == PhpDataType.Callable; } }
public IEnumerable<object> Keys { get { return null; } }
public TypeRefMask ElementType { get { return TypeRefMask.AnyType; } }
public TypeRefMask LambdaReturnType { get { return TypeRefMask.AnyType; } }
public AST.Signature LambdaSignature { get { return default(AST.Signature); } }
/// <summary>
/// Gets underlaying type code of the primitive type.
/// </summary>
public PhpDataType TypeCode { get { return _code; } }
public ITypeRef/*!*/Transfer(TypeRefContext/*!*/source, TypeRefContext/*!*/target) { return this; }
#endregion
#region IEquatable<ITypeRef> Members
public override int GetHashCode()
{
return (int)_code;
}
public override bool Equals(object obj)
{
return Equals(obj as PrimitiveTypeRef);
}
public bool Equals(ITypeRef other)
{
return Equals(other as PrimitiveTypeRef);
}
#endregion
#region IEquatable<PrimitiveTypeRef> Members
public bool Equals(PrimitiveTypeRef other)
{
return other != null && other._code == _code;
}
#endregion
}
/// <summary>
/// Represents a lambda function with known return type and parameters optionally.
/// </summary>
internal sealed class LambdaTypeRef : ITypeRef, IEquatable<LambdaTypeRef>
{
private readonly TypeRefMask _returnType;
private readonly AST.Signature _signature;
public LambdaTypeRef(TypeRefMask returnType, AST.Signature signature)
{
_returnType = returnType;
_signature = signature;
}
#region ITypeRef Members
/// <summary>
/// Lambda function is of sub class of <c>Closure</c>.
/// </summary>
public QualifiedName QualifiedName
{
get { return NameUtils.SpecialNames.Closure; }
}
/// <summary>
/// Lambda function is an object of type <c>Closure</c>.
/// This handles case when lambda is used as object with methods <c>bindTo</c> or <c>__invoke</c>.
/// </summary>
public bool IsObject
{
get { return true; }
}
public bool IsArray
{
get { return false; }
}
public bool IsPrimitiveType
{
get { return false; }
}
public bool IsLambda
{
get { return true; }
}
public IEnumerable<object> Keys
{
get { throw new InvalidOperationException(); }
}
public TypeRefMask ElementType
{
get { throw new InvalidOperationException(); }
}
public TypeRefMask LambdaReturnType { get { return _returnType; } }
public AST.Signature LambdaSignature { get { return _signature; } }
public PhpDataType TypeCode { get { return PhpDataType.Callable; } }
public ITypeRef Transfer(TypeRefContext source, TypeRefContext target)
{
if (source == target || _returnType.IsVoid || _returnType.IsAnyType)
return this;
// note: there should be no circular dependency
return new LambdaTypeRef(target.AddToContext(source, _returnType), _signature);
}
#endregion
#region IEquatable<ITypeRef> Members
public override int GetHashCode()
{
return _returnType.GetHashCode() ^ 0x777;
}
public override bool Equals(object obj)
{
return Equals(obj as LambdaTypeRef);
}
public bool Equals(ITypeRef other)
{
return Equals(other as LambdaTypeRef);
}
#endregion
#region IEquatable<LambdaTypeRef> Members
public bool Equals(LambdaTypeRef other)
{
return other != null && other._returnType.Equals(_returnType) && Equals(other._signature, _signature);
}
private static bool Equals(AST.FormalParam[] params1, AST.FormalParam[] params2)
{
if (params1.Length == params2.Length)
{
for (int i = 0; i < params1.Length; i++)
if (params1[i].Name != params2[i].Name)
return false;
return true;
}
return false;
}
#endregion
}
#endregion
}

Просмотреть файл

@ -0,0 +1,805 @@
using AST = Pchp.Syntax.AST;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Pchp.Syntax;
using Pchp.CodeAnalysis.Utilities;
namespace Pchp.CodeAnalysis.FlowAnalysis
{
/// <summary>
/// Context of <see cref="TypeRefMask"/> and <see cref="ITypeRef"/> instances.
/// Contains additional information for routine context like current namespace, current type context etc.
/// </summary>
public sealed class TypeRefContext
{
#region Fields & Properties
/// <summary>
/// Bit masks initialized when such type is added to the context.
/// Its bits corresponds to <see cref="_typeRefs"/> indices.
/// </summary>
private ulong _isObjectMask, _isArrayMask, _isIntMask, _isDoubleMask, _isBoolMask, _isStringMask, _isPrimitiveMask, _isLambdaMask, _isResourceMask, _nullTypeMask, _AnObjectMask;
private ulong IsNumberMask { get { return _isIntMask | _isDoubleMask; } }
private ulong IsNullableMask { get { return _isObjectMask | _isArrayMask | _isStringMask | _isLambdaMask | _nullTypeMask | _isResourceMask; } }
/// <summary>
/// Allowed types for array key.
/// </summary>
private ulong IsArrayKeyMask { get { return _isStringMask | _isBoolMask | _isIntMask | _isDoubleMask | _isResourceMask; } }
/// <summary>
/// List of types occuring in the context.
/// </summary>
private readonly List<ITypeRef>/*!*/_typeRefs;
/// <summary>
/// Name of <c>parent</c> class type if any.
/// </summary>
private readonly QualifiedName? _parentTypeCtx;
/// <summary>
/// contains type mask of <c>self</c> type with <c>includesSubclasses</c> flag set whether type is not final.
/// </summary>
private readonly TypeRefMask _typeCtxMask;
/// <summary>
/// When resolved, contains type mask of <c>parent</c> type.
/// </summary>
private TypeRefMask _parentTypeMask;
/// <summary>
/// When resolved, contains type mask of <c>static</c> type.
/// </summary>
private TypeRefMask _staticTypeMask;
/// <summary>
/// Current naming context. Used for resolving PHPDoc type names, current namespace name etc. Can be <c>null</c>.
/// </summary>
public NamingContext Naming { get { return _namingCtx; } }
private readonly NamingContext _namingCtx;
/// <summary>
/// Current source unit. Used for resolving current file name, elements position etc. Can be <c>null</c>.
/// </summary>
public SourceUnit SourceUnit { get { return _sourceUnit; } }
private readonly SourceUnit _sourceUnit;
#endregion
#region Primitive Types
internal static readonly PrimitiveTypeRef/*!*/NullTypeRef = new PrimitiveTypeRef(PhpDataType.Null);
internal static readonly PrimitiveTypeRef/*!*/BoolTypeRef = new PrimitiveTypeRef(PhpDataType.Boolean);
internal static readonly PrimitiveTypeRef/*!*/IntTypeRef = new PrimitiveTypeRef(PhpDataType.Long);
internal static readonly PrimitiveTypeRef/*!*/DoubleTypeRef = new PrimitiveTypeRef(PhpDataType.Double);
internal static readonly PrimitiveTypeRef/*!*/StringTypeRef = new PrimitiveTypeRef(PhpDataType.String);
internal static readonly PrimitiveTypeRef/*!*/ArrayTypeRef = new PrimitiveTypeRef(PhpDataType.Array);
internal static readonly PrimitiveTypeRef/*!*/ObjectTypeRef = new PrimitiveTypeRef(PhpDataType.Object);
internal static readonly PrimitiveTypeRef/*!*/ResourceTypeRef = new PrimitiveTypeRef(PhpDataType.Resource);
internal static readonly PrimitiveTypeRef/*!*/CallableTypeRef = new PrimitiveTypeRef(PhpDataType.Callable);
#endregion
#region Initialization
public TypeRefContext(NamingContext naming, SourceUnit sourceUnit, AST.TypeDecl typeCtx, QualifiedName? parentTypeCtx)
{
Debug.Assert(!parentTypeCtx.HasValue || typeCtx != null);
_namingCtx = naming;
_sourceUnit = sourceUnit;
_parentTypeCtx = parentTypeCtx;
_typeRefs = new List<ITypeRef>();
_typeCtxMask = GetTypeCtxMask(typeCtx);
}
/// <summary>
/// Gets type mask corresponding to <c>self</c> with <c>includesSubclasses</c> flag set whether type is not final.
/// </summary>
private TypeRefMask GetTypeCtxMask(AST.TypeDecl typeCtx)
{
if (typeCtx != null)
{
var typeIsFinal = (typeCtx.MemberAttributes & PhpMemberAttributes.Final) != 0;
return GetTypeMask(new ClassTypeRef(NameUtils.MakeQualifiedName(typeCtx)), !typeIsFinal);
}
//
return 0;
}
/// <summary>
/// Explicitly defines late static bind type (type of <c>static</c>).
/// </summary>
/// <param name="staticTypeMask">Type mask of <c>static</c> or <c>void</c> if this information is unknown.</param>
internal void SetLateStaticBindType(TypeRefMask staticTypeMask)
{
_staticTypeMask = staticTypeMask;
}
#endregion
#region AddToContext
/// <summary>
/// Ensures given type is in the context.
/// </summary>
/// <param name="typeRef">Type reference to be in the context.</param>
/// <returns>Index of the type within the context. Can return <c>-1</c> if there is too many types in the context already.</returns>
public int AddToContext(ITypeRef/*!*/typeRef)
{
Contract.ThrowIfNull(typeRef);
var types = _typeRefs;
var index = this.GetTypeIndex(typeRef);
if (index < 0 && this.Types.Count < TypeRefMask.IndicesCount)
index = this.AddToContextNoCheck(typeRef);
//
return index;
}
private int AddToContextNoCheck(ITypeRef/*!*/typeRef)
{
Contract.ThrowIfNull(typeRef);
Debug.Assert(_typeRefs.IndexOf(typeRef) == -1);
int index = _typeRefs.Count;
this.UpdateMasks(typeRef, index);
_typeRefs.Add(typeRef);
//
return index;
}
/// <summary>
/// Updates internal masks for newly added type.
/// </summary>
/// <param name="typeRef">Type.</param>
/// <param name="index">Type index.</param>
private void UpdateMasks(ITypeRef/*!*/typeRef, int index)
{
Debug.Assert(index >= 0 && index < TypeRefMask.IndicesCount);
ulong mask = (ulong)1 << index;
if (typeRef.IsObject) _isObjectMask |= mask;
if (typeRef.IsArray) _isArrayMask |= mask;
if (typeRef.IsLambda) _isLambdaMask |= mask;
if (typeRef.IsPrimitiveType)
{
_isPrimitiveMask |= mask;
switch (typeRef.TypeCode)
{
case PhpDataType.Boolean:
_isBoolMask = mask;
break;
case PhpDataType.Long:
_isIntMask = mask;
break;
case PhpDataType.Double:
_isDoubleMask = mask;
break;
case PhpDataType.String:
_isStringMask = mask;
break;
case PhpDataType.Null:
_nullTypeMask = mask;
break;
case PhpDataType.Resource:
_isResourceMask |= mask;
break;
case PhpDataType.Object:
_AnObjectMask = mask;
break;
}
}
}
/// <summary>
/// Adds properly types from another context.
/// </summary>
/// <param name="other">Another type context which types will be added to this one.</param>
internal void AddToContext(TypeRefContext/*!*/other)
{
Contract.ThrowIfNull(other);
foreach (var typeref in other.Types)
{
AddToContext(typeref.Transfer(other, this));
}
}
/// <summary>
/// Adds properly types from another context matching given mask.
/// </summary>
/// <param name="context">Context of <paramref name="mask"/>.</param>
/// <param name="mask">Type mask representing types in <paramref name="context"/>.</param>
/// <returns>Returns type mask in this context representing <paramref name="mask"/> as <paramref name="context"/>.</returns>
public TypeRefMask AddToContext(TypeRefContext/*!*/context, TypeRefMask mask)
{
Contract.ThrowIfNull(context);
if (mask.IsAnyType || mask.IsVoid || object.ReferenceEquals(this, context))
return mask;
var result = default(TypeRefMask);
var types = context.Types;
var count = Math.Min(types.Count, TypeRefMask.IndicesCount);
for (int i = 0; i < count; i++)
{
if (mask.HasType(i))
{
var index = AddToContext(types[i].Transfer(context, this));
result.AddType(index);
}
}
//
result.IsHint = mask.IsHint;
result.IncludesSubclasses = mask.IncludesSubclasses;
//
return result;
}
#endregion
#region Helper Methods
/// <summary>
/// Gets enumeration of types matching given masks.
/// </summary>
private IList<ITypeRef>/*!!*/GetTypes(TypeRefMask typemask, ulong bitmask)
{
var mask = typemask.Mask & bitmask & ~TypeRefMask.FlagsMask;
if (mask == (ulong)0 || typemask.IsAnyType)
return EmptyArray<ITypeRef>.Instance;
var result = new List<ITypeRef>(1);
for (int i = 0; mask != 0; i++, mask = (mask & ~(ulong)1) >> 1)
if ((mask & 1) != 0)
{
result.Add(_typeRefs[i]);
}
return result;
}
private TypeRefMask GetPrimitiveTypeRefMask(PrimitiveTypeRef/*!*/typeref)
{
// primitive type cannot include subclasses
var index = AddToContext(typeref);
return TypeRefMask.CreateFromTypeIndex(index);
}
/// <summary>
/// Does not lookup existing types whether there is typeref already.
/// </summary>
private TypeRefMask GetPrimitiveTypeRefMaskNoCheck(PrimitiveTypeRef/*!*/typeref)
{
if (this.Types.Count < TypeRefMask.IndicesCount)
{
var index = AddToContextNoCheck(typeref);
return TypeRefMask.CreateFromTypeIndex(index);
}
else
{
return TypeRefMask.AnyType;
}
}
/// <summary>
/// Gets type mask for reserved class name (self, parent, static).
/// </summary>
private TypeRefMask GetTypeMaskOfReservedClassName(Name name)
{
if (name == Name.SelfClassName) return GetSelfTypeMask();
if (name == Name.ParentClassName) return GetParentTypeMask();
if (name == Name.StaticClassName) return GetStaticTypeMask();
throw new ArgumentException();
}
#endregion
#region GetTypeMask
/// <summary>
/// Helper method that builds <see cref="TypeRefMask"/> for given type in this context.
/// </summary>
public TypeRefMask GetTypeMask(ITypeRef/*!*/typeref, bool includesSubclasses)
{
var index = AddToContext(typeref);
var mask = TypeRefMask.CreateFromTypeIndex(index);
if (includesSubclasses)
mask.SetIncludesSubclasses();
return mask;
}
/// <summary>
/// Gets type mask corresponding to given qualified name within this context.
/// </summary>
public TypeRefMask GetTypeMask(QualifiedName qname, bool includesSubclasses = true)
{
if (qname.IsReservedClassName)
return GetTypeMaskOfReservedClassName(qname.Name);
return GetTypeMask(new ClassTypeRef(qname), includesSubclasses);
}
/// <summary>
/// Gets type mask corresponding to given TypeRef within this context.
/// </summary>
public TypeRefMask GetTypeMask(AST.TypeRef/*!*/tref, bool includesSubclasses = true)
{
Contract.ThrowIfNull(tref);
var dtype = tref as AST.DirectTypeRef;
if (dtype != null)
{
return GetTypeMask(dtype.ClassName, includesSubclasses);
}
else
{
var itype = tref as AST.IndirectTypeRef;
if (itype != null)
{
return GetTypeMask(itype, includesSubclasses);
}
}
//
return TypeRefMask.AnyType;
}
/// <summary>
/// Gets type mask corresponding to given TypeRef within this context.
/// </summary>
private TypeRefMask GetTypeMask(AST.IndirectTypeRef/*!*/tref, bool includesSubclasses)
{
Contract.ThrowIfNull(tref);
var dvar = tref.ClassNameVar as AST.DirectVarUse;
if (dvar != null && dvar.IsMemberOf == null && dvar.VarName.IsThisVariableName)
return GetThisTypeMask();
//
return TypeRefMask.AnyType;
}
/// <summary>
/// Gets type mask corresponding to the parameter type hint.
/// </summary>
/// <param name="typeHint">Type hint. Can be <c>null</c>, <see cref="GenericQualifiedName"/> or <see cref="PrimitiveTypeName"/>.</param>
/// <returns>Type mask of type hint if it was provided, otherwise AnyType.</returns>
public TypeRefMask GetTypeMaskFromTypeHint(object typeHint)
{
if (typeHint != null)
{
var value = new TypeHintValue(typeHint);
if (value.IsGenericQualifiedName)
{
return GetTypeMask(value.GenericQualifiedName.QualifiedName);
}
if (value.IsPrimitiveType)
{
var pname = value.PrimitiveTypeName.Name;
if (pname == QualifiedName.Callable.Name) return GetPrimitiveTypeRefMask(CallableTypeRef);
if (pname == QualifiedName.Array.Name) return GetArrayTypeMask();
if (pname == QualifiedName.String.Name) return GetStringTypeMask();
if (pname == QualifiedName.Boolean.Name) return GetBooleanTypeMask();
if (pname == QualifiedName.Integer.Name) return GetIntTypeMask();
if (pname == QualifiedName.LongInteger.Name) return GetIntTypeMask();
if (pname == QualifiedName.Double.Name) return GetDoubleTypeMask();
}
}
return TypeRefMask.AnyType;
}
/// <summary>
/// Gets <c>string</c> type for this context.
/// </summary>
public TypeRefMask GetStringTypeMask()
{
if (_isStringMask != 0)
{
return new TypeRefMask(_isStringMask);
}
else
{
return GetPrimitiveTypeRefMaskNoCheck(StringTypeRef);
}
}
/// <summary>
/// Gets <c>int</c> type for this context.
/// </summary>
public TypeRefMask GetIntTypeMask()
{
if (_isIntMask != 0)
{
return new TypeRefMask(_isIntMask);
}
else
{
return GetPrimitiveTypeRefMaskNoCheck(IntTypeRef);
}
}
/// <summary>
/// Gets <c>bool</c> type for this context.
/// </summary>
public TypeRefMask GetBooleanTypeMask()
{
if (_isBoolMask != 0)
{
return new TypeRefMask(_isBoolMask);
}
else
{
return GetPrimitiveTypeRefMaskNoCheck(BoolTypeRef);
}
}
/// <summary>
/// Gets <c>double</c> type for this context.
/// </summary>
public TypeRefMask GetDoubleTypeMask()
{
if (_isDoubleMask != 0)
{
return new TypeRefMask(_isDoubleMask);
}
else
{
return GetPrimitiveTypeRefMaskNoCheck(DoubleTypeRef);
}
}
/// <summary>
/// Gets <c>number</c> (<c>int</c> and <c>double</c>) type for this context.
/// </summary>
public TypeRefMask GetNumberTypeMask()
{
return GetIntTypeMask() | GetDoubleTypeMask();
}
/// <summary>
/// Gets <c>null</c> type for this context.
/// </summary>
public TypeRefMask GetNullTypeMask()
{
if (_nullTypeMask != 0)
{
return new TypeRefMask(_nullTypeMask);
}
else
{
return GetPrimitiveTypeRefMaskNoCheck(NullTypeRef);
}
}
/// <summary>
/// Gets type mask of generic <c>array</c> with element of any type.
/// </summary>
public TypeRefMask GetArrayTypeMask()
{
return GetPrimitiveTypeRefMask(ArrayTypeRef);
}
/// <summary>
/// Gets type mask of <c>array</c> with elements of given type.
/// </summary>
public TypeRefMask GetArrayTypeMask(TypeRefMask elementType)
{
TypeRefMask result;
if (elementType.IsAnyType)
{
result = GetArrayTypeMask(); // generic array
}
else if (elementType.IsVoid)
{
result = GetTypeMask(new ArrayTypeRef(null, 0), false); // empty array
}
else if (elementType.IsSingleType)
{
result = GetTypeMask(new ArrayTypeRef(null, elementType), false);
}
else
{
result = 0;
if ((elementType & _isArrayMask) != 0) // array elements contain another arrays
{
// simplify this to generic arrays
elementType &= ~_isArrayMask;
elementType |= GetArrayTypeMask();
}
// construct array type mask from array types with single element type
// go through all array types
var mask = elementType & ~(ulong)TypeRefMask.FlagsMask;
for (int i = 0; mask != 0; i++, mask = (mask & ~(ulong)1) >> 1)
if ((mask & 1) != 0) // _typeRefs[i].IsArray
{
result |= GetTypeMask(new ArrayTypeRef(null, (ulong)1 << i), false);
}
}
//
result.IsHint = elementType.IsHint;
return result;
}
/// <summary>
/// Gets <c>self</c> type for this context.
/// </summary>
public TypeRefMask GetSelfTypeMask()
{
var result = _typeCtxMask;
result.IncludesSubclasses = false;
return result;
}
/// <summary>
/// Gets type of <c>$this</c> in current context.
/// </summary>
public TypeRefMask GetThisTypeMask()
{
return _typeCtxMask; // the same as $this
}
/// <summary>
/// Gets <c>parent</c> type for this context.
/// </summary>
public TypeRefMask GetParentTypeMask()
{
if (_parentTypeMask == 0 && _parentTypeCtx.HasValue)
_parentTypeMask = GetTypeMask(new ClassTypeRef(_parentTypeCtx.Value), false);
return _parentTypeMask;
}
/// <summary>
/// Gets <c>static</c> type for this context.
/// </summary>
public TypeRefMask GetStaticTypeMask()
{
if (_staticTypeMask == 0)
{
var mask = _typeCtxMask;
if (mask != 0)
mask.IncludesSubclasses = true;
_staticTypeMask = mask;
}
return _staticTypeMask;
}
/// <summary>
/// Gets mask representing only array types in given mask.
/// (Only bits corresponding to an array type will be set).
/// </summary>
public TypeRefMask GetArraysFromMask(TypeRefMask mask)
{
if (mask.IsAnyType) return 0;
return mask & _isArrayMask;
}
/// <summary>
/// Gets mask representing only object types in given mask.
/// (Only bits corresponding to an object type will be set).
/// </summary>
public TypeRefMask GetObjectsFromMask(TypeRefMask mask)
{
if (mask.IsAnyType) return 0;
return mask & _isObjectMask;
}
#endregion
#region Public Methods
/// <summary>
/// Gets enumeration of all types in the context.
/// </summary>
public IList<ITypeRef>/*!*/Types { get { return _typeRefs; } }
/// <summary>
/// Gets types referenced by given type mask.
/// </summary>
public IList<ITypeRef>/*!*/GetTypes(TypeRefMask mask)
{
return GetTypes(mask, TypeRefMask.AnyTypeMask);
}
/// <summary>
/// Gets types of type <c>object</c> (classes, interfaces, traits) referenced by given type mask.
/// </summary>
public IList<ITypeRef>/*!*/GetObjectTypes(TypeRefMask mask)
{
if (mask.IsAnyType)
return EmptyArray<ITypeRef>.Instance;
return GetTypes(mask, _isObjectMask);
}
/// <summary>
/// Gets string representation of types contained in given type mask.
/// </summary>
public string ToString(TypeRefMask mask)
{
if (!mask.IsVoid)
{
if (mask.IsAnyType)
return TypeRefMask.MixedTypeName;
//
var types = new List<string>(1);
// handle arrays separately
var arrmask = mask & _isArrayMask;
if (arrmask != 0)
{
mask &= ~_isArrayMask;
ITypeRef elementtype = null;
var elementmask = GetElementType(arrmask);
if (elementmask.IsSingleType)
elementtype = GetTypes(elementmask).FirstOrDefault();
if (elementtype != null)
types.Add(elementtype.QualifiedName.ToString() + "[]");
else
types.Add(ArrayTypeRef.QualifiedName.ToString());
}
//// int|double => number
//var isNumber = (_isIntMask != 0 && _isDoubleMask != 0 && (mask & IsNumberMask) == IsNumberMask);
//if (isNumber)
// mask &= ~IsNumberMask;
//
types.AddRange(GetTypes(mask).Select(t => t.QualifiedName.ToString()));
//if (isNumber)
// types.Add("number");
//
if (types.Count != 0)
{
types.Sort();
return string.Join(PHPDocBlock.TypeVarDescTag.TypeNamesSeparator.ToString(), types.Distinct());
}
}
return TypeRefMask.VoidTypeName;
}
/// <summary>
/// Gets index of the given type within the context. Returns <c>-1</c> if such type is not present.
/// </summary>
public int GetTypeIndex(ITypeRef/*!*/typeref) { return _typeRefs.IndexOf(typeref); }
/// <summary>
/// Gets value indicating whether given type mask represents a number.
/// </summary>
public bool IsNumber(TypeRefMask mask) { return (mask.Mask & IsNumberMask) != 0; }
/// <summary>
/// Gets value indicating whether given type mask represents a string type.
/// </summary>
public bool IsString(TypeRefMask mask) { return (mask.Mask & _isStringMask) != 0; }
/// <summary>
/// Gets value indicating whether given type mask represents a boolean.
/// </summary>
public bool IsBoolean(TypeRefMask mask) { return (mask.Mask & _isBoolMask) != 0; }
/// <summary>
/// Gets value indicating whether given type mask represents an integer type.
/// </summary>
public bool IsInteger(TypeRefMask mask) { return (mask.Mask & _isIntMask) != 0; }
/// <summary>
/// Gets value indicating whether given type mask represents a double type.
/// </summary>
public bool IsDouble(TypeRefMask mask) { return (mask.Mask & _isDoubleMask) != 0; }
/// <summary>
/// Gets value indicating whether given type mask represents an object.
/// </summary>
public bool IsObject(TypeRefMask mask) { return (mask.Mask & _isObjectMask) != 0; }
/// <summary>
/// Gets value indicating whether given type mask represents an array.
/// </summary>
public bool IsArray(TypeRefMask mask) { return (mask.Mask & _isArrayMask) != 0; }
/// <summary>
/// Gets value indicating whether given type mask represents a lambda function or <c>callable</c> primitive type.
/// </summary>
public bool IsLambda(TypeRefMask mask) { return (mask.Mask & _isLambdaMask) != 0; }
/// <summary>
/// Gets value indicating whether given type mask represents a resource.
/// </summary>
public bool IsResource(TypeRefMask mask) { return (mask.Mask & _isResourceMask) != 0; }
/// <summary>
/// Gets value indicating whether given type mask represents a primitive type.
/// </summary>
public bool IsPrimitiveType(TypeRefMask mask) { return (mask.Mask & _isPrimitiveMask) != 0; }
/// <summary>
/// Gets value indicating whether the type mask represents <c>null</c>.
/// </summary>
public bool IsNull(TypeRefMask mask)
{
return (mask.Mask & _nullTypeMask) != 0 && !mask.IsAnyType;
}
/// <summary>
/// Gets value indicating whether given type can be <c>null</c>.
/// </summary>
public bool IsNullable(TypeRefMask mask) { return (mask.Mask & IsNullableMask) != 0; }
public bool IsArrayKey(TypeRefMask mask) { return (mask.Mask & IsArrayKeyMask) != 0; } // TODO: type can be of type object with method __toString() ?
/// <summary>
/// Gets value indicating whether given mask represents an unspecified <c>object</c> instance.
/// </summary>
public bool IsAnObject(TypeRefMask mask)
{
return (mask.Mask & _AnObjectMask) != 0;
}
/// <summary>
/// In case of array type, gets its possible element types.
/// </summary>
public TypeRefMask GetElementType(TypeRefMask mask)
{
TypeRefMask result;
if (IsArray(mask) && !mask.IsAnyType)
{
result = default(TypeRefMask); // uninitalized
var arrtypes = GetTypes(mask, _isArrayMask);
foreach (var t in arrtypes)
{
Debug.Assert(t.IsArray);
result |= t.ElementType;
}
if (result.IsVoid)
{
// empty array
//result = TypeRefMask.AnyType;
}
}
else
{
result = TypeRefMask.AnyType;
}
return result;
}
#endregion
}
}

Просмотреть файл

@ -0,0 +1,334 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
namespace Pchp.CodeAnalysis.FlowAnalysis
{
/// <summary>
/// Represents type mask for <see cref="TypeRefContext"/>.
/// </summary>
public struct TypeRefMask : IEquatable<TypeRefMask>, IEquatable<ulong>
{
#region Constants
public const string MixedTypeName = "mixed";
public const string VoidTypeName = "void";
private const int BitsCount = sizeof(ulong) * 8;
/// <summary>
/// Gets maximum number of types that can be handled by <see cref="TypeRefMask"/>.
/// </summary>
public const int IndicesCount = BitsCount - 2; // BitsCount - bits_reserved_for_flags
/// <summary>
/// Bit mask of all flags supported by the <see cref="TypeRefMask"/>.
/// </summary>
public const ulong FlagsMask = (ulong)MaskFlags.Mask;
/// <summary>
/// Mask of any type.
/// </summary>
public const ulong AnyTypeMask = ~(ulong)0;
#endregion
#region enum MaskFlags
/// <summary>
/// Additional type mask flags.
/// </summary>
[Flags]
private enum MaskFlags : ulong
{
IsHint = (ulong)1 << (BitsCount - 1),
IncludesSubclasses = (ulong)1 << (BitsCount - 2),
/// <summary>
/// Mask of all flags.
/// </summary>
Mask = IsHint | IncludesSubclasses
}
#endregion
#region Fields & Properties
/// <summary>
/// Each bit corresponds to a type within its <see cref="TypeRefContext"/>.
/// </summary>
public ulong Mask { get { return _mask; } }
private ulong _mask;
/// <summary>
/// Gets value indicating whether the type represents an any type.
/// </summary>
public bool IsAnyType { get { return _mask == AnyTypeMask; } }
/// <summary>
/// Gets value indicating whether the type information is not initialized.
/// </summary>
/// <remarks>Also represents <c>void</c> type.</remarks>
public bool IsUninitialized { get { return _mask == (ulong)0; } }
/// <summary>
/// Gets value indicating whether the type represents <c>void</c>.
/// </summary>
public bool IsVoid { get { return (_mask & ~(ulong)(MaskFlags.Mask)) == 0; } }
/// <summary>
/// Gets or sets value indicating whether the type represents a hint.
/// </summary>
public bool IsHint
{
get { return (_mask & (ulong)MaskFlags.IsHint) != 0; }
set
{
if (value) SetIsHint();
else RemoveIsHint();
}
}
/// <summary>
/// Gets value indicating whether given type mask represents a type including its subclasses.
/// </summary>
public bool IncludesSubclasses
{
get { return (_mask & (ulong)MaskFlags.IncludesSubclasses) != 0; }
set
{
if (value) SetIncludesSubclasses();
else RemoveIncludesSubclasses();
}
}
/// <summary>
/// Gets value indicating whether the mask represents just a single type reference.
/// </summary>
public bool IsSingleType
{
get
{
var mask = _mask & ~FlagsMask; // remove flags
return mask != 0 && (mask & (mask - 1)) == 0;
}
}
/// <summary>
/// Gets type mask representing any type.
/// </summary>
public static TypeRefMask AnyType { get { return new TypeRefMask(AnyTypeMask); } }
#endregion
#region Construction
public TypeRefMask(ulong mask)
{
_mask = mask;
}
/// <summary>
/// Creates type mask corresponding to a single type of given index.
/// </summary>
public static TypeRefMask CreateFromTypeIndex(int index)
{
var typemask = default(TypeRefMask);
typemask.AddType(index);
//
return typemask;
}
#endregion
/// <summary>
/// Gets value indicating whether this type mask represents the type with given index.
/// </summary>
public bool HasType(int index)
{
return ((_mask & ((ulong)1 << index)) != 0 && index < IndicesCount && index >= 0);
}
/// <summary>
/// Adds type with given index to the mask.
/// </summary>
public void AddType(int index)
{
if (index >= 0 && index < IndicesCount)
{
_mask |= ((ulong)1 << index);
}
else
{
_mask = AnyTypeMask; // AnyType
}
}
/// <summary>
/// Marks this type as a hint.
/// </summary>
public void SetIsHint()
{
_mask |= (ulong)MaskFlags.IsHint;
}
/// <summary>
/// Marks this type as not a hint.
/// </summary>
public void RemoveIsHint()
{
if (!this.IsAnyType)
{
_mask &= ~(ulong)MaskFlags.IsHint;
}
}
/// <summary>
/// Marks this type as a hint.
/// </summary>
public void SetIncludesSubclasses()
{
_mask |= (ulong)MaskFlags.IncludesSubclasses;
}
/// <summary>
/// Marks this type as not a hint.
/// </summary>
internal void RemoveIncludesSubclasses()
{
if (!this.IsAnyType)
{
_mask &= ~(ulong)MaskFlags.IncludesSubclasses;
}
}
/// <summary>
/// Gets or sets value indicating whether the mask represents a type at given index.
/// </summary>
/// <remarks>Index higher than or equal to <see cref="IndicesCount"/> are ignored.</remarks>
public bool this[int index]
{
get
{
return HasType(index);
}
set
{
Debug.Assert(index >= 0);
if (value) AddType(index);
else if (index < IndicesCount && !this.IsAnyType) _mask &= ~((ulong)1 << index);
}
}
#region Object Members
public override int GetHashCode()
{
return _mask.GetHashCode();
}
public override bool Equals(object obj)
{
return obj is TypeRefMask && base.Equals((TypeRefMask)obj);
}
public override string ToString()
{
if (IsAnyType) return MixedTypeName;
string value = null;
if (IsVoid)
{
value = VoidTypeName;
}
else
{
ulong mask = this.Mask & ~(ulong)FlagsMask;
for (int i = 0; mask != 0; i++, mask = (mask & ~(ulong)1) >> 1)
{
if ((mask & 1) != 0)
{
if (value != null) value += ",";
value += (i).ToString();
}
}
}
//
if (IsHint)
{
value += " (hint)";
}
//
return value;
}
#endregion
#region IEquatable<TypeRefMask> Members
public bool Equals(TypeRefMask other)
{
return Equals(other.Mask);
}
#endregion
#region Operators
public static bool operator ==(TypeRefMask a, TypeRefMask b)
{
return a.Mask == b.Mask;
}
public static bool operator !=(TypeRefMask a, TypeRefMask b)
{
return a.Mask != b.Mask;
}
public static TypeRefMask operator +(TypeRefMask a, TypeRefMask b)
{
return new TypeRefMask(a.Mask | b.Mask);
}
public static TypeRefMask operator |(TypeRefMask a, TypeRefMask b)
{
return new TypeRefMask(a.Mask | b.Mask);
}
public static TypeRefMask operator |(TypeRefMask a, ulong b)
{
return new TypeRefMask(a.Mask | b);
}
public static TypeRefMask Or (TypeRefMask a, TypeRefMask b)
{
return a | b;
}
public static implicit operator ulong(TypeRefMask type)
{
return type.Mask;
}
public static implicit operator TypeRefMask(ulong mask)
{
return new TypeRefMask(mask);
}
#endregion
#region IEquatable<ulong> Members
public bool Equals(ulong other)
{
return _mask == other;
}
#endregion
}
}

Просмотреть файл

@ -0,0 +1,22 @@
using Pchp.CodeAnalysis.Semantics;
using Pchp.CodeAnalysis.Utilities;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pchp.CodeAnalysis.FlowAnalysis
{
/// <summary>
/// Queue of work items to do.
/// </summary>
internal class Worklist
{
/// <summary>
/// List of blocks to be processed.
/// </summary>
readonly DistinctQueue<BoundBlock> _queue = new DistinctQueue<BoundBlock>();
}
}

Просмотреть файл

@ -50,6 +50,13 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Compilation\SourceCompiler.cs" />
<Compile Include="FlowAnalysis\TypeRef\CallInfo.cs" />
<Compile Include="FlowAnalysis\TypeRef\ITypeRef.cs" />
<Compile Include="FlowAnalysis\TypeRef\PhpDataType.cs" />
<Compile Include="FlowAnalysis\TypeRef\TypeRef.cs" />
<Compile Include="FlowAnalysis\TypeRef\TypeRefContext.cs" />
<Compile Include="FlowAnalysis\TypeRef\TypeRefMask.cs" />
<Compile Include="FlowAnalysis\Worklist.cs" />
<Compile Include="Semantics\BoundMethodBody.cs" />
<Compile Include="Semantics\Expressions.cs" />
<Compile Include="Semantics\SemanticsBinder.cs" />
@ -61,6 +68,7 @@
<Compile Include="CommandLine\PhpParseOptions.cs" />
<Compile Include="Compilation\PhpCompilation.cs" />
<Compile Include="Compilation\PhpCompilationOptions.cs" />
<Compile Include="FlowAnalysis\TypeRef\TypeHelpers.cs" />
<Compile Include="Symbols\ReferenceManager.cs" />
<Compile Include="Constants.cs" />
<Compile Include="Emitter\Model\AssemblyReference.cs" />
@ -105,14 +113,15 @@
<Compile Include="Semantics\ISemanticModel.cs" />
<Compile Include="Symbols\TypeSymbol.cs" />
<Compile Include="Utilities\Contract.cs" />
<Compile Include="Utilities\DistinctQueue.cs" />
<Compile Include="Utilities\EnumeratorExtension.cs" />
<Compile Include="Utilities\NameUtils.cs" />
<Compile Include="Utilities\TypeHintValue.cs" />
</ItemGroup>
<ItemGroup>
<None Include="project.json" />
</ItemGroup>
<ItemGroup>
<Folder Include="FlowAnalysis\" />
<Folder Include="Symbols\Tables\" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -21,12 +21,18 @@ namespace Pchp.CodeAnalysis.Semantics
/// Gets type symbol by its name in current context.
/// Can be <c>null</c> if type cannot be found.
/// </summary>
INamedTypeSymbol GetType(QualifiedName name);
INamedTypeSymbol GetClass(QualifiedName name);
/// <summary>
/// Get global function symbol by its name in current context.
/// Can be <c>null</c> if function cannot be found.
/// </summary>
IMethodSymbol GetFunction(QualifiedName name);
/// <summary>
/// Gets value determining whether <paramref name="qname"/> type can be assigned from <paramref name="from"/>.
/// </summary>
/// <remarks>Gets <c>true</c>, if <paramref name="qname"/> is equal to or is a base type of <paramref name="from"/>.</remarks>
bool IsAssignableFrom(QualifiedName qname, INamedTypeSymbol from);
}
}

Просмотреть файл

@ -166,15 +166,16 @@ namespace Pchp.CodeAnalysis.Symbols
// If this is a nested type generic parameters in metadata include generic parameters of the outer types.
int firstIndex = _genericParameterHandles.Count - _arity;
var ownedParams = new ITypeParameterSymbol[_arity];
for (int i = 0; i < ownedParams.Length; i++)
{
throw new NotImplementedException();
//ownedParams[i] = new PETypeParameterSymbol(moduleSymbol, this, (ushort)i, _genericParameterHandles[firstIndex + i]);
}
throw new NotImplementedException();
ImmutableInterlocked.InterlockedInitialize(ref _lazyTypeParameters,
ImmutableArray.Create<ITypeParameterSymbol>(ownedParams));
//var ownedParams = new ITypeParameterSymbol[_arity];
//for (int i = 0; i < ownedParams.Length; i++)
//{
// ownedParams[i] = new PETypeParameterSymbol(moduleSymbol, this, (ushort)i, _genericParameterHandles[firstIndex + i]);
//}
//ImmutableInterlocked.InterlockedInitialize(ref _lazyTypeParameters,
// ImmutableArray.Create<ITypeParameterSymbol>(ownedParams));
}
}

Просмотреть файл

@ -68,7 +68,7 @@ namespace Pchp.CodeAnalysis.Symbols
Contract.ThrowIfNull(trees);
var visitor = new PopulatorVisitor(compilation, this);
trees.Foreach(visitor.VisitSourceUnit);
trees.ForEach(visitor.VisitSourceUnit);
}
public IMethodSymbol GetFunction(QualifiedName name) => _functions.TryGetOrDefault(name);

Просмотреть файл

@ -0,0 +1,70 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pchp.CodeAnalysis.Utilities
{
/// <summary>
/// Represents queue where items are enqueued just once and queue can be accessed in parralel.
/// </summary>
internal sealed class DistinctQueue<T>
{
readonly object _syncRoot = new object();
readonly HashSet<T> _set = new HashSet<T>();
readonly Queue<T> _queue = new Queue<T>();
/// <summary>
/// Count of items in the queue.
/// </summary>
public int Count
{
get { return _queue.Count; }
}
/// <summary>
/// Enqueues item into the queue.
/// </summary>
public bool Enqueue(T value)
{
lock (_syncRoot)
{
if (_set.Add(value))
{
_queue.Enqueue(value);
return true;
}
else
{
return false;
}
}
}
/// <summary>
/// Dequeues item from the queue.
/// </summary>
public T TryDequeue()
{
T value;
lock (_syncRoot)
{
if (_queue.Count != 0)
{
value = _queue.Dequeue();
_set.Remove(value);
}
else
{
value = default(T);
}
}
return value;
}
}
}

Просмотреть файл

@ -50,7 +50,7 @@ namespace Pchp.CodeAnalysis
/// <summary>
/// Calls given action for each element in given enumerable.
/// </summary>
public static void Foreach<T>(this IEnumerable<T>/*!*/enumerable, Action<T>/*!*/func)
public static void ForEach<T>(this IEnumerable<T>/*!*/enumerable, Action<T>/*!*/func)
{
Contract.ThrowIfNull(enumerable);
Contract.ThrowIfNull(func);

Просмотреть файл

@ -62,5 +62,31 @@ namespace Pchp.CodeAnalysis
return true;
}
/// <summary>
/// Gets value indicating whether given qualified name was not set.
/// </summary>
public static bool IsEmpty(this QualifiedName qname)
{
return qname.IsSimpleName && string.IsNullOrEmpty(qname.Name.Value);
}
/// <summary>
/// Special PHP type and function names.
/// </summary>
public struct SpecialNames
{
public static QualifiedName ArrayAccess { get { return new QualifiedName(new Name("ArrayAccess")); } }
public static QualifiedName Iterator { get { return new QualifiedName(new Name("Iterator")); } }
public static QualifiedName Traversable { get { return new QualifiedName(new Name("Traversable")); } }
public static QualifiedName Closure { get { return new QualifiedName(new Name("Closure")); } }
public static QualifiedName Exception { get { return new QualifiedName(new Name("Exception")); } }
public static Name offsetGet { get { return new Name("offsetGet"); } }
public static Name offsetSet { get { return new Name("offsetSet"); } }
public static Name current { get { return new Name("current"); } }
public static Name __invoke { get { return new Name("__invoke"); } }
public static Name __toString { get { return new Name("__toString"); } }
}
}
}

Просмотреть файл

@ -0,0 +1,87 @@
using Pchp.Syntax;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pchp.CodeAnalysis.Utilities
{
/// <summary>
/// Wraps type hint <see cref="object"/> provided by parser and provides properties to access real pimitive type or generic qualified name.
/// </summary>
public struct TypeHintValue
{
/// <summary>
/// Hint object.
/// </summary>
private readonly object _obj;
/// <summary>
/// Gets value indicating whether the hint does not define any type.
/// </summary>
public bool IsEmpty { get { return object.ReferenceEquals(_obj, null); } }
/// <summary>
/// Gets value indicating whether the value represents a primitive type.
/// </summary>
public bool IsPrimitiveType { get { return _obj is PrimitiveTypeName; } }
/// <summary>
/// Gets value indicating whether the value represents a class type.
/// </summary>
public bool IsGenericQualifiedName { get { return _obj is GenericQualifiedName; } }
/// <summary>
/// Gets value indicating whether the value represents a class type.
/// </summary>
public bool IsQualifiedName { get { return IsGenericQualifiedName; } }
public PrimitiveTypeName PrimitiveTypeName { get { return (PrimitiveTypeName)_obj; } }
public GenericQualifiedName GenericQualifiedName { get { return (GenericQualifiedName)_obj; } }
public QualifiedName QualifiedName { get { return this.GenericQualifiedName.QualifiedName; } }
/// <summary>
/// Gets name of the type or <c>null</c>.
/// </summary>
public override string ToString()
{
if (!this.IsEmpty)
{
if (this.IsPrimitiveType) return this.PrimitiveTypeName.Name.Value;
if (this.IsQualifiedName) return this.QualifiedName.ToString();
}
return null;
}
#region construction
/// <summary>
/// Wraps type hint object.
/// </summary>
/// <param name="hint">Boxed primitive type or generic qualified type.</param>
public TypeHintValue(object hint)
{
Debug.Assert(hint == null || hint is PrimitiveTypeName || hint is GenericQualifiedName);
_obj = hint;
}
public TypeHintValue(PrimitiveTypeName hint)
: this((object)hint)
{ }
public TypeHintValue(GenericQualifiedName hint)
: this((object)hint)
{ }
public TypeHintValue(QualifiedName hint)
: this(new GenericQualifiedName(hint))
{ }
#endregion
}
}