in src/Analysis/Ast/Impl/Types/ArgumentSet.cs [90:294]
public ArgumentSet(IPythonFunctionType fn, int overloadIndex, IPythonType instanceType, CallExpression callExpr, IExpressionEvaluator eval) {
Eval = eval;
OverloadIndex = overloadIndex;
DeclaringModule = fn.DeclaringModule;
Expression = callExpr;
if (callExpr == null) {
// Typically invoked by specialization code without call expression in the code.
// Caller usually does not care about arguments.
_evaluated = true;
return;
}
var overload = fn.Overloads[overloadIndex];
var fdParameters = overload.FunctionDefinition?.Parameters.Where(p => !p.IsPositionalOnlyMarker).ToArray();
// Some specialized functions have more complicated definitions, so we pass
// parameters to those, TypeVar() is an example, so we allow the latter logic to handle
// argument instatiation. For simple specialized functions, it is enough to handle here.
if (fn.IsSpecialized && overload.Parameters.Count == 0) {
// Specialized functions typically don't have AST definitions.
// We construct the arguments from the call expression. If an argument does not have a name,
// we try using name from the function definition based on the argument's position.
_arguments = new List<Argument>();
for (var i = 0; i < callExpr.Args.Count; i++) {
var name = callExpr.Args[i].Name;
if (string.IsNullOrEmpty(name)) {
name = i < overload.Parameters.Count ? overload.Parameters[i].Name : $"arg{i}";
}
var node = fdParameters?.ElementAtOrDefault(i);
_arguments.Add(new Argument(name, ParameterKind.Normal, callExpr.Args[i].Expression, null, node));
}
return;
}
var callLocation = callExpr.Target?.GetLocation(eval);
// https://www.python.org/dev/peps/pep-3102/#id5
// For each formal parameter, there is a slot which will be used to contain
// the value of the argument assigned to that parameter. Slots which have
// had values assigned to them are marked as 'filled'.Slots which have
// no value assigned to them yet are considered 'empty'.
var slots = new Argument[overload.Parameters.Count];
for (var i = 0; i < overload.Parameters.Count; i++) {
var node = fdParameters?.ElementAtOrDefault(i);
slots[i] = new Argument(overload.Parameters[i], node);
}
// Locate sequence argument, if any
var sa = slots.Where(s => s.Kind == ParameterKind.List).ToArray();
if (sa.Length > 1) {
// Error should have been reported at the function definition location by the parser.
return;
}
var da = slots.Where(s => s.Kind == ParameterKind.Dictionary).ToArray();
if (da.Length > 1) {
// Error should have been reported at the function definition location by the parser.
return;
}
_listArgument = sa.Length == 1 && sa[0].Name.Length > 0 ? new ListArg(sa[0].Name, sa[0].ValueExpression, sa[0].Location) : null;
_dictArgument = da.Length == 1 ? new DictArg(da[0].Name, da[0].ValueExpression, da[0].Location) : null;
// Class methods
var formalParamIndex = 0;
if (fn.DeclaringType != null && fn.HasClassFirstArgument() && slots.Length > 0) {
slots[0].Value = instanceType ?? fn.DeclaringType;
formalParamIndex++;
}
try {
// Positional arguments
var callParamIndex = 0;
for (; callParamIndex < callExpr.Args.Count; callParamIndex++, formalParamIndex++) {
var arg = callExpr.Args[callParamIndex];
if (!string.IsNullOrEmpty(arg.Name) && !arg.Name.StartsWithOrdinal("**")) {
// Keyword argument. Done with positionals.
break;
}
if (formalParamIndex >= overload.Parameters.Count) {
// We ran out of formal parameters and yet haven't seen
// any sequence or dictionary ones. This looks like an error.
_errors.Add(new DiagnosticsEntry(Resources.Analysis_TooManyFunctionArguments, callLocation.Span,
ErrorCodes.TooManyFunctionArguments, Severity.Warning, DiagnosticSource.Analysis));
return;
}
var formalParam = overload.Parameters[formalParamIndex];
if (formalParam.Kind == ParameterKind.List) {
if (string.IsNullOrEmpty(formalParam.Name)) {
// If the next unfilled slot is a vararg slot, and it does not have a name, then it is an error.
_errors.Add(new DiagnosticsEntry(Resources.Analysis_TooManyPositionalArgumentBeforeStar, arg.GetLocation(eval).Span,
ErrorCodes.TooManyPositionalArgumentsBeforeStar, Severity.Warning, DiagnosticSource.Analysis));
return;
}
// If the next unfilled slot is a vararg slot then all remaining
// non-keyword arguments are placed into the vararg slot.
if (_listArgument == null) {
_errors.Add(new DiagnosticsEntry(Resources.Analysis_TooManyFunctionArguments, arg.GetLocation(eval).Span,
ErrorCodes.TooManyFunctionArguments, Severity.Warning, DiagnosticSource.Analysis));
return;
}
for (; callParamIndex < callExpr.Args.Count; callParamIndex++) {
arg = callExpr.Args[callParamIndex];
if (!string.IsNullOrEmpty(arg.Name)) {
// Keyword argument. Done here.
break;
}
_listArgument._Expressions.Add(arg.Expression);
}
break; // Sequence or dictionary parameter found. Done here.
}
if (formalParam.Kind == ParameterKind.Dictionary) {
// Next slot is a dictionary slot, but we have positional arguments still.
_errors.Add(new DiagnosticsEntry(Resources.Analysis_TooManyPositionalArgumentBeforeStar, arg.GetLocation(eval).Span,
ErrorCodes.TooManyPositionalArgumentsBeforeStar, Severity.Warning, DiagnosticSource.Analysis));
return;
}
// Regular parameter
slots[formalParamIndex].ValueExpression = arg.Expression;
}
// Keyword arguments
for (; callParamIndex < callExpr.Args.Count; callParamIndex++) {
var arg = callExpr.Args[callParamIndex];
if (string.IsNullOrEmpty(arg.Name)) {
_errors.Add(new DiagnosticsEntry(Resources.Analysis_PositionalArgumentAfterKeyword, arg.GetLocation(eval).Span,
ErrorCodes.PositionalArgumentAfterKeyword, Severity.Warning, DiagnosticSource.Analysis));
return;
}
var nvp = slots.FirstOrDefault(s => s.Name.EqualsOrdinal(arg.Name));
if (nvp == null) {
// 'def f(a, b)' and then 'f(0, c=1)'. Per spec:
// if there is a 'keyword dictionary' argument, the argument is added
// to the dictionary using the keyword name as the dictionary key,
// unless there is already an entry with that key, in which case it is an error.
if (_dictArgument == null) {
_errors.Add(new DiagnosticsEntry(Resources.Analysis_UnknownParameterName, arg.GetLocation(eval).Span,
ErrorCodes.UnknownParameterName, Severity.Warning, DiagnosticSource.Analysis));
return;
}
if (_dictArgument.Arguments.ContainsKey(arg.Name)) {
_errors.Add(new DiagnosticsEntry(Resources.Analysis_ParameterAlreadySpecified.FormatUI(arg.Name), arg.GetLocation(eval).Span,
ErrorCodes.ParameterAlreadySpecified, Severity.Warning, DiagnosticSource.Analysis));
return;
}
_dictArgument._Expressions[arg.Name] = arg.Expression;
continue;
}
if (nvp.Kind == ParameterKind.PositionalOnly) {
_errors.Add(new DiagnosticsEntry(Resources.Analysis_PositionalOnlyArgumentNamed.FormatInvariant(arg.Name), arg.GetLocation(eval).Span,
ErrorCodes.PositionalOnlyNamed, Severity.Warning, DiagnosticSource.Analysis));
return;
}
if (nvp.ValueExpression != null || nvp.Value != null) {
// Slot is already filled.
_errors.Add(new DiagnosticsEntry(Resources.Analysis_ParameterAlreadySpecified.FormatUI(arg.Name), arg.GetLocation(eval).Span,
ErrorCodes.ParameterAlreadySpecified, Severity.Warning, DiagnosticSource.Analysis));
return;
}
// OK keyword parameter
nvp.ValueExpression = arg.Expression;
}
// We went through all positionals and keywords.
// For each remaining empty slot: if there is a default value for that slot,
// then fill the slot with the default value. If there is no default value,
// then it is an error.
foreach (var slot in slots.Where(s => s.Kind != ParameterKind.List && s.Kind != ParameterKind.Dictionary && s.Value == null)) {
if (slot.ValueExpression == null) {
var parameter = overload.Parameters.First(p => p.Name == slot.Name);
if (parameter.DefaultValue == null) {
// TODO: parameter is not assigned and has no default value.
_errors.Add(new DiagnosticsEntry(Resources.Analysis_ParameterMissing.FormatUI(slot.Name), callLocation.Span,
ErrorCodes.ParameterMissing, Severity.Warning, DiagnosticSource.Analysis));
}
// Note that parameter default value expression is from the function definition AST
// while actual argument values are from the calling file AST.
slot.ValueExpression = CreateExpression(parameter.Name, parameter.DefaultValueString);
slot.Value = parameter.DefaultValue;
slot.ValueIsDefault = true;
}
}
} finally {
// Optimistically return what we gathered, even if there are errors.
_arguments = slots.Where(s => s.Kind != ParameterKind.List && s.Kind != ParameterKind.Dictionary).ToList();
}
}