private void AfterCall(Guid id, RelinqScriptExpression e, TypeInferenceCache cache) { var startedEntry = History.Single(entry2 => entry2.Id == id); var finishedEntry = new TypeInferenceHistoryEntry(startedEntry); finishedEntry.InferredType = cache.ContainsKey(e) ? cache[e] : null; if (e.IsCall()) { RelinqScriptExpression invtarget = null; if (e is InvokeExpression) { invtarget = e.Children.ElementAt(0); } else if (e is IndexerExpression || e is OperatorExpression) { invtarget = e; } if (invtarget != null && cache.Invocations.ContainsKey(invtarget)) { finishedEntry.InferredInvocation = cache.Invocations[invtarget]; } } History.Add(finishedEntry); if (History.Count % 20000 == 0) DumpHistory(); }
public void InferTypes(RelinqScriptExpression e, TypeInferenceCache cache, Action<RelinqScriptExpression, TypeInferenceCache> impl) { Action<RelinqScriptExpression, TypeInferenceCache> wrappedImpl = (e1, cache1) => { #if TRACE var id = BeforeCall(e, cache); try { impl(e, cache); } finally { AfterCall(id, e, cache); } #else impl(e, cache); #endif }; if (Cache == null) { wrappedImpl(e, cache); } else { Cache.RunThroughCache(e, cache, wrappedImpl); } }
private static Expression EnsureBoolean(this Expression e, TypeInferenceCache types) { var t = types[e]; if (t == typeof(bool)) { return(e.CurrentTransform()); } else { if (t.IsValueType) { t.IsInteger().AssertTrue(); var @const = e as Const; if (@const != null) { var value = @const.Value.AssertCast <int>(); (value == 0 || value == 1).AssertTrue(); return(new Const(value == 1)); } return(Operator.NotEqual(e.CurrentTransform(), new Const(0))); } else { return(Operator.NotEqual(e.CurrentTransform(), new Const(null, t))); } } }
public TypeInferenceContext(RelinqScriptExpression root, TypeInferenceCache inferences, IntegrationContext integration) { Root = root; // ctx.Root might only be one of the following: // * Invoke expression (for both cases of compile-time and late-bound invocation) // * Indexer or Operator expression // * Root of an entire AST if (Root.Parent != null && !Root.IsCall()) { throw new NotSupportedException(root.ToString()); } Inferences = inferences; Integration = integration; }
private Guid BeforeCall(RelinqScriptExpression e, TypeInferenceCache cache) { var startedEntry = new TypeInferenceHistoryEntry{Expression = e}; startedEntry.InferredType = cache.ContainsKey(e) ? cache[e] : null; History.Add(startedEntry); Func<LambdaExpression, String, RelinqScriptType> argType = (lambda, arg) => { var argIndex = Array.IndexOf(lambda.Args.ToArray(), arg); var lambdaType = ((Lambda)cache[lambda]).Type.GetFunctionSignature(); return lambdaType.GetParameters()[argIndex].ParameterType; }; e.GetClosure().ForEach(kvp => startedEntry.Closure.Add(kvp.Key, cache.ContainsKey(kvp.Value) ? argType(kvp.Value, kvp.Key) : null)); return startedEntry.Id; }
public static IEnumerable<RelinqScriptType> ToXArgs( this RelinqScriptExpression root, TypeInferenceCache inf) { // cba to duplicate validating logics here // see todo in CSharpExpressionTreeBuilder if (!root.IsCall()) { throw new NotSupportedException(String.Format( "X-args can only be acquired for call-type nodes. " + "Target node '{0}' is unsuitable for this purpose.", root)); } else { if (root is InvokeExpression) { var ie = (InvokeExpression)root; // head is either an irrelevant type (if its a delegate invocation) // or a source of the method group (if its a normal invocation) yield return inf[ie.Target] is ClrType ? new This() : inf[((MemberAccessExpression)ie.Target).Target]; foreach(var arg in ie.Args) yield return inf[arg]; // body + tail } else if (root is IndexerExpression) { var ie = (IndexerExpression)root; yield return inf[ie.Target]; // head is not null, since indexers are instance methods foreach (var operand in ie.Operands) yield return inf[operand]; // body + tail } else if (root is OperatorExpression) { var ie = (OperatorExpression)root; yield return null; // head is null, since the call is static foreach (var operand in ie.Operands) yield return inf[operand]; // body + tail } else { throw new NotSupportedException(String.Format( "Something was overlooked: there's no way a call-type node " + "can be the following: '{0}'.", root)); } } }
private void InferIndexer(IndexerExpression ie, TypeInferenceCache cache) { InferTypes(ie.Target, cache); var typeofTarget = cache[ie.Target]; if (typeofTarget is ClrType) { var preview = cache.Clone(); ie.Operands.ForEach(operand => InferTypes(operand, preview)); var types = ie.Operands.Select(operand => preview[operand]); if (types.Any(type => type is Variant)) { cache.Add(ie, new Variant()); cache.Upgrade(preview); } else { var alts = typeofTarget.LookupIndexers(); if (alts == null) { throw new NoSuchIndexerException(Root, ie, typeofTarget); } InferMethodGroup(alts, ie, cache); } } else if (typeofTarget is Variant) { cache.Add(ie, new Variant()); } else { throw new NoSuchIndexerException(Root, ie, typeofTarget); } }
private void InferInvoke(InvokeExpression ie, TypeInferenceCache cache) { InferTypes(ie.Target, cache); var typeofTarget = cache[ie.Target]; if (typeofTarget is ClrType) { var preview = cache.Clone(); ie.Args.ForEach(arg => InferTypes(arg, preview)); var types = ie.Args.Select(arg => preview[arg]); if (types.Any(type => type is Variant)) { cache.Add(ie, new Variant()); cache.Upgrade(preview); } else { var clrType = ((ClrType)typeofTarget).Type; if (clrType.IsDelegate()) { var sig = clrType.GetFunctionSignature(); InferMethodGroup(new MethodGroup(sig.AsArray(), clrType.Name), ie, cache); } else { throw new CannotBeInvokedException(Root, ie, typeofTarget); } } } else if (typeofTarget is MethodGroup) { InferMethodGroup((MethodGroup)typeofTarget, ie, cache); } else if (typeofTarget is Variant) { cache.Add(ie, new Variant()); } else { throw new CannotBeInvokedException(Root, ie, typeofTarget); } }
private void InferMemberAccess(MemberAccessExpression mae, TypeInferenceCache cache) { InferTypes(mae.Target, cache); var typeofTarget = cache[mae.Target]; if (typeofTarget is ClrType) { // nb: the member access also covers method group lookup // i.e. object.Method(arg1, arg2) is in fact resolved in two steps: // 1) object.Method -> resolves to all instance or extension methods // (on this step fields and properties are omitted because the expression // is being used in the context of an invocation) // 2) MG(arg1, arg2) -> picks up the best method from the MG that matches arglist var usedInContextOfInvocation = mae.Parent is InvokeExpression && mae.ChildIndex == 0; cache.Add(mae, usedInContextOfInvocation ? typeofTarget.LookupMethodGroup(mae.Name) : typeofTarget.LookupMemberAccess(mae.Name)); if (cache[mae] == null) { throw new NoSuchMemberException(Root, mae, typeofTarget); } } else if (typeofTarget is Variant) { cache.Add(mae, new Variant()); } else { throw new NoSuchMemberException(Root, mae, typeofTarget); } }
private void InferLambda(LambdaExpression le, TypeInferenceCache cache) { // lambda is the only one expression that can have its type set from outside if (!cache.ContainsKey(le)) { var lambdaType = Enumerable.Repeat(typeof(Variant), 1 + le.Args.Count()).ForgeFuncType(); cache.Add(le, new Lambda(le, lambdaType)); } // detect overlapping variables var closure = le.Parent == null ? null : le.Parent.GetClosure(); if (closure != null) { var overlapping = closure.Keys.Intersect(le.Args); if (overlapping.IsNotEmpty()) { throw new RedeclaredVariableException(Root, le, closure); } } // detect overriding keywords foreach (var arg in le.Args) { if (Integration.IsRegisteredJS(arg)) { throw new VariableOverridesKeywordException(Root, le, arg); } } InferTypes(le.Body, cache); }
private void InferTypesImpl(RelinqScriptExpression e, TypeInferenceCache cache) { try { switch (e.NodeType) { case ExpressionType.Keyword: InferKeyword((KeywordExpression)e, cache); break; case ExpressionType.Variable: InferVariable((VariableExpression)e, cache); break; case ExpressionType.Constant: InferConstant((ConstantExpression)e, cache); break; case ExpressionType.New: InferNew((NewExpression)e, cache); break; case ExpressionType.Lambda: InferLambda((LambdaExpression)e, cache); break; case ExpressionType.MemberAccess: InferMemberAccess((MemberAccessExpression)e, cache); break; case ExpressionType.Invoke: InferInvoke((InvokeExpression)e, cache); break; case ExpressionType.Indexer: InferIndexer((IndexerExpression)e, cache); break; case ExpressionType.Operator: InferOperator((OperatorExpression)e, cache); break; case ExpressionType.Conditional: InferConditional((ConditionalExpression)e, cache); break; default: throw new TypeInferenceException( JSToCSharpExceptionType.UnexpectedNodeType, Root, e); } } catch (TypeInferenceException) { throw; } catch (Exception ex) { throw new TypeInferenceException( JSToCSharpExceptionType.Unexpected, Root, e, ex); } }
private void InferConditional(ConditionalExpression ce, TypeInferenceCache cache) { ce.Children.ForEach(child => InferTypes(child, cache)); var typeofTest = cache[ce.Test]; var typeofFalse = cache[ce.IfFalse]; var typeofTrue = cache[ce.IfTrue]; if (typeofTest is Variant || typeofFalse is Variant || typeofTrue is Variant) { cache.Add(ce, new Variant()); } else { if (!(typeofTest is ClrType && typeofTest.HasImplicitCastTo(typeof(bool)))) { throw new InconsistentConditionalExpression( JSToCSharpExceptionType.ConditionalTestInvalidType, Root, ce, typeofTest, typeofTrue, typeofFalse); } var f2t = typeofFalse.HasImplicitCastTo(typeofTrue); var t2f = typeofTrue.HasImplicitCastTo(typeofFalse); if (!f2t && !t2f) { if (typeofFalse is Lambda || typeofTrue is Lambda) { throw new NotImplementedException(String.Format( "Don't know how to infer the '{0} ? {1} : {2}' conditional expression.", typeofTest, typeofTrue, typeofFalse)); } else { throw new InconsistentConditionalExpression( JSToCSharpExceptionType.ConditionalClausesNoCommonTypeWithOnlyOneClauseBeingCast, Root, ce, typeofTest, typeofTrue, typeofFalse); } } else if (f2t && t2f && typeofFalse != typeofTrue) { throw new InconsistentConditionalExpression( JSToCSharpExceptionType.ConditionalClausesAmbiguousCommonType, Root, ce, typeofTest, typeofTrue, typeofFalse); } else { cache.Add(ce, f2t ? typeofTrue : typeofFalse); } } }
private void InferKeyword(KeywordExpression ke, TypeInferenceCache cache) { try { cache.Add(ke, Integration.ProduceCSharp(ke.Name).GetType()); } catch (JSToCSharpIntegrationException jsex) { throw new TypeInferenceException(JSToCSharpExceptionType.Integration, Root, ke, jsex); } }
public CompilationContext(TypeInferenceCache inferences) { Closure = new Dictionary<String, ParameterExpression>(); Types = inferences.AsReadOnly(); Invocations = inferences.Invocations.AsReadOnly(); }
private static Expression EnsureBoolean(this Expression e, TypeInferenceCache types) { var t = types[e]; if (t == typeof(bool)) return e.CurrentTransform(); else { if (t.IsValueType) { t.IsInteger().AssertTrue(); var @const = e as Const; if (@const != null) { var value = @const.Value.AssertCast<int>(); (value == 0 || value == 1).AssertTrue(); return new Const(value == 1); } return Operator.NotEqual(e.CurrentTransform(), new Const(0)); } else { return Operator.NotEqual(e.CurrentTransform(), new Const(null, t)); } } }
private void InferOperator(OperatorExpression oe, TypeInferenceCache cache) { var preview = cache.Clone(); oe.Operands.ForEach(operand => InferTypes(operand, preview)); var types = oe.Operands.Select(operand => preview[operand]); if (types.Any(type => type is Variant)) { cache.Add(oe, new Variant()); cache.Upgrade(preview); } else { var alts = oe.Type.LookupOperators(types.ToArray()); // logical not can also be used to express ones complement // since they both correspond to a single LINQ expression type if (oe.Type == OperatorType.LogicalNot) { var addendum = OperatorType.OnesComplement.LookupOperators(types.ToArray()); if (addendum != null) { var original = alts == null ? new MethodInfo[0] : alts.Alts; alts = new MethodGroup(original.Concat(addendum.Alts), oe.Type.GetOpCode()); } } if (alts == null) { throw new NoSuchOperatorException(Root, oe, types); } InferMethodGroup(alts, oe, cache); } }
private void InferMethodGroup(MethodGroup mg, RelinqScriptExpression root, TypeInferenceCache cache) { // q: should existing but not accessible (security!) methods be included into the resolution? // a: yes and here's why: // scenario 1. Included; when overload resolution binds to an unauthorized method = crash. // scenario 2. Not included, so overload resolution binds to an unexpected method = fail. // lets us the fail fast if the resolution is unnecessary. // inferences made here won't be wasted anyways since they get cached var ctx = new TypeInferenceContext(root, cache, Integration); var preview = new TypeInferenceEngine(ctx); root.CallArgs().ForEach(child => preview.InferTypes(child)); // we cannot pass the preview.Ctx inside since it might have // potentially half-inferred lambdas that won't be able to be reinferred // by MG resolution since ctx is init only. var resolved = mg.Resolve(ctx); cache.Add(root, resolved.Signature.ReturnType); cache.Invocations.Add(root, resolved); cache.Upgrade(resolved.Inferences); }
private void InferVariable(VariableExpression ve, TypeInferenceCache cache) { if (ve.GetClosure().ContainsKey(ve.Name)) { var lambda = ve.GetClosure()[ve.Name]; var funcType = ((Lambda)cache[lambda]).Type; var argIndex = Array.IndexOf(lambda.Args.ToArray(), ve.Name); cache.Add(ve, funcType.GetFunctionDesc().Args.ElementAt(argIndex)); } else { throw new UndeclaredVariableException(Root, ve, ve.GetClosure()); } }
private void InferTypes(RelinqScriptExpression e, TypeInferenceCache cache) { _sap.InferTypes(e, cache, InferTypesImpl); }
private void InferConstant(ConstantExpression ce, TypeInferenceCache cache) { var inferred = TypeInferenceConstants.InferType(ce); if (inferred != null) { cache.Add(ce, inferred); } else { throw new ConstantInferenceFailedException(Root, ce); } }
private void InferNew(NewExpression ne, TypeInferenceCache cache) { ne.Props.Values.ForEach(e => InferTypes(e, cache)); var propTypes = ne.Props.Values.Select(e => cache[e]); if (propTypes.All(t => t is ClrType)) { var clrPropTypes = propTypes.Cast<ClrType>().Select(pt => pt.Type); cache.Add(ne, AnonymousTypesHelper.ForgeTupleType(ne.Props.Keys, clrPropTypes)); } else { var fail = Array.FindIndex(propTypes.ToArray(), t => !(t is ClrType || t is Variant)); if (fail == -1) { cache.Add(ne, new Variant()); } else { throw new CannotForgeAnonymousTypeException( Root, ne, ne.Props.Keys.ElementAt(fail), propTypes.ElementAt(fail)); } } }
public LinqExpression Compile(TypeInferenceCache inferences) { return Compile(Ast, new CompilationContext(inferences)); }