inferUndefinedExpressionType method

DartType? inferUndefinedExpressionType(
  1. Expression expression
)
inherited

Returns an expected DartType of expression, may be null if cannot be inferred.

Implementation

DartType? inferUndefinedExpressionType(Expression expression) {
  var parent = expression.parent;
  // `(myFunction(),)` or `(name: myFunction())`.
  if (parent case NamedExpression(parent: var grandParent) && var named) {
    parent = grandParent;
    expression = named;
  }
  if (parent is RecordLiteral) {
    var recordType = inferUndefinedExpressionType(parent);
    if (recordType is RecordType) {
      if (expression case NamedExpression named) {
        return recordType.namedFields
            .firstWhere((field) => field.name == named.name.label.name)
            .type;
      } else {
        var index = parent.fields.indexed
            .firstWhere((record) => record.$2 == expression)
            .$1;
        return recordType.positionalFields[index].type;
      }
    }
  }
  // `await (v + v2)`
  if (parent is ParenthesizedExpression) {
    return inferUndefinedExpressionType(parent);
  }
  // `myFunction();`.
  if (expression is MethodInvocation) {
    if (parent is CascadeExpression && parent.parent is ExpressionStatement) {
      return VoidTypeImpl.instance;
    }
    if (parent is ExpressionStatement) {
      return VoidTypeImpl.instance;
    }
  }
  if (parent case ConditionalExpression conditionalExpression) {
    // `v = myFunction() ? 1 : 2;`.
    if (conditionalExpression.condition == expression) {
      return _coreTypeBool;
    } else {
      var type = conditionalExpression.correspondingParameter?.type;
      if (type is InterfaceType && type.isDartCoreFunction) {
        return FunctionTypeImpl(
          typeParameters: const [],
          parameters: const [],
          returnType: DynamicTypeImpl.instance,
          nullabilitySuffix: NullabilitySuffix.none,
        );
      }
      return type;
    }
  }
  // `=> myFunction();`.
  if (parent is ExpressionFunctionBody) {
    if (_closureReturnType(expression) case var returnType?) {
      return returnType;
    }
    var executable = expression.enclosingExecutableElement;
    return executable?.returnType;
  }
  // `return myFunction();`.
  if (parent is ReturnStatement) {
    if (_closureReturnType(expression) case var returnType?) {
      return returnType;
    }
    var executable = expression.enclosingExecutableElement;
    return executable?.returnType;
  }
  // `int v = myFunction();`.
  if (parent is VariableDeclaration) {
    var variableDeclaration = parent;
    if (variableDeclaration.initializer == expression) {
      var variableElement = variableDeclaration.declaredFragment?.element;
      if (variableElement case VariableElement(:var type)) {
        if (type is InvalidType) {
          return typeProvider.dynamicType;
        }
        return type;
      }
    }
  }
  if (parent is AssignmentExpression) {
    var assignment = parent;
    // `myField = 42;`.
    if (assignment.leftHandSide == expression) {
      var rhs = assignment.rightHandSide;
      return rhs.staticType;
    } else if (assignment.rightHandSide == expression) {
      if (assignment.operator.type == TokenType.EQ) {
        // `v = myFunction();`.
        return assignment.writeType;
      } else if (assignment.writeType case var expectedType?) {
        // `v += myFunction();`.
        var method = assignment.element;
        if (method case MethodElement(
          :var returnType,
          formalParameters: List(length: 1, :var first),
        )) {
          if (typeSystem.isAssignableTo(returnType, expectedType)) {
            // The return type is assignable to the expected type, then use
            // the expected parameter type.
            return first.type;
          } else if (typeSystem.isAssignableTo(expectedType, returnType) &&
              typeSystem.isAssignableTo(expectedType, first.type)) {
            // The expected type is a subtype of the return type and the
            // parameter would accept the expected type, then use the
            // expected type.
            // ---
            // Spec (section 17.31) reads:
            // The static type of an additive expression is usually
            // determined by the signature given in the declaration of the
            // operator used. However, invocations of the operators + and -
            // of class int, double and num are treated specially by the
            // typechecker.
            // ---
            // This ensures that cases like `int v = 0; v += myFunction();`
            // will return `int`.
            if (_isSpecialCaseNumTypes(method, expectedType)) {
              return expectedType;
            }
          }
        }
        return InvalidTypeImpl.instance;
      }
    }
  }
  if (parent is BinaryExpression) {
    var binary = parent;
    var method = binary.element;
    // `v + myFunction();`.
    if (method != null) {
      if (binary.rightOperand == expression) {
        var parameters = method.formalParameters;
        return parameters.length == 1 ? parameters[0].type : null;
      }
    } else if (binary.operator.type == TokenType.QUESTION_QUESTION) {
      // `v ?? myFunction();`.
      // This handles when the expression is being assigned somewhere.
      var type = inferUndefinedExpressionType(binary);
      if (binary.rightOperand == expression) {
        return type ?? binary.leftOperand.staticType;
      } else if (binary.leftOperand == expression) {
        type ??= binary.rightOperand.staticType;
        return switch (type) {
          TypeImpl type => type.withNullability(NullabilitySuffix.question),
          _ => null,
        };
      }
    }
  }
  // `foo( myFunction() );`.
  if (parent is ArgumentList) {
    var parameter = expression.correspondingParameter;
    return parameter?.type;
  }
  // `bool`.
  {
    // `assert( myFunction() );`.
    if (parent is AssertStatement) {
      var statement = parent;
      if (statement.condition == expression) {
        return _coreTypeBool;
      }
    }
    // `if ( myFunction() ) {}`.
    if (parent is IfStatement) {
      var statement = parent;
      if (statement.expression == expression) {
        return _coreTypeBool;
      }
    }
    if (parent is WhenClause) {
      var clause = parent;
      if (clause.expression == expression) {
        return _coreTypeBool;
      }
    }
    // `while ( myFunction() ) {}`.
    if (parent is WhileStatement) {
      var statement = parent;
      if (statement.condition == expression) {
        return _coreTypeBool;
      }
    }
    // `do {} while ( myFunction() );`.
    if (parent is DoStatement) {
      var statement = parent;
      if (statement.condition == expression) {
        return _coreTypeBool;
      }
    }
    // `!myFunction()`.
    if (parent is PrefixExpression) {
      var prefixExpression = parent;
      if (prefixExpression.operator.type == TokenType.BANG) {
        return _coreTypeBool;
      }
    }
    // Binary expression `&&` or `||`.
    if (parent is BinaryExpression) {
      var binaryExpression = parent;
      var operatorType = binaryExpression.operator.type;
      if (operatorType == TokenType.AMPERSAND_AMPERSAND ||
          operatorType == TokenType.BAR_BAR) {
        return _coreTypeBool;
      }
    }
  }
  // Handle `await`, infer a `Future` type.
  if (parent is AwaitExpression) {
    var grandParent = parent.parent;
    // `await myFunction();`
    if (grandParent is ExpressionStatement) {
      return typeProvider.futureType(typeProvider.voidType);
    }
    var inferredParentType =
        inferUndefinedExpressionType(parent) ?? typeProvider.dynamicType;
    if (inferredParentType is InvalidType) {
      inferredParentType = typeProvider.dynamicType;
    }
    return typeProvider.futureType(inferredParentType);
  }
  // We don't know.
  return null;
}