private IMember GetValueFromBinaryOp(Expression expr, LookupOptions lookupOptions) { if (expr is AndExpression a) { GetValueFromExpression(a.Left, lookupOptions); GetValueFromExpression(a.Right, lookupOptions); return(Interpreter.GetBuiltinType(BuiltinTypeId.Bool)); } if (expr is OrExpression orexp) { // Consider 'self.__params = types.MappingProxyType(params or {})' var leftSide = GetValueFromExpression(orexp.Left, lookupOptions); if (!leftSide.IsUnknown()) { return(leftSide); } var rightSide = GetValueFromExpression(orexp.Right, lookupOptions); return(rightSide.IsUnknown() ? Interpreter.GetBuiltinType(BuiltinTypeId.Bool) : rightSide); } if (!(expr is BinaryExpression binop) || binop.Left == null) { return(null); } var op = binop.Operator; var left = GetValueFromExpression(binop.Left, lookupOptions) ?? UnknownType; var right = GetValueFromExpression(binop.Right, lookupOptions) ?? UnknownType; if (left.IsUnknown() && right.IsUnknown()) { // Fast path for when nothing below will give any results. if (op.IsComparison()) { return(Interpreter.GetBuiltinType(BuiltinTypeId.Bool)); } return(UnknownType); } var leftType = left.GetPythonType(); var rightType = right.GetPythonType(); var leftTypeId = leftType.TypeId; var rightTypeId = rightType.TypeId; if (op == PythonOperator.Add && leftTypeId == rightTypeId && left is IPythonCollection lc && right is IPythonCollection rc) { switch (leftTypeId) { case BuiltinTypeId.List: return(PythonCollectionType.CreateConcatenatedList(Module, lc, rc)); case BuiltinTypeId.Tuple: return(PythonCollectionType.CreateConcatenatedTuple(Module, lc, rc)); } } // Mod-style string formatting; don't bother looking at the right side. if (op == PythonOperator.Mod && (leftTypeId == BuiltinTypeId.Str || leftTypeId == BuiltinTypeId.Unicode)) { return(Interpreter.GetBuiltinType(leftTypeId)); } var leftIsSupported = IsSupportedBinopBuiltin(leftTypeId); var rightIsSupported = IsSupportedBinopBuiltin(rightTypeId); if (leftIsSupported && rightIsSupported) { if (TryGetValueFromBuiltinBinaryOp(op, leftTypeId, rightTypeId, Interpreter.LanguageVersion.Is3x(), out var member)) { return(member); } } if (leftIsSupported) { IMember ret; if (op.IsComparison()) { // If the op is a comparison, and the thing on the left is the builtin, // flip the operation and call it instead. ret = CallOperator(op.InvertComparison(), right, rightType, left, leftType, expr, tryRight: false); } else { ret = CallOperator(op, left, leftType, right, rightType, expr, tryLeft: false); } if (!ret.IsUnknown()) { return(ret); } return(op.IsComparison() ? Interpreter.GetBuiltinType(BuiltinTypeId.Bool) : left); } if (rightIsSupported) { // Try calling the function on the left side, otherwise just return right. var ret = CallOperator(op, left, leftType, right, rightType, expr, tryRight: false); if (!ret.IsUnknown()) { return(ret); } return(op.IsComparison() ? Interpreter.GetBuiltinType(BuiltinTypeId.Bool) : right); } var callRet = CallOperator(op, left, leftType, right, rightType, expr); if (!callRet.IsUnknown()) { return(callRet); } if (op.IsComparison()) { callRet = CallOperator(op.InvertComparison(), right, rightType, left, leftType, expr); if (!callRet.IsUnknown()) { return(callRet); } } // TODO: Specific parsing // TODO: warn about incompatible types like 'str' + 1 switch (op) { case PythonOperator.Equal: case PythonOperator.GreaterThan: case PythonOperator.GreaterThanOrEqual: case PythonOperator.In: case PythonOperator.Is: case PythonOperator.IsNot: case PythonOperator.LessThan: case PythonOperator.LessThanOrEqual: case PythonOperator.Not: case PythonOperator.NotEqual: case PythonOperator.NotIn: // Assume all of these return True/False return(Interpreter.GetBuiltinType(BuiltinTypeId.Bool)); case PythonOperator.Divide: case PythonOperator.TrueDivide: if (Interpreter.LanguageVersion.Is3x()) { return(Interpreter.GetBuiltinType(BuiltinTypeId.Float)); } break; } return(left.IsUnknown() ? right : left); }