protected internal override void TraverseOperator(Operator op)
        {
            op.Args.ForEach(Traverse);

            // todo. also support nullables and lifted versions of operators
            op.Args.Any(arg => Types[arg].IsNullable()).AssertFalse();

            var impArgs = op.Args;
            var anyBool = impArgs.Any(arg => Types[arg] == typeof(bool));
            if (anyBool) { Types.Add(op, typeof(bool)); return; }

            var opt = op.OperatorType;
            if (opt.IsAssign() || opt == OperatorType.Coalesce || 
                opt == OperatorType.LeftShift || opt == OperatorType.RightShift)
            {
                Types.Add(op, Types[op.Args.First()]);
            }
            else if (opt == OperatorType.AndAlso || opt == OperatorType.OrElse || opt == OperatorType.Not ||
                opt == OperatorType.GreaterThan || opt == OperatorType.GreaterThanOrEqual ||
                opt == OperatorType.Equal || opt == OperatorType.NotEqual ||
                opt == OperatorType.LessThan || opt == OperatorType.LessThanOrEqual)
            {
                Types.Add(op, typeof(bool));
            }
            else
            {
                if (op.IsUnary())
                {
                    var t_target = Types[op.Unary().Target];
                    if (t_target == null) Types.Add(op, null);
                    else
                    {
                        (t_target.IsInteger() || t_target.IsFloatingPoint()).AssertTrue();
                        Types.Add(op, t_target);
                    }
                }
                else if (op.IsBinary())
                {
                    var t_lhs = Types[op.Binary().Lhs];
                    var t_rhs = Types[op.Binary().Rhs];
                    if (t_lhs == null || t_rhs == null) Types.Add(op, null);
                    else
                    {
                        if (t_lhs.IsInteger() && t_rhs.IsInteger())
                        {
                            Func<Type, int> bitness = t => 
                                t == typeof(sbyte) || t == typeof(byte) ? 8 :
                                t == typeof(short) || t == typeof(ushort) ? 16 :
                                t == typeof(int) || t == typeof(uint) ? 32 :
                                t == typeof(long) || t == typeof(ulong) ? 64 :
                                ((Func<int>)(() => { throw AssertionHelper.Fail(); }))();
                            Func<Type, bool> signed = t => 
                                t == typeof(sbyte) || t == typeof(short) ||
                                t == typeof(int) || t == typeof(long) ? true :
                                t == typeof(byte) || t == typeof(ushort) ||
                                t == typeof(uint) || t == typeof(ulong) ? false :
                                ((Func<bool>)(() => { throw AssertionHelper.Fail(); }))();
                            Func<int, bool, Type> mk_int = (n_bits, is_signed) =>
                            {
                                if (n_bits == 8) return is_signed ? typeof(sbyte) : typeof(byte);
                                if (n_bits == 16) return is_signed ? typeof(short) : typeof(ushort);
                                if (n_bits == 32) return is_signed ? typeof(int) : typeof(uint);
                                if (n_bits == 64) return is_signed ? typeof(long) : typeof(ulong);
                                throw AssertionHelper.Fail();
                            };

                            // todo. the rules below are specific to C#
                            Func<Type, Type, bool> has_cast_to = (wb, t) => wb == t || bitness(wb) < bitness(t);
                            Func<Type, Type, bool> args_match = (t1, t2) => has_cast_to(t_lhs, t1) && has_cast_to(t_rhs, t2);
                            Type t_result = null;
                            if (args_match(typeof(int), typeof(int))) t_result = typeof(int);
                            else if (args_match(typeof(uint), typeof(uint))) t_result = typeof(uint);
                            else if (args_match(typeof(long), typeof(long))) t_result = typeof(long);
                            else if (args_match(typeof(ulong), typeof(ulong))) t_result = typeof(ulong);
                            else t_result.AssertNotNull();

                            Types.Add(op, t_result);
                        }
                        else
                        {
                            (t_lhs.IsFloatingPoint() && t_rhs.IsFloatingPoint()).AssertTrue();
                            if (t_lhs == typeof(double) || t_rhs == typeof(double)) Types.Add(op, typeof(double));
                            else if (t_lhs == typeof(float) || t_rhs == typeof(float)) Types.Add(op, typeof(float));
                            else throw AssertionHelper.Fail();
                        }
                    }
                }
                else
                {
                    throw AssertionHelper.Fail();
                }
            }
        }
        protected internal override void TraverseOperator(Operator op)
        {
            var opt = op.OperatorType;
            if (op.IsUnary())
            {
                if (!opt.ToString().StartsWith("Post"))
                    _writer.Write(opt.ToCSharpSymbol());

                Traverse(op.Args.First());

                if (opt.ToString().StartsWith("Post"))
                    _writer.Write(opt.ToCSharpSymbol());
            }
            else if (op.IsBinary())
            {
                Traverse(op.Args.First());
                _writer.Write(" " + opt.ToCSharpSymbol() + " ");
                Traverse(op.Args.Second());
            }
            else
            {
                op.Unsupported();
            }
        }