Repository navigation
Conversation
| */ | ||
| public void request(QueryType queryType, String data, Sink sink, | ||
| List<String> fieldNames, List<ColumnMetaData.Rep> fieldTypes, | ||
| List<String> fieldNames, List<ColumnMetaData.@Nullable Rep> fieldTypes, |
There was a problem hiding this comment.
This might better be List<? extends ColumnMetaData.@Nullable Rep>, however, AFAIK it would change public API signature, so it would not be 100% backward compatible
ca7e071 to
3a5b15e
Compare
5df3a4d to
a8777a6
Compare
|
nit: Apache recommends using |
|
@snuyanzin , thanks for the reference. I've filed #5230 for it. |
|
…ecognize JSpecify defines the locations where `@Nullable` and `@NonNull` carry meaning and states that an annotation anywhere else has none. Nothing reports one. An author who writes `@Nullable int count` or `List<@nullable ?> values` gets no diagnostic and a false sense of safety, and the specification asks source-analysis tools to offer exactly this diagnostic. Error Prone's own checks reach 4 of the 17 locations the specification lists: NullablePrimitive, NullableWildcard, NullableTypeParameter and NullableOnContainingClass. Measured on the Apache Calcite branch that moves from the Checker Framework to NullAway and JSpecify (apache/calcite#5213), those four reach none of the 161 annotations in unrecognized locations that branch carries, because all 161 sit on the root type of a local variable or of a cast. JSpecifyUnrecognizedAnnotationLocation reports those 17 locations and two more the specification's catch-all reaches: a constructor's result type and a method reference's root type. One check rather than a set of per-construct checks, because the rule is about position: everything not listed as recognized is unrecognized. That is what separates `(List<@nullable String>) o`, which is clean, from `(@nullable String) o`, which is not, and each per-construct check would have to re-derive that a type argument inside an unrecognized root is still recognized. One check is also one severity and one `@SuppressWarnings` name. It reads org.jspecify.annotations.Nullable and .NonNull by qualified name and no other annotation. Reading any annotation named `Nullable` was rejected: the Checker Framework reads its own on a local variable's and a cast's root type, and IntelliJ reads JetBrains' on a local, so telling those users the annotation has no meaning where their own tool reads it would be wrong. The check ships at SUGGESTION and reports nothing there, not even a note, so upgrading NullAway without asking for it changes nothing; -Xep:JSpecifyUnrecognizedAnnotationLocation:WARN or :ERROR turns it on. Error Prone cannot ship a plugin check off outright (google/error-prone#5387). -XepOpt:...:CheckLocalVariableRootType=false silences the one location a codebase arriving from JetBrains' annotations carries in bulk; IntelliJ added the same switch in IDEA-374631. It is one key per location rather than one list, because ErrorProneFlags holds one value per key and two convention plugins appending to a shared list would leave whichever ran last, silently. Fixes move the annotation to a wildcard or type parameter bound, onto the last name of a qualified type, and after an array's element type, so `@Nullable Bounds.Inner` and `@Nullable String[]` become `Bounds.@nullable Inner` and `String @nullable []` rather than losing it. They remove it where no single recognized form expresses the intent. The changelog entry is one line, matching the house format. Note that this repository has never carried an `Unreleased` section: every one of the twelve commits that touched CHANGELOG.md is a dedicated "Release notes for X" pull request, so entries are curated at release rather than written per change. Fold the section into the release notes or drop it if that flow is preferred. What a reader needs beyond the check's name -- that it ships at SUGGESTION, reports nothing there, and is raised with -Xep:JSpecifyUnrecognizedAnnotationLocation:WARN -- belongs in the release paragraph above the bullet list, which is written at release time. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ecognize JSpecify defines the locations where `@Nullable` and `@NonNull` carry meaning and states that an annotation anywhere else has none. Nothing reports one. An author who writes `@Nullable int count` or `List<@nullable ?> values` gets no diagnostic and a false sense of safety, and the specification asks source-analysis tools to offer exactly this diagnostic. Error Prone's own checks reach 4 of the 17 locations the specification lists: NullablePrimitive, NullableWildcard, NullableTypeParameter and NullableOnContainingClass. Measured on the Apache Calcite branch that moves from the Checker Framework to NullAway and JSpecify (apache/calcite#5213), those four reach none of the 161 annotations in unrecognized locations that branch carries, because all 161 sit on the root type of a local variable or of a cast. JSpecifyUnrecognizedAnnotationLocation reports those 17 locations and two more the specification's catch-all reaches: a constructor's result type and a method reference's root type. One check rather than a set of per-construct checks, because the rule is about position: everything not listed as recognized is unrecognized. That is what separates `(List<@nullable String>) o`, which is clean, from `(@nullable String) o`, which is not, and each per-construct check would have to re-derive that a type argument inside an unrecognized root is still recognized. One check is also one severity and one `@SuppressWarnings` name. It reads org.jspecify.annotations.Nullable and .NonNull by qualified name and no other annotation. Reading any annotation named `Nullable` was rejected: the Checker Framework reads its own on a local variable's and a cast's root type, and IntelliJ reads JetBrains' on a local, so telling those users the annotation has no meaning where their own tool reads it would be wrong. The check ships at SUGGESTION and reports nothing there, not even a note, so upgrading NullAway without asking for it changes nothing; -Xep:JSpecifyUnrecognizedAnnotationLocation:WARN or :ERROR turns it on. Error Prone cannot ship a plugin check off outright (google/error-prone#5387). -XepOpt:...:CheckLocalVariableRootType=false silences the one location a codebase arriving from JetBrains' annotations carries in bulk; IntelliJ added the same switch in IDEA-374631. It is one key per location rather than one list, because ErrorProneFlags holds one value per key and two convention plugins appending to a shared list would leave whichever ran last, silently. Fixes move the annotation to a wildcard or type parameter bound, onto the last name of a qualified type, and after an array's element type, so `@Nullable Bounds.Inner` and `@Nullable String[]` become `Bounds.@nullable Inner` and `String @nullable []` rather than losing it. They remove it where no single recognized form expresses the intent. The changelog entry is one line, matching the house format. Note that this repository has never carried an `Unreleased` section: every one of the twelve commits that touched CHANGELOG.md is a dedicated "Release notes for X" pull request, so entries are curated at release rather than written per change. Fold the section into the release notes or drop it if that flow is preferred. What a reader needs beyond the check's name -- that it ships at SUGGESTION, reports nothing there, and is raised with -Xep:JSpecifyUnrecognizedAnnotationLocation:WARN -- belongs in the release paragraph above the bullet list, which is written at release time. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ecognize JSpecify defines the locations where `@Nullable` and `@NonNull` carry meaning and states that an annotation anywhere else has none. Nothing reports one. An author who writes `@Nullable int count` or `List<@nullable ?> values` gets no diagnostic and a false sense of safety, and the specification asks source-analysis tools to offer exactly this diagnostic. Error Prone's own checks reach 4 of the 17 locations the specification lists: NullablePrimitive, NullableWildcard, NullableTypeParameter and NullableOnContainingClass. Measured on the Apache Calcite branch that moves from the Checker Framework to NullAway and JSpecify (apache/calcite#5213), those four reach none of the 161 annotations in unrecognized locations that branch carries, because all 161 sit on the root type of a local variable or of a cast. JSpecifyUnrecognizedAnnotationLocation reports those 17 locations and two more the specification's catch-all reaches: a constructor's result type and a method reference's root type. One check rather than a set of per-construct checks, because the rule is about position: everything not listed as recognized is unrecognized. That is what separates `(List<@nullable String>) o`, which is clean, from `(@nullable String) o`, which is not, and each per-construct check would have to re-derive that a type argument inside an unrecognized root is still recognized. One check is also one severity and one `@SuppressWarnings` name. It reads org.jspecify.annotations.Nullable and .NonNull by qualified name and no other annotation. Reading any annotation named `Nullable` was rejected: the Checker Framework reads its own on a local variable's and a cast's root type, and IntelliJ reads JetBrains' on a local, so telling those users the annotation has no meaning where their own tool reads it would be wrong. The check ships at SUGGESTION and reports nothing there, not even a note, so upgrading NullAway without asking for it changes nothing; -Xep:JSpecifyUnrecognizedAnnotationLocation:WARN or :ERROR turns it on. Error Prone cannot ship a plugin check off outright (google/error-prone#5387). -XepOpt:...:CheckLocalVariableRootType=false silences the one location a codebase arriving from JetBrains' annotations carries in bulk; IntelliJ added the same switch in IDEA-374631. It is one key per location rather than one list, because ErrorProneFlags holds one value per key and two convention plugins appending to a shared list would leave whichever ran last, silently. Fixes move the annotation to a wildcard or type parameter bound, onto the last name of a qualified type, and after an array's element type, so `@Nullable Bounds.Inner` and `@Nullable String[]` become `Bounds.@nullable Inner` and `String @nullable []` rather than losing it. They remove it where no single recognized form expresses the intent. The changelog entry is one line, matching the house format. Note that this repository has never carried an `Unreleased` section: every one of the twelve commits that touched CHANGELOG.md is a dedicated "Release notes for X" pull request, so entries are curated at release rather than written per change. Fold the section into the release notes or drop it if that flow is preferred. What a reader needs beyond the check's name -- that it ships at SUGGESTION, reports nothing there, and is raised with -Xep:JSpecifyUnrecognizedAnnotationLocation:WARN -- belongs in the release paragraph above the bullet list, which is written at release time. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ecognize (#1787) ## Why JSpecify defines the locations where `@Nullable` and `@NonNull` carry meaning and states that an annotation anywhere else has none. `@Nullable int count` and `List<@nullable ?> values` read as claims about nullness and are not, so an author who writes one holds a false sense of safety and nothing says otherwise. The specification asks source-analysis tools to offer exactly this diagnostic. Error Prone's existing checks reach 4 of the 17 locations the specification lists. Compiling one file that holds an example of each through the default checks reports `NullablePrimitive` on the primitive type, `NullableWildcard` on the wildcard, `NullableTypeParameter` on the type parameter, and `NullableOnContainingClass` on the outer type qualifying an inner type. The 13 they do not reach are the local variable root type, cast root type, `instanceof` type, pattern type, object creation, array creation, supertype, thrown type, exception parameter, receiver parameter, class declaration, enum constant, and annotation interface member return. Measured on a real migration: the Apache Calcite branch that replaces the Checker Framework with NullAway and JSpecify ([apache/calcite#5213](apache/calcite#5213)) carries 161 annotations in unrecognized locations, all of them local variable root types (141) and cast root types (20). The four existing checks reach none of the 161. **40 of the 161 were introduced by the migration branch itself**, established both by `git blame` against the branch's commit range and by comparing each line against the merge base. So the check catches the mistake while it is being made, not only the backlog a project inherits. Every one carries a suggested fix: `-XepPatchChecks:JSpecifyUnrecognizedAnnotationLocation` makes 161 replacements across 74 files and the rebuild reports zero. ## What `JSpecifyUnrecognizedAnnotationLocation` reports the 17 unrecognized locations the specification lists, and two more its catch-all reaches: the result type of a constructor, which `NullableConstructor` already reports, and the root type of a method reference, which nothing else reaches. It reads `org.jspecify.annotations.Nullable` and `org.jspecify.annotations.NonNull` by their qualified names and no other annotation. Reading any annotation named `Nullable` was rejected: the tools that define the others read them in some of these very locations. The Checker Framework reads its `@Nullable` on the root type of a local variable and of a cast; IntelliJ reads JetBrains' on a local variable. Telling those users the annotation has no meaning where their own tool does read it would be wrong. Supporting another annotation is left to a flag on this check. It is one check rather than a documented set of per-construct checks because JSpecify's rule is about position, not about constructs: everything not listed as recognized is unrecognized. That is what separates `(List<@nullable String>) o`, which is clean, from `(@nullable String) o`, which is not, and `new ArrayList<@nullable String>()` from `new @nullable ArrayList<String>()`. Each per-construct check has to re-derive that a type argument inside an unrecognized root is still recognized. One check is also one severity and one `@SuppressWarnings("JSpecifyUnrecognizedAnnotationLocation")`, where a documented set costs N flags and N suppression names and grows silently as locations are covered. The check ships at `SUGGESTION` and reports nothing there, not even a note, so a user who upgrades NullAway without asking for it sees no change. `-Xep:JSpecifyUnrecognizedAnnotationLocation:WARN` or `:ERROR` turns it on. Error Prone cannot ship a plugin check turned off outright ([google/error-prone#5387](google/error-prone#5387)), and this is how close to off a default can get. `-XepOpt:JSpecifyUnrecognizedAnnotationLocation:CheckLocalVariableRootType=false` silences the root type of a local variable, the one location a codebase arriving from JetBrains' annotations carries in bulk; IntelliJ added the same switch in [IDEA-374631](https://youtrack.jetbrains.com/issue/IDEA-374631). It reports by default, so that switching the check on reports every location the specification lists. Fixes move the annotation to the bound of a wildcard or type parameter, onto the last name of a qualified type, and after the element type of an array, so `@Nullable Bounds.Inner` and `@Nullable String[]` become `Bounds.@nullable Inner` and `String @nullable []` rather than losing the annotation. They remove it where no single recognized form expresses the intent, such as a lower-bounded wildcard or an intersection bound, and where a nullness annotation already stands at the destination. Running the check over NullAway's own sources found one real defect, fixed here: `GenericsTests.nestedGenericTypes` wrote `@Nullable Wrapper<String>.Fn<String>`, which annotates the outer `Wrapper` rather than the `Fn` the test means to make nullable. The JSpecify-correct `Wrapper<String>.@nullable Fn<String>` passes every assertion that test already made. One test source in `JSpecifyArrayTests` annotates an unrecognized location on purpose and now says so with `@SuppressWarnings`. ## Verification 66 tests in `JSpecifyUnrecognizedAnnotationLocationTest` establish one diagnostic per location, that the recognized locations nested inside unrecognized ones stay unreported, a fix per fix shape, and exact report counts for the annotations two tree paths reach, which a `// BUG:` marker cannot tell apart from one reported once; `UnrecognizedAnnotationLocationInTestSourcesTest` (new) fails if the harness stops running the check over the sources every other test compiles. ## Scope A construct the check cannot name is left alone rather than reported under the specification's catch-all, so the failure mode is a missed diagnostic rather than a wrong one. A codebase still on the Checker Framework's or JetBrains' annotations gets no diagnostic until it has switched. That is the narrowing working as intended; the follow-up is a flag on this check that maps another annotation onto JSpecify's. The severity is per check rather than per location, so a build cannot take `@Nullable Outer.Inner`, which contradicts a property the specification calls intrinsic, as an error while leaving another location a warning. `Description.Builder.overrideSeverity` would express that and is `@RestrictedApi`: "Overriding the severity for individual Descriptions causes any command line options to be ignored, which is potentially very confusing." Measured: with it, a user who writes `-Xep:JSpecifyUnrecognizedAnnotationLocation:WARN` gets errors anyway. A build that wants the strict reading of the outer type has it from `NullableOnContainingClass`, which reports it as an error today. `CheckLocalVariableRootType` is the only location switch, and it is one key per location rather than one list of locations because `ErrorProneFlags` holds one value per key: two convention plugins each appending to a shared list would leave whichever ran last, silently. --------- Assisted-by: Claude Opus 5 <noreply@anthropic.com>
The file adapter now declares its package @NullMarked, and NullAway verifies it. A CSV cell, a table name in a model, and a field configuration are all absent-able, and the signatures now say so: field() reads a row whose cells may be absent, and the row converters carry a nullable element type. Two lazily populated fields were the reason for the remaining reports. FileReader.getTable wrote its result into tableElement and returned nothing, so no caller could see that the field was populated; it is now readTable, which returns the element it read. The bad-source-column check in FileRowConverter looked the heading up twice, once to validate and once to take the index, and now does both at once. A model that names no file for a CSV table, or no url for an HTML table, was already a NullPointerException deeper in; requireNonNull names the missing operand instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The model that the adapter's own test uses names neither bootstrap.servers nor topic.name, because a table that injects its own consumer needs neither, so both options are optional and KafkaTableOptions now says so. That reaches KafkaRowConverter.rowDataType, whose topic name is absent for such a table; neither implementation looks at it. The bootstrap servers are required on the path that builds a consumer, and requireNonNull there names the missing operand rather than letting the Kafka client report it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
SparkValues read the rowType field, which AbstractRelNode keeps as a lazily computed cache, where it meant the row type its constructor was given; getRowType() is the accessor that always has one. EnumerableToSparkConverter throws before it reaches its unfinished body, so the body is gone and the comment that describes what it would generate stays. RexToLixTranslator.translateCondition passes its correlates argument straight to setCorrelates, which has always accepted null, so the parameter says so now. That is what lets SparkCalc convert a program that has no correlates. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The call factory for Babel's CREATE TABLE takes the collection type out of a symbol literal, and SqlLiteral.symbolValue returns null for a literal that holds no symbol. The parser always writes one, so requireNonNull states that rather than leaving the constructor to find out. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A Redis schema with no password is the ordinary case, and it reaches the pool config through RedisConfig and RedisJedisManager, both of which say so now. RedisSchema validated its operands through isEmptyObject, whose result NullAway cannot connect back to the value, so the checks name the value they read and read it once. RedisTable had a RedisEnumerator field that nothing ever read. The anonymous Enumerable in RedisTable.scan is now a named inner class. NullAway checks an anonymous class's overrides against the erased supertype, dropping the @nullable on its type argument, so it reported the enumerator() override as a nullability mismatch; a named subclass with the same type argument is accepted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The push-down rule builds a search string from whichever of the two projections and the two row types the match happened to have, so those parameters are optional and the signature says so. A literal that is neither numeric nor CHAR yields no search text, which is how getFilter already reads the result. SplunkResultEnumerator reads the CSV header in its constructor, and a header it could not read leaves the field names absent; moveNext now stops instead of dereferencing them. close() swallowed the NullPointerException it raised on a null Closeable, and returns early instead. When the caller wants a single field that the header does not have, the enumerator yields a null element for every row. Its element type, and that of SplunkConnection.getSearchResultEnumerator, is @nullable Object to say so, and Linq4j.emptyEnumerator accepts a nullable element type, so the error path still returns an empty enumerator of that type. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A MongoDB document has no value for a field it does not carry, so a projection of one field enumerates nulls, and the getter, the enumerator and the enumerable that carry it now say so. That needed the element type of AbstractEnumerable to admit null, which Enumerable and Queryable have admitted all along; the four interfaces between them said otherwise, and now agree. The filter translator builds its documents with JsonBuilder, whose maps and lists hold absent values, and it passes a null operator to mean equality, which translateOp2 has always read that way. The two anonymous Enumerables in MongoTable are named classes, to avoid uber/NullAway#1746. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A Cassandra row has no value for a column it never set, so the enumerator and the enumerable that carries it enumerate nulls. The tuple components a STRUCT holds are already collected through requireNonNull, which is where the comment saying null cannot appear inside a collection lives. The enumerable is a named class rather than an anonymous one, to avoid uber/NullAway#1746. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Pig rel nodes look down the tree for the table they act on, and a tree with no table underneath returns none, which is what RelNode.getTable has always said. PigToEnumerableConverter read the rowType field, AbstractRelNode's lazily computed cache, where it meant this node's row type. A model that names no file or no columns for a Pig table reached the File and the array with a null; requireNonNull names the missing operand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An Arrow vector holds a null wherever the column has no value, which getValue returns for a timestamp and the enumerator hands on, so the enumerator, the enumerable and the query that builds it carry a nullable element type. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An InnoDB row has no value for a column that holds none, which the enumerator returns and the row array carries. The implementor and the internal expression node are filled in as the translation proceeds, so their fields are marked NullAway.Init rather than pretending a half-built object never exists. A model that names no sql file or no data file path reached the schema with a null; requireNonNull names the missing operand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Piglet keeps four maps from a relation to its alias and its Pig operator, and looking a name up in them can miss, which the getters now say. Handler looked its relations up the same way and pushed whatever came back, including nothing; it now reports the unknown name instead of failing later in the builder. PigTable.scan returned null rather than an enumerable, and nothing could have used it; it throws. SqlUserDefinedFunction declared its operand type inference non-null though SqlFunction below it has always accepted none, which is what PigUserDefinedFunction passes. PigRelBuilder.updateAlias takes a nullable operator and alias and records only what it is given, so collect() still accepts an input that no Pig operator was registered for, such as a plain scan. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Geode entry has no value for a field it does not carry, which the converters return and the enumerator hands on. The two schema factories read three required operands out of the model and passed them on unchecked; requireNonNull names whichever one is missing. The lazily built table maps and the limit an implement context may not have say so. The fourth operand, pdxSerializablePackagePath, is optional, and createClientCache takes it as @nullable. It passes the operand to Geode as main does, so a new client cache without it still fails in Geode's ReflectionBasedAutoSerializer; that defect of main is outside this change. GeodeTable and autodetectRelTypeFromRegion spell Region's wildcards as `? extends Object`; see uber/NullAway#1732. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An Elasticsearch hit carries either _source or fields and never both, so each of the two is absent half the time, and a document has no value for a field it does not carry. That runs through the getters, the row converters and the aggregation buckets, whose key is absent for a missing bucket. The predicate analyzer reads a literal that may hold no value: a range bound needs one and says so, while a term query writes whatever it got. A LIKE with no escape, a projection that is not an item reference, and an expression the analyzer cannot convert are all absent results the callers already handled. The schema factory takes the credentials and the path prefix from the model, where they are optional. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An os table function returns a row whose columns are absent wherever the command printed nothing, which the enumerators and the line parsers now carry. Ten of them built the same anonymous enumerable over an osquery table; they share OsQueryEnumerable instead, which also avoids uber/NullAway#1746, as do the named enumerator in the stdin function and the named line parser in vmstat. os.name is absent on a JVM that does not publish it, and the table functions switch on it. SqlShell prints a column that has no value, and looks a column label up in a map that may not hold it. The Avatica server for Chinook holds its server and its meta instance from the point it starts them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…verification A model that names no directory for a CSV schema, and no file for a CSV table, reached the File with a null; requireNonNull names the missing operand. The filterable table pushed down a literal that may hold no value. The maze enumerates without a solution set when the table is asked for the maze alone, which is what the null argument means. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The test fixtures carry absent values everywhere a SQL value can be null: the expected result of checkScalar and checkString, the column a result set reads, the origins and unique keys the metadata query may not know, and the alias a Pig relation may not have. The mock catalog and the mock planner fill their fields in as registration proceeds, so those are marked NullAway.Init. Twelve classes took the @nullable argument that Object.equals has always allowed. DiffRepository reads a DOM, where a node list yields no node past its length and an attribute may carry no value; the reads that cannot miss say so by name. The eight schemata packages that had no package-info.java now have one. BaseQueryable in :linq4j required a provider, though Smalls builds one that overrides enumerator() and never asks a provider to execute it; getProvider still refuses to return null. The anonymous enumerables in Smalls and InfiniteOrdersTable stay anonymous: an explicit type argument or a static helper avoids uber/NullAway#1746. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The benchmarks keep their sources in the jmh source set rather than a main one, so the verification follows them there, and the errorprone CI job builds jmhClasses alongside classes. A JMH state class is filled in by the @setup methods and the @PARAM values, which is what NullAway.Init says; the rest is the usual: a statistics map that a phase may not have produced, an employee with no commission, and an edge the graph may not hold. Two Error Prone warnings that the jmh source set had never been built against are fixed rather than suppressed: a helper that reads no instance state is static, and a parse failure during setup is thrown rather than printed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
NullAway 0.14.2 infers wildcard bounds and array types more precisely than 0.14.1, so 55 @SuppressWarnings("NullAway") and @SuppressWarnings("NullAway.Init") annotations no longer suppress anything. The suppression-remover script that ships with NullAway found them by removing each one and recompiling. The List::toArray method reference that the workaround for uber/NullAway#1728 avoided compiles too; that issue is closed. The new version also reports two places that 0.14.1 accepted: Util.cast passed an argument it already treats as non-null to castNonNull, and ArrayTable searched the array that holds a trailing null instead of the non-null alias it sorts. NullAway 0.14.2 crashes with a NullPointerException in TypeSubstitutionUtils when it reads sarg.rangeSet through Sarg<?> in the Elasticsearch PredicateAnalyzer, because Guava declares Range<C extends Comparable> with a raw bound. uber/NullAway#1898 fixes it after 0.14.2; until a release has it, PredicateAnalyzer reads the ranges through a generic helper. 0.14.2 also ships the JSpecifyUnrecognizedAnnotationLocation check, which reports a nullness annotation in a location JSpecify gives no meaning to, such as the root type of a local variable or of a cast. It is enabled as an error wherever NullAway runs. The Checker Framework did read @nullable in those locations, so the migration had carried 163 of them over; each one is removed in the commit that introduced it, or in the commit that moves the annotations to JSpecify when it predates this change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@contract, @MonotonicNonNull, @RequiresNonNull, @EnsuresNonNull and @EnsuresNonNullIf lived in calcite-linq4j, in org.apache.calcite.linq4j.annotations, only because linq4j is the lowest module that core depends on. They have nothing to do with LINQ, and the package is public API, so moving it after a release would break code that imports it. They now live in a new calcite-annotations module, package org.apache.calcite.annotations. The module depends on jspecify only. calcite-linq4j and calcite-core declare it as an API dependency, so a project that depends on either of them still gets the annotations. The commit also drops a comment in linq4j/build.gradle.kts that described a dependency that is no longer there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every module that NullAway verifies declares @NullMarked on its packages, and @nullable appears in their public signatures, so their API needs org.jspecify:jspecify. Ten modules declared it as an API dependency, elasticsearch and piglet as an implementation dependency, which keeps it off a consumer's compile classpath in Gradle, and ten modules did not declare it and got it only through calcite-core or calcite-linq4j. All published modules now declare it with api(). ubenchmark, which has only JMH sources and is not published, declares it as jmhImplementation. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@rubenada, thank you for the review. I should have caught that before requesting review. I've reworked the code so it is backward compatible (e.g. does not add excessive |
…By is built An earlier commit in this series made SqlOrderBy.setOperand refuse a null query (operand 0) and a null ORDER BY list (operand 1), since both fields are declared non-null. The constructor and SqlOrderBy.OPERATOR.createCall still stored a null, so the same value was accepted when the call was created and refused when an operand was replaced; createCall carried @SuppressWarnings("NullAway") to let it through. The constructor and createCall now check both operands as setOperand does, and the suppression is gone. The parser always supplies a query and uses SqlNodeList.EMPTY for a missing ORDER BY, so parsed SQL is unaffected. Caller sees: NullPointerException "query" from new SqlOrderBy(...) or SqlOrderBy.OPERATOR.createCall(...) when the query is null NullPointerException "orderList" from the same two when the ORDER BY list is null Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
@vlsi any idea why the "Build Website and Javadoc" CI is failing? |
The issue is that the job uses "latest" version of jekyll or something like that. |



Jira: CALCITE-7736
The Checker Framework verifies Calcite's nullness through a Gradle plugin of its own, 48
.astubfiles that patch the nullness of the JDK and of third-party libraries, and two dedicated CI jobs that cover:linq4j,:core, and:server. NullAway runs as an Error Prone check, ships nullness models for the JDK and common libraries, and reads JSpecify annotations, which other checkers read too.What changes
CheckContracts. It covers the main code of every module that has code, 24 projects instead of 3,:annotationsincluded. Test code is not verified.build.gradle.kts, to be enabled one at a time later. Every check that was active on main stays active;DoNotCallandReturnValueIgnoredreport two places under 2.50.0, listed below..astubfiles and both Checker Framework jobs are removed.ErrorProne and NullAway (JDK 25, latest Guava)is the only job that checks nullness, so a nullness error fails that job and not the test jobs. The stubs also described Avatica, Janino, and slf4j; NullAway treats those unannotated libraries as returning non-null.@Nullableand@NonNullmove fromorg.checkerframework.checker.nullness.qualtoorg.jspecify.annotations. Both areTYPE_USE, so no annotation changes position. Autostyle rewrites the old imports from now on.@NullMarkedinstead of@DefaultQualifier, and NullAway'sRequireExplicitNullMarkingcheck fails a class that no@NullMarkedcovers.@MonotonicNonNull,@RequiresNonNull,@EnsuresNonNull,@EnsuresNonNullIf, and@Contractare declared in a newcalcite-annotationsmodule, packageorg.apache.calcite.annotations, which depends onjspecifyonly. NullAway matches them by simple name, so Calcite takes no dependency on the checker.@PolyNullon a generic method becomes the type variable itself:<T extends @Nullable Object> T first(@Nullable T v0, T v1)returns a non-null value for a non-null default to javac, NullAway, and Kotlin alike. This coversUtil.first,ExtendedEnumerable.aggregatewith a seed, and the sixCalciteConnectionConfigplugin getters, which takeClass<@NonNull T>so that a null default still infers. Elsewhere@PolyNullbecomes@Nullablewith a@Contractclause such as"!null, _ -> !null", 94 of them, and NullAway checks each clause against the method body. A receiver parameter, a varargs method, and a type argument cannot carry a clause, so they keep a plain@Nullable.ElementType.MODULEfrom the@Targetof JSpecify's@NullMarkedbefore the jar reaches the compile classpath. Without it javac warnsunknown enum constant ElementType.MODULE, and-Werrorfails the build (A@NullMarkedalternative without@Target(MODULE)jspecify/jspecify#795). javac 25 with--release 8still warns, so the transform stays after [CALCITE-4553] Use Gradle toolchain to compile Calcite with JDK 25 and --release 8 #5278.The part worth reviewing
The two tools read an unbounded type parameter differently. Under the Checker Framework,
<T>means<T extends @Nullable Object>; under JSpecify and@NullMarked, it means a non-nullObject. Calcite relied on the first reading:SqlShuttleis aSqlVisitor<@Nullable SqlNode>, and it is passed toSqlNode.accept(SqlVisitor<R>). The commit "Give type parameters the nullable bounds the Checker Framework inferred" writes the nullable bound out at 61 declarations, among them the Rex and Sql visitors,Pair,PairList,Enumerable, and the linq4jFunctiontypes. The erasure is unchanged, so the change is binary compatible.The commits after it fix what NullAway reported, one area per commit, and the last ones add the modules to
nullawayProjectsone per commit. The rename commit, "Move the Checker Framework annotations to JSpecify", touches 1273 files and is worth skimming rather than reading. The last commits of the branch answer a review of the earlier ones: they restore what NullAway did not need, keep@PolyNullsemantics for the generic methods, and simplify workarounds.What the caller sees
Code that worked on main and fails or behaves differently with this change:
SqlOrderBy.setOperand(0, null)andsetOperand(1, null)throwNullPointerException(operand,orderList), asSqlSelect.setOperandalready does for its required operands.TimeExtractionFunction.translateCastToTimeExtract(rexNode, null)throwsNullPointerException. The parameter was already non-null by default on main; only a defensive check went.PigTable.scanthrowsUnsupportedOperationException: PigTable cannot be scannedinstead of returning null, which every caller dereferenced.ChainedSqlOperatorTable.add(deprecated) throwsUnsupportedOperationException: ChainedSqlOperatorTable is immutable; use SqlOperatorTables.chainwhere it threw a message-lessUnsupportedOperationExceptionfromImmutableList.add.requireNonNull, so a missing operand fails withNullPointerExceptionnaming it (hostfor Cassandra,databasefor MongoDB,sqlFilePathandibdDataFileBasePathfor InnoDB,locatorHostandlocatorPortfor Geode) instead ofIllegalArgumentException,NumberFormatException, or a wrappedRuntimeException. A model that worked before still works.Defects fixed along the way, each needed for NullAway to accept the code:
FunctionExpression.getFunction()no longer fails withNullPointerException(the proxy hands it a nullargsarray);ExpressionTest.testLambdaWithoutParametersViaGetFunctionis new.FilterProjectTransposeRuleuses the default distribution when the input lacks the distribution trait, where it failed withNullPointerExceptioninRelDistributionTraitDef.canonize.DruidQuery.isValidLeafCastandDruidTable.isValidParentKindreturn false for a type without a family and for a null parent, where they threwNullPointerException.Not changed:
PigRelBuilder.collect()still accepts an input that no Pig operator was registered for;PigRelBuilderTestin:pigletguards it.pdxSerializablePackagePathis a defect of main, fixed separately in [CALCITE-7852] Geode schema fails with NullPointerException when pdxSerializablePackagePath is not set #5325 (CALCITE-7852). HereGeodeUtils.createClientCachedeclares the parameter@Nullable, because the factories pass the absent operand, and still fails on it as main does.What downstream users see
org.jspecify:jspecify1.0.1 as an API dependency, andcalcite-coreandcalcite-linq4jalso declare the newcalcite-annotations. Before, 12 modules declaredorg.checkerframework:checker-qual, two of them as an implementation dependency, and the other modules got it throughcalcite-coreorcalcite-linq4j. Code that importsorg.checkerframeworkannotations and getschecker-qualonly through Calcite stops compiling until it declares that dependency itself.@NullMarkedpackages. Kotlin 2.x reads JSpecify strictly by default: a parameter without@Nullableno longer acceptsnull, and a method whose@PolyNullbecame@Nullableplus@Contractreturns a nullable type, because Kotlin does not read@Contract. That applies to the 94 non-generic methods, among themRexShuttle.apply,SqlUtil.stripAs,RelOptUtil.toString(RelNode, SqlExplainLevel, boolean),RexLiteral.fromJdbcString,NumberUtil.add, and most ofSqlFunctions; the generic ones keep a non-null result for a non-null argument. Kotlin users can turn these errors into warnings with-Xjspecify-annotations=warn.ExtendedEnumerable.correlateJointakesFunction2<TSource, ? super @Nullable TInner, TResult>, so a class that overrides it with the old parameter type no longer compiles; callers are not affected.JsonFunctions.jsonObjectAggAddandjsonArrayAggAddtakeMap<String, @Nullable Object>andList<@Nullable Object>instead of raw types.Workarounds for NullAway 0.14.2 and the MongoDB driver
NullPointerExceptioninTypeSubstitutionUtilsonsarg.rangeSet.asRanges()read throughSarg<?>in the ElasticsearchPredicateAnalyzer, because Guava declaresRange<C extends Comparable>with a raw bound. Skip nested annotation restoration from raw source types uber/NullAway#1898 fixes it after 0.14.2; until a release has it,PredicateAnalyzerreads the ranges through a generic helper.:mongodbdeclarescompileOnly("com.google.code.findbugs:jsr305"). The driver'scom.mongodb.lang.Nullablecarries@javax.annotation.Nonnull(when = When.MAYBE), and the driver's POM does not declare jsr305. With javac 25 and NullAway, javac reportsunknown enum constant When.MAYBE, which-Werrorturns into an error; javac 21 with NullAway and javac 25 without it compile cleanly. The driver's move to JSpecify is JAVA-5938, planned for a major release.Verification
This is what the nullness job runs. It compiles with the JDK 25 toolchain that #5278 introduced; Gradle 8.14 cannot run on JDK 25, so the CI job runs Gradle on JDK 21. It passes in CI, as do the test jobs on Linux (JDK 8, 11, 17, 21, 24), Windows (JDK 8, 17, 21), and macOS (JDK 21); the
bytecodeCheckfailures on javac 8 and 11 are gone now that everything compiles with javac 25.PigRelBuilderTestin:pigletis new and fails on the earlier head of this branch withNullPointerException: pigOp.The Kotlin claim was checked with kotlinc 2.4.20 against the compiled classes of
:coreand:linq4j:val s: String = Util.first(x, ""),config.parserFactory(SqlParserImplFactory::class.java, default)assigned to a non-null type, andaggregate(0) { a, b -> a + b }returningIntcompile on this head and fail withreturn type mismatch: expected 'String', actual 'String?'and two more such errors on 5d171ba.🤖 Generated with Claude Code