// Typecheck an input for this function, and get the cursor type for an invocation with that input. // arg0 and arg0Type correspond to the input and its type. // The cursor type for aggregate functions is generally the type of a row in the input schema (table), // for example Table in an invocation Average(Table, valueFunction). // Returns true on success, false if the input or its type are invalid with respect to this function's declaration // (and populate the error container accordingly). public virtual bool CheckInput(TexlNode inputNode, DType inputSchema, IErrorContainer errors, out DType typeScope) { Contracts.AssertValue(inputNode); Contracts.Assert(inputSchema.IsValid); Contracts.AssertValue(errors); CallNode callNode = inputNode.Parent.CastList().Parent.CastCall(); Contracts.AssertValue(callNode); typeScope = inputSchema; bool fArgsValid = true; if (_function.ParamTypes.Length == 0) { switch (typeScope.Kind) { case DKind.Record: break; case DKind.Error: fArgsValid = false; errors.EnsureError(inputNode, TexlStrings.ErrBadType); break; default: fArgsValid = false; errors.Error(callNode, TexlStrings.ErrBadType); break; } } else if (_function.ParamTypes[0].IsTable) { if (!typeScope.IsTable) { errors.Error(callNode, TexlStrings.ErrNeedTable_Func, _function.Name); fArgsValid = false; } // This assumes that the lambdas operate on the individual records // of the table, not the entire table. bool fError = false; typeScope = typeScope.ToRecord(ref fError); fArgsValid &= !fError; } else { Contracts.Assert(_function.ParamTypes[0].IsRecord); if (!typeScope.IsRecord) { errors.Error(callNode, TexlStrings.ErrNeedRecord_Func, _function.Name); bool fError = false; typeScope = typeScope.ToRecord(ref fError); fArgsValid = false; } } return(fArgsValid); }
public TexlError Error(TexlNode node, ErrorResourceKey errKey, params object[] args) { if (DefaultSeverity <= maximumSeverity) { return(errors.Error(node, errKey, args)); } return(null); }
public override bool CheckInvocation(TexlNode[] args, DType[] argTypes, IErrorContainer errors, out DType returnType, out Dictionary <TexlNode, DType> nodeToCoercedTypeMap) { Contracts.AssertValue(args); Contracts.AssertAllValues(args); Contracts.AssertValue(argTypes); Contracts.Assert(args.Length == argTypes.Length); Contracts.Assert(args.Length == 1); Contracts.AssertValue(errors); var reifiedError = ErrorType.ReifiedError(); var acceptedFields = reifiedError.GetNames(DPath.Root); var requiredKindField = acceptedFields.Where(tn => tn.Name == "Kind").First(); Contracts.Assert(requiredKindField.Type.IsEnum); var optionalFields = acceptedFields.Where(tn => tn.Name != "Kind"); returnType = DType.ObjNull; nodeToCoercedTypeMap = null; var argument = args[0]; var argumentType = argTypes[0]; if (argumentType.Kind != DKind.Record && argumentType.Kind != DKind.Table) { errors.EnsureError(argument, TexlStrings.ErrBadType); return(false); } // We cache the whole name list regardless of path. var names = argumentType.GetNames(DPath.Root).ToArray(); // First handle required fields (currently only 'Kind') if (!names.Any(field => field.Name == requiredKindField.Name)) { // Kind is required, point it out to the maker, and specify the enumeration type. errors.EnsureError( argument, TexlStrings.ErrBadSchema_ExpectedType, reifiedError.GetKindString()); errors.Error( argument, TexlStrings.ErrColumnMissing_ColName_ExpectedType, requiredKindField.Name.Value, "ErrorKind"); return(false); } var argumentKindType = names.First(tn => tn.Name == requiredKindField.Name).Type; if (!argumentKindType.CoercesTo(requiredKindField.Type)) { errors.EnsureError( argument, TexlStrings.ErrBadSchema_ExpectedType, reifiedError.GetKindString()); errors.Error( argument, TexlStrings.ErrBadRecordFieldType_FieldName_ExpectedType, requiredKindField.Name.Value, "ErrorKind"); return(false); } bool valid = true; var record = argument.AsRecord(); foreach (var name in names) { if (!acceptedFields.Any(field => field.Name == name.Name)) { // If they have a record literal, we can position the errors for rejected fields. if (record != null) { errors.EnsureError(record.Children.Where((_, i) => record.Ids[i].Name == name.Name).FirstOrDefault() ?? record, TexlStrings.ErrErrorIrrelevantField); } else { errors.EnsureError(argument, TexlStrings.ErrErrorIrrelevantField); } valid = false; } } bool matchedWithCoercion; bool typeValid; if (argumentType.Kind == DKind.Record) { // A record with the proper types for the fields that are specified. var expectedOptionalFieldsRecord = DType.CreateRecord( acceptedFields.Where(field => // Kind has already been handled before field.Name != "Kind" && names.Any(name => name.Name == field.Name))); typeValid = CheckType(argument, argumentType, expectedOptionalFieldsRecord, errors, true, out matchedWithCoercion); } else { // A table with the proper types for the fields that are specified. var expectedOptionalFieldsTable = DType.CreateTable( acceptedFields.Where(field => // Kind has already been handled before field.Name != "Kind" && names.Any(name => name.Name == field.Name))); typeValid = CheckType(argument, argumentType, expectedOptionalFieldsTable, errors, true, out matchedWithCoercion); } if (!typeValid) { errors.EnsureError(DocumentErrorSeverity.Severe, argument, TexlStrings.ErrTypeError); valid = false; } else if (matchedWithCoercion && valid) { var recordOrTableSchema = acceptedFields.Where(field => names.Any(name => name.Name == field.Name)); var expectedRecordOrTable = argumentType.Kind == DKind.Record ? DType.CreateRecord(recordOrTableSchema) : DType.CreateTable(recordOrTableSchema); CollectionUtils.Add(ref nodeToCoercedTypeMap, argument, expectedRecordOrTable); } return(valid); }