Skip to content

Commit 050b324

Browse files
committed
[Omit needless words] Split before last preposition in most cases.
Splitting *before* the last preposition tends to keep the prepositional phrase together. Only leave the preposition on the base name in rare cases where we would end up with weird argument labels (e.g., prefer "moveTo(x:y:)" to "move(toX:y:)"). Also, refine our heuristics for when we can remove the preposition entirely.
1 parent 4989007 commit 050b324

File tree

4 files changed

+104
-84
lines changed

4 files changed

+104
-84
lines changed

lib/Basic/StringExtras.cpp

Lines changed: 85 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -820,26 +820,18 @@ StringRef camel_case::toLowercaseInitialisms(StringRef string,
820820
return scratch.copyString(scratchStr);
821821
}
822822

823-
/// Determine whether the given word preceding "with" indicates that
824-
/// "with" should be retained.
825-
static bool wordPairsWithWith(StringRef word) {
826-
return camel_case::sameWordIgnoreFirstCase(word, "compatible");
827-
}
828-
829823
/// Determine whether the given word occurring before the given
830824
/// preposition results in a conflict that suppresses preposition
831825
/// splitting.
832826
static bool wordConflictsBeforePreposition(StringRef word,
833827
StringRef preposition) {
834-
if (camel_case::sameWordIgnoreFirstCase(preposition, "with")) {
835-
if (camel_case::sameWordIgnoreFirstCase(word, "encode"))
836-
return true;
837-
838-
if (camel_case::sameWordIgnoreFirstCase(word, "copy"))
839-
return true;
828+
if (camel_case::sameWordIgnoreFirstCase(preposition, "with") &&
829+
camel_case::sameWordIgnoreFirstCase(word, "compatible"))
830+
return true;
840831

841-
return false;
842-
}
832+
if (camel_case::sameWordIgnoreFirstCase(preposition, "of") &&
833+
camel_case::sameWordIgnoreFirstCase(word, "kind"))
834+
return true;
843835

844836
return false;
845837
}
@@ -855,27 +847,78 @@ static bool wordConflictsAfterPreposition(StringRef word,
855847
return true;
856848
}
857849

858-
if (camel_case::sameWordIgnoreFirstCase(word, "visible") &&
859-
camel_case::sameWordIgnoreFirstCase(preposition, "to"))
850+
if (camel_case::sameWordIgnoreFirstCase(preposition, "to") &&
851+
camel_case::sameWordIgnoreFirstCase(word, "visible"))
860852
return true;
861853

862854
return false;
863855
}
864856

865-
/// Determine whether there is a preposition in the given name.
866-
static bool containsPreposition(StringRef name) {
867-
for (auto word : camel_case::getWords(name)) {
868-
if (camel_case::sameWordIgnoreFirstCase(word, "with")) continue;
869-
if (getPartOfSpeech(word) == PartOfSpeech::Preposition) return true;
857+
/// When splitting based on a preposition, whether we should place the
858+
/// preposition on the argument label (vs. on the base name).
859+
static bool shouldPlacePrepositionOnArgLabel(StringRef beforePreposition,
860+
StringRef preposition,
861+
StringRef afterPreposition) {
862+
// X/Y/Z often used as coordinates and should be the labels.
863+
if (afterPreposition == "X" ||
864+
afterPreposition == "Y" ||
865+
afterPreposition == "Z")
866+
return false;
867+
868+
return true;
869+
}
870+
871+
/// Determine whether the preposition in a split is "vacuous", and
872+
/// should be removed.
873+
static bool isVacuousPreposition(StringRef beforePreposition,
874+
StringRef preposition,
875+
StringRef afterPreposition,
876+
const OmissionTypeName &paramType) {
877+
// Only consider "with" or "using" to be potentially vacuous.
878+
if (!camel_case::sameWordIgnoreFirstCase(preposition, "with") &&
879+
!camel_case::sameWordIgnoreFirstCase(preposition, "using"))
880+
return false;
881+
882+
// If the preposition is "with", check for special cases.
883+
if (camel_case::sameWordIgnoreFirstCase(preposition, "with")) {
884+
// Some words following the preposition indicate that "with" is
885+
// not vacuous.
886+
auto following = camel_case::getFirstWord(afterPreposition);
887+
if (camel_case::sameWordIgnoreFirstCase(following, "coder") ||
888+
camel_case::sameWordIgnoreFirstCase(following, "zone"))
889+
return false;
890+
891+
// If the last word of the argument label looks like a past
892+
// participle (ends in "-ed"), the preposition is not vacuous.
893+
auto lastWord = camel_case::getLastWord(afterPreposition);
894+
if (lastWord.endswith("ed"))
895+
return false;
896+
897+
if (camel_case::sameWordIgnoreFirstCase(following, "delegate") ||
898+
camel_case::sameWordIgnoreFirstCase(following, "frame"))
899+
return true;
870900
}
901+
902+
// If the parameter has a default argument, it's vacuous.
903+
if (paramType.hasDefaultArgument()) return true;
904+
905+
// If the parameter is of function type, it's vacuous.
906+
if (paramType.isFunction()) return true;
907+
908+
// If the first word of the name is a verb, the preposition is
909+
// likely vacuous.
910+
if (getPartOfSpeech(camel_case::getFirstWord(beforePreposition))
911+
== PartOfSpeech::Verb)
912+
return true;
913+
871914
return false;
872915
}
873916

874917
/// Split the base name after the last preposition, if there is one.
875-
static bool splitBaseNameAfterLastPreposition(StringRef &baseName,
876-
StringRef &argName,
877-
const OmissionTypeName &paramType,
878-
StringRef secondArgName) {
918+
static bool splitBaseNameAfterLastPreposition(
919+
StringRef &baseName,
920+
StringRef &argName,
921+
const OmissionTypeName &paramType) {
879922
// Scan backwards for a preposition.
880923
auto nameWords = camel_case::getWords(baseName);
881924
auto nameWordRevIter = nameWords.rbegin(),
@@ -917,36 +960,21 @@ static bool splitBaseNameAfterLastPreposition(StringRef &baseName,
917960
return false;
918961

919962
// Determine whether we should drop the preposition.
920-
bool dropPreposition = false;
921-
922-
// If the first parameter has a default, drop the preposition.
923-
if (paramType.hasDefaultArgument()) {
924-
dropPreposition = true;
925-
926-
// If the preposition is "with" and the base name starts with a
927-
// verb, assume "with" is a separator and remove it.
928-
} else if (nameWordRevIter.base().getPosition() > 4 &&
929-
camel_case::sameWordIgnoreFirstCase(preposition, "with") &&
930-
!wordPairsWithWith(*std::next(nameWordRevIter)) &&
931-
getPartOfSpeech(camel_case::getFirstWord(baseName))
932-
== PartOfSpeech::Verb) {
933-
dropPreposition = true;
934-
935-
// If the preposition is "using" and the parameter is a function or
936-
// block, assume "using" is a separator and remove it.
937-
} else if (nameWordRevIter.base().getPosition() > 5 &&
938-
paramType.isFunction() &&
939-
camel_case::sameWordIgnoreFirstCase(preposition, "using")) {
940-
dropPreposition = true;
941-
}
942-
943-
// If we have a preposition in the second argument label, pull the
944-
// first preposition into the first argument label to balance them.
945-
bool prepositionOnArgLabel = false;
946-
if (containsPreposition(secondArgName)) {
947-
prepositionOnArgLabel = true;
963+
StringRef beforePreposition(baseName.begin(),
964+
preposition.begin() - baseName.begin());
965+
StringRef afterPreposition(preposition.end(),
966+
baseName.end() - preposition.end());
967+
bool dropPreposition = isVacuousPreposition(beforePreposition,
968+
preposition,
969+
afterPreposition,
970+
paramType);
971+
972+
// By default, put the prposition on the argument label.
973+
bool prepositionOnArgLabel =
974+
shouldPlacePrepositionOnArgLabel(beforePreposition, preposition,
975+
afterPreposition);
976+
if (prepositionOnArgLabel)
948977
++nameWordRevIter;
949-
}
950978

951979
unsigned startOfArgumentLabel = nameWordRevIter.base().getPosition();
952980
unsigned endOfBaseName = startOfArgumentLabel;
@@ -990,8 +1018,7 @@ static bool splitBaseNameAfterLastPreposition(StringRef &baseName,
9901018
/// Split the base name, if it makes sense.
9911019
static bool splitBaseName(StringRef &baseName, StringRef &argName,
9921020
const OmissionTypeName &paramType,
993-
StringRef paramName,
994-
StringRef secondArgName) {
1021+
StringRef paramName) {
9951022
// If there is already an argument label, do nothing.
9961023
if (!argName.empty()) return false;
9971024

@@ -1014,20 +1041,12 @@ static bool splitBaseName(StringRef &baseName, StringRef &argName,
10141041
return false;
10151042

10161043
// Try splitting after the last preposition.
1017-
if (splitBaseNameAfterLastPreposition(baseName, argName, paramType,
1018-
secondArgName))
1044+
if (splitBaseNameAfterLastPreposition(baseName, argName, paramType))
10191045
return true;
10201046

10211047
return false;
10221048
}
10231049

1024-
// Retrieve the second "real" argument label.
1025-
static StringRef getSecondArgumentName(ArrayRef<StringRef> names) {
1026-
if (names.empty()) return "";
1027-
if (names[0] == "error") return getSecondArgumentName(names.slice(1));
1028-
return names[0];
1029-
}
1030-
10311050
bool swift::omitNeedlessWords(StringRef &baseName,
10321051
MutableArrayRef<StringRef> argNames,
10331052
StringRef firstParamName,
@@ -1102,8 +1121,7 @@ bool swift::omitNeedlessWords(StringRef &baseName,
11021121

11031122
// If needed, split the base name.
11041123
if (!argNames.empty() &&
1105-
splitBaseName(baseName, argNames[0], paramTypes[0], firstParamName,
1106-
getSecondArgumentName(argNames.slice(1))))
1124+
splitBaseName(baseName, argNames[0], paramTypes[0], firstParamName))
11071125
anyChanges = true;
11081126

11091127
// Omit needless words based on parameter types.

test/IDE/Inputs/custom-modules/OmitNeedlessWords.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@
99
-(void)removeWithNoRemorse:(nonnull id)object;
1010
-(void)bookmarkWithURLs:(nonnull NSArray<NSURL *> *)urls;
1111
-(void)saveToURL:(nonnull NSURL *)url forSaveOperation:(NSInteger)operation;
12+
-(void)indexWithItemNamed:(nonnull NSString *)name;
1213
@end

test/IDE/print_omit_needless_words.swift

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,19 @@
3535
// CHECK-FOUNDATION: func makeObjectsPerform(_: Selector, withObject: AnyObject?, withObject: AnyObject?)
3636

3737
// Note: id -> "Object".
38-
// CHECK-FOUNDATION: func indexOf(_: AnyObject) -> Int
38+
// CHECK-FOUNDATION: func index(of _: AnyObject) -> Int
3939

4040
// Note: Class -> "Class"
4141
// CHECK-OBJECTIVEC: func isKindOf(aClass: AnyClass) -> Bool
4242

4343
// Note: Pointer-to-struct name matching; "with" splits the first
4444
// piece, then the "with" is dropped.
4545
//
46-
// CHECK-FOUNDATION: func copyWith(_: Zone = nil) -> AnyObject!
46+
// CHECK-FOUNDATION: func copy(withZone _: Zone = nil) -> AnyObject!
4747

4848
// Note: Objective-C type parameter names.
49-
// CHECK-FOUNDATION: func objectFor(_: Copying) -> AnyObject?
50-
// CHECK-FOUNDATION: func removeObjectFor(_: Copying)
49+
// CHECK-FOUNDATION: func object(forKey _: Copying) -> AnyObject?
50+
// CHECK-FOUNDATION: func removeObject(forKey _: Copying)
5151

5252
// Note: Don't drop the name of the first parameter in an initializer entirely.
5353
// CHECK-FOUNDATION: init(array: [AnyObject])
@@ -112,8 +112,8 @@
112112
// Note: <context type>By<gerund> --> <gerund>.
113113
// CHECK-FOUNDATION: func withString(_: String) -> String
114114

115-
// Note: Not splitting on "With".
116-
// CHECK-FOUNDATION: func urlWith(addedString _: String) -> URL?
115+
// Note: Noun phrase puts preposition inside.
116+
// CHECK-FOUNDATION: func url(withAddedString _: String) -> URL?
117117

118118
// Note: CalendarUnits is not a set of "Options".
119119
// CHECK-FOUNDATION: class func forCalendarUnits(_: CalendarUnit) -> String!
@@ -207,19 +207,19 @@
207207
// CHECK-APPKIT: func getRGBAComponents(_: UnsafeMutablePointer<Int8>)
208208

209209
// Note: Skipping over "3D"
210-
// CHECK-APPKIT: func drawInAirAt(_: Point3D)
210+
// CHECK-APPKIT: func drawInAir(at _: Point3D)
211211

212212
// Note: with<something> -> <something>
213-
// CHECK-APPKIT: func drawAt(_: Point3D, withAttributes: [String : AnyObject]? = [:])
213+
// CHECK-APPKIT: func draw(at _: Point3D, withAttributes: [String : AnyObject]? = [:])
214214

215215
// Note: Don't strip names that aren't preceded by a verb or preposition.
216216
// CHECK-APPKIT: func setTextColor(_: NSColor?)
217217

218218
// Note: Splitting with default arguments.
219-
// CHECK-APPKIT: func drawIn(_: NSView?)
219+
// CHECK-APPKIT: func draw(in _: NSView?)
220220

221221
// Note: NSDictionary default arguments for "options"
222-
// CHECK-APPKIT: func drawAnywhereIn(_: NSView?, options: [Object : AnyObject] = [:])
222+
// CHECK-APPKIT: func drawAnywhere(in _: NSView?, options: [Object : AnyObject] = [:])
223223
// CHECK-APPKIT: func drawAnywhere(options _: [Object : AnyObject] = [:])
224224

225225
// Note: no lowercasing of initialisms when there might be a prefix.
@@ -245,7 +245,7 @@
245245

246246
// CHECK-APPKIT: func dismiss(animated _: Bool)
247247

248-
// CHECK-APPKIT: func shouldCollapseAutoExpandedItemsFor(deposited _: Bool) -> Bool
248+
// CHECK-APPKIT: func shouldCollapseAutoExpandedItems(forDeposited _: Bool) -> Bool
249249

250250
// Introducing argument labels and pruning the base name.
251251
// CHECK-APPKIT: func rectForCancelButtonWhenCentered(_: Bool)
@@ -256,25 +256,26 @@
256256
// CHECK-APPKIT: func setContentHuggingPriority(_: NSLayoutPriority)
257257

258258
// Look through typedefs of pointers.
259-
// CHECK-APPKIT: func layoutAt(_: NSPointPointer)
259+
// CHECK-APPKIT: func layout(at _: NSPointPointer)
260260

261261
// The presence of a property prevents us from stripping redundant
262262
// type information from the base name.
263263
// CHECK-APPKIT: func addGestureRecognizer(_: NSGestureRecognizer)
264264
// CHECK-APPKIT: func removeGestureRecognizer(_: NSGestureRecognizer)
265-
// CHECK-APPKIT: func favoriteViewFor(_: NSGestureRecognizer) -> NSView?
265+
// CHECK-APPKIT: func favoriteView(forGestureRecognizer _: NSGestureRecognizer) -> NSView?
266266
// CHECK-APPKIT: func addLayoutConstraints(_: Set<NSLayoutConstraint>)
267267
// CHECK-APPKIT: func add(_: Rect)
268268
// CHECK-APPKIT: class func conjureRect(_: Rect)
269269

270-
// CHECK-OMIT-NEEDLESS-WORDS: func jumpTo(_: URL)
270+
// CHECK-OMIT-NEEDLESS-WORDS: func jump(to _: URL)
271271
// CHECK-OMIT-NEEDLESS-WORDS: func objectIsCompatibleWith(_: AnyObject) -> Bool
272272
// CHECK-OMIT-NEEDLESS-WORDS: func insetBy(x _: Int, y: Int)
273273
// CHECK-OMIT-NEEDLESS-WORDS: func setIndirectlyToValue(_: AnyObject)
274274
// CHECK-OMIT-NEEDLESS-WORDS: func jumpToTop(_: AnyObject)
275275
// CHECK-OMIT-NEEDLESS-WORDS: func removeWithNoRemorse(_: AnyObject)
276-
// CHECK-OMIT-NEEDLESS-WORDS: func bookmarkWith(_: [URL])
276+
// CHECK-OMIT-NEEDLESS-WORDS: func bookmark(withURLs _: [URL])
277277
// CHECK-OMIT-NEEDLESS-WORDS: func save(to _: URL, forSaveOperation: Int)
278+
// CHECK-OMIT-NEEDLESS-WORDS: func index(withItemNamed _: String)
278279

279280
// Don't drop the 'error'.
280281
// CHECK-ERRORS: func tryAndReturnError(_: ()) throws

test/Sema/omit_needless_words.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
class C1 {
44
init(tasteString: String) { } // expected-warning{{'init(tasteString:)' could be named 'init(taste:)'}}{{8-8=taste }}
55
func processWithString(string: String, toInt: Int) { } // expected-warning{{'processWithString(_:toInt:)' could be named 'process(withString:to:)'}}{{8-25=process}}{{42-42=to }}
6-
func processWithInt(value: Int) { } // expected-warning{{'processWithInt' could be named 'processWith'}}{{8-22=processWith}}
6+
func processWithInt(value: Int) { } // expected-warning{{'processWithInt' could be named 'process(withInt:)'}}{{8-22=process}}
77
}
88

99
extension String {
@@ -14,7 +14,7 @@ extension String {
1414
func callSites(s: String) {
1515
let c1 = C1(tasteString: "blah") // expected-warning{{'init(tasteString:)' could be named 'init(taste:)'}}{{15-26=taste}}
1616
c1.processWithString("a", toInt: 1) // expected-warning{{'processWithString(_:toInt:)' could be named 'process(withString:to:)'}}{{6-23=process}}{{29-34=to}}
17-
c1.processWithInt(5) // expected-warning{{'processWithInt' could be named 'processWith'}}{{6-20=processWith}}
17+
c1.processWithInt(5) // expected-warning{{'processWithInt' could be named 'process(withInt:)'}}{{6-20=process}}
1818
_ = String.randomString // expected-warning{{'randomString' could be named 'random'}}{{14-26=random}}
1919
_ = s.wonkycasedString // expected-warning{{'wonkycasedString' could be named 'wonkycased'}}{{9-25=wonkycased}}
2020
}

0 commit comments

Comments
 (0)